Convert cutVideoSegments.sh from shell wrapper to native Node.js script (#1002)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: thomasnordquist <7721625+thomasnordquist@users.noreply.github.com>
This commit is contained in:
@@ -1,28 +1,32 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env node
|
||||||
set -e
|
|
||||||
|
|
||||||
# Read scenes.json and cut video into segments as GIFs
|
/**
|
||||||
if [ ! -f "scenes.json" ]; then
|
* Cut video into segments as GIFs based on scenes.json
|
||||||
echo "scenes.json not found"
|
*
|
||||||
exit 1
|
* This script reads scenes.json and uses ffmpeg to create GIF segments
|
||||||
fi
|
* from the ui-test.mp4 video file.
|
||||||
|
*/
|
||||||
|
|
||||||
if [ ! -f "ui-test.mp4" ]; then
|
|
||||||
echo "ui-test.mp4 not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Cutting video into GIF segments based on scenes.json..."
|
|
||||||
|
|
||||||
export GIF_SCALE="1024"
|
|
||||||
|
|
||||||
# Parse scenes.json and cut video segments as GIFs
|
|
||||||
node -e "
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
|
|
||||||
|
// Check required files exist
|
||||||
|
if (!fs.existsSync('scenes.json')) {
|
||||||
|
console.error('scenes.json not found');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync('ui-test.mp4')) {
|
||||||
|
console.error('ui-test.mp4 not found');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Cutting video into GIF segments based on scenes.json...');
|
||||||
|
|
||||||
const scenes = JSON.parse(fs.readFileSync('scenes.json', 'utf8'));
|
const scenes = JSON.parse(fs.readFileSync('scenes.json', 'utf8'));
|
||||||
|
|
||||||
|
const GIF_SCALE = process.env.GIF_SCALE || '1024';
|
||||||
|
|
||||||
console.log('Creating GIF segments...');
|
console.log('Creating GIF segments...');
|
||||||
|
|
||||||
// Sanitize scene name to prevent path traversal and command injection
|
// Sanitize scene name to prevent path traversal and command injection
|
||||||
@@ -33,13 +37,13 @@ function sanitizeName(name) {
|
|||||||
|
|
||||||
async function cutSegmentAsGif(scene, index) {
|
async function cutSegmentAsGif(scene, index) {
|
||||||
const safeName = sanitizeName(scene.name);
|
const safeName = sanitizeName(scene.name);
|
||||||
const segmentName = \`segment-\${String(index + 1).padStart(2, '0')}-\${safeName}\`;
|
const segmentName = `segment-${String(index + 1).padStart(2, '0')}-${safeName}`;
|
||||||
const paletteFile = \`\${segmentName}-palette.png\`;
|
const paletteFile = `${segmentName}-palette.png`;
|
||||||
const outputFile = \`\${segmentName}.gif\`;
|
const outputFile = `${segmentName}.gif`;
|
||||||
const startTime = scene.start / 1000; // Convert ms to seconds
|
const startTime = scene.start / 1000; // Convert ms to seconds
|
||||||
const duration = scene.duration / 1000; // Convert ms to seconds
|
const duration = scene.duration / 1000; // Convert ms to seconds
|
||||||
|
|
||||||
console.log(\`Creating \${outputFile} (start: \${startTime}s, duration: \${duration}s)\`);
|
console.log(`Creating ${outputFile} (start: ${startTime}s, duration: ${duration}s)`);
|
||||||
|
|
||||||
// Step 1: Generate palette for this segment
|
// Step 1: Generate palette for this segment
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
@@ -48,7 +52,7 @@ async function cutSegmentAsGif(scene, index) {
|
|||||||
'-ss', startTime.toString(),
|
'-ss', startTime.toString(),
|
||||||
'-t', duration.toString(),
|
'-t', duration.toString(),
|
||||||
'-i', 'ui-test.mp4',
|
'-i', 'ui-test.mp4',
|
||||||
'-vf', 'fps=10,scale=${process.env.GIF_SCALE || 1024}:-1:flags=lanczos,palettegen',
|
'-vf', `fps=10,scale=${GIF_SCALE}:-1:flags=lanczos,palettegen`,
|
||||||
paletteFile
|
paletteFile
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -56,8 +60,8 @@ async function cutSegmentAsGif(scene, index) {
|
|||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
console.error(\`Failed to create palette for \${outputFile}\`);
|
console.error(`Failed to create palette for ${outputFile}`);
|
||||||
reject(new Error(\`ffmpeg palette generation exited with code \${code}\`));
|
reject(new Error(`ffmpeg palette generation exited with code ${code}`));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -70,7 +74,7 @@ async function cutSegmentAsGif(scene, index) {
|
|||||||
'-t', duration.toString(),
|
'-t', duration.toString(),
|
||||||
'-i', 'ui-test.mp4',
|
'-i', 'ui-test.mp4',
|
||||||
'-i', paletteFile,
|
'-i', paletteFile,
|
||||||
'-filter_complex', 'fps=10,scale=${process.env.GIF_SCALE || 1024}:-1:flags=lanczos[x];[x][1:v]paletteuse',
|
'-filter_complex', `fps=10,scale=${GIF_SCALE}:-1:flags=lanczos[x];[x][1:v]paletteuse`,
|
||||||
outputFile
|
outputFile
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -85,8 +89,8 @@ async function cutSegmentAsGif(scene, index) {
|
|||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
console.error(\`Failed to create \${outputFile}\`);
|
console.error(`Failed to create ${outputFile}`);
|
||||||
reject(new Error(\`ffmpeg GIF creation exited with code \${code}\`));
|
reject(new Error(`ffmpeg GIF creation exited with code ${code}`));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -96,11 +100,8 @@ async function cutSegmentAsGif(scene, index) {
|
|||||||
for (let i = 0; i < scenes.length; i++) {
|
for (let i = 0; i < scenes.length; i++) {
|
||||||
await cutSegmentAsGif(scenes[i], i);
|
await cutSegmentAsGif(scenes[i], i);
|
||||||
}
|
}
|
||||||
console.log('All GIF segments created successfully');
|
console.log('Video segments created successfully');
|
||||||
})().catch(err => {
|
})().catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
"
|
|
||||||
|
|
||||||
echo "Video segments created successfully"
|
|
||||||
@@ -29,7 +29,7 @@ mv app720.gif ui-test.gif
|
|||||||
# Cut video into segments based on scenes.json
|
# Cut video into segments based on scenes.json
|
||||||
echo "Cutting video into segments..."
|
echo "Cutting video into segments..."
|
||||||
if [ -f "scenes.json" ]; then
|
if [ -f "scenes.json" ]; then
|
||||||
./scripts/cutVideoSegments.sh
|
node ./scripts/cutVideoSegments.js
|
||||||
else
|
else
|
||||||
echo "Warning: scenes.json not found, skipping segment creation"
|
echo "Warning: scenes.json not found, skipping segment creation"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user