feat(video): add ffmpeg-backed video converter for mp4/webm/mov/avi/mkv

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 21:58:23 +02:00
co-authored by Claude Sonnet 5
parent 886c2e8539
commit 913dbac691
2 changed files with 154 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { register } from './registry.js';
const execFileAsync = promisify(execFile);
export const VIDEO_FORMATS = ['mp4', 'webm', 'mov', 'avi', 'mkv'];
const CODEC_ARGS = {
mp4: ['-c:v', 'libx264', '-c:a', 'aac'],
mov: ['-c:v', 'libx264', '-c:a', 'aac'],
mkv: ['-c:v', 'libx264', '-c:a', 'aac'],
webm: ['-c:v', 'libvpx-vp9', '-c:a', 'libopus'],
avi: ['-c:v', 'mpeg4', '-c:a', 'libmp3lame'],
};
export function registerVideoConverters() {
for (const sourceFormat of VIDEO_FORMATS) {
for (const targetFormat of VIDEO_FORMATS) {
if (sourceFormat === targetFormat) continue;
register({
family: 'video',
sourceFormat,
targetFormat,
convert: async (inputPath, outputPath, { quality, timeoutMs } = {}) => {
const ffmpegPath = process.env.FFMPEG_PATH || 'ffmpeg';
const args = ['-y', '-i', inputPath, ...CODEC_ARGS[targetFormat]];
if (quality != null) {
args.push('-vf', `scale=-2:${quality}`);
}
args.push(outputPath);
await execFileAsync(ffmpegPath, args, { timeout: timeoutMs });
},
});
}
}
}