import { execFile } from 'child_process'; import { promisify } from 'util'; const execFileAsync = promisify(execFile); export const runFFmpeg = async (args, options = {}) => { const { stdio = 'pipe', ...otherOptions } = options; try { const { stdout, stderr } = await execFileAsync('ffmpeg', args, { stdio, ...otherOptions, }); return { stdout, stderr }; } catch (error) { throw new Error(`FFmpeg error: ${error.message}\nStderr: ${error.stderr}`); } }; export const getMediaDuration = async (inputPath) => { const args = [ '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1:noinvalidchars=1', inputPath, ]; const { stdout } = await runFFmpeg(args); return parseFloat(stdout.trim()); }; export const extractFrameAtTime = async (inputPath, outputPath, timeCode) => { const args = [ '-i', inputPath, '-ss', timeCode, '-vframes', '1', '-q:v', '2', outputPath, ]; await runFFmpeg(args); }; export const transcodeVideo = async (inputPath, outputPath, options = {}) => { const { videoCodec = 'libx264', videoPreset = 'fast', videoBitrate = '10M', audioCodec = 'aac', audioBitrate = '192k', } = options; const args = [ '-i', inputPath, '-c:v', videoCodec, '-preset', videoPreset, '-b:v', videoBitrate, '-c:a', audioCodec, '-b:a', audioBitrate, '-movflags', '+faststart', outputPath, ]; await runFFmpeg(args); }; export const trimSegment = async (inputPath, outputPath, inPoint, outPoint) => { const args = [ '-i', inputPath, '-ss', inPoint, '-to', outPoint, '-c', 'copy', outputPath, ]; await runFFmpeg(args); }; export const concatSegments = async (segmentListFile, outputPath) => { const args = [ '-f', 'concat', '-safe', '0', '-i', segmentListFile, '-c', 'copy', outputPath, ]; await runFFmpeg(args); };