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:
Copilot
2025-12-24 16:07:55 +01:00
committed by GitHub
parent c6831212b4
commit b5f6c7fa3f
2 changed files with 34 additions and 33 deletions

View File

@@ -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"

View File

@@ -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