fluent-ffmpeg / node-fluent-ffmpeg

A fluent API to FFMPEG (http://www.ffmpeg.org)
MIT License
7.85k stars 874 forks source link

Is it possible to write multiple output files from a complex filter? #1269

Open ChrisMICDUP opened 5 months ago

ChrisMICDUP commented 5 months ago

Version information

Code to reproduce

async function extractClipsWithComplexFilter(id, jsonClips, localMP4File) {
    const startTime = Date.now();

    let i = 0;
    let complexFilterArray = [];
    for (const clip of jsonClips.clips) {
        console.log(`Extracting clip_${i}`);
        complexFilterArray.push(`[0:v]trim=start=${utils.parseTime(clip.startTimestamp)/1000}:end=${utils.parseTime(clip.endTimestamp)/1000},setpts=PTS-STARTPTS[v_clip_${i}]`);
        complexFilterArray.push(`[0:a]atrim=start=${utils.parseTime(clip.startTimestamp) / 1000}:end=${utils.parseTime(clip.endTimestamp) / 1000},asetpts=PTS-STARTPTS[a_clip_${i++}]`);
        break; // only do one clip for now
    }
    let mapArray = [];
    let outputArray = [];
    for (let j = 0; j < i; j++) {
        mapArray.push(`-map '[v_clip_${j}]' -map '[a_clip_${j}]'`);
        outputArray.push(`clip_${j}.mp4`);
    }

    /* This CLI works
     ffmpeg -i test.mp4 -filter_complex
        [0:v]trim=start=1948.605:end=2038.755,setpts=PTS-STARTPTS[v_clip_0];
        [0:a]atrim=start=1948.605:end=2038.755,asetpts=PTS-STARTPTS[a_clip_0];
        ...
        [0:v]trim=start=5041.255:end=5131.045,setpts=PTS-STARTPTS[v_clip_10];
        [0:a]atrim=start=5041.255:end=5131.045,asetpts=PTS-STARTPTS[a_clip_10];
        -map '[v_clip_0]' -map '[a_clip_0]' clip_0.mp4
        ...
        -map '[v_clip_10]' -map '[a_clip_10]' clip_10.mp4
     */
    ffmpeg(localMP4File)
        .complexFilter(complexFilterArray)
        .output('clip_0.mp4')
        .map('[v_clip_0]')
        .map('[a_clip_0]')
        .on('error', error => console.log('Error: ' + error.message))
        .on('end', () => console.log('Success!'))
        .run();
    console.log(`Extracted ${complexFilterArray.length} clips in ${Date.now()-startTime}`);

    return {status: 200} // TODO: failure
}

(note: if the problem only happens with some inputs, include a link to such an input file)

Expected results

I pass a json array of clip information with start and end timestamps. The number of clips could vary from 1-50.

What I want is a way to define an array of outputs and maps at runtime and create a number of clips.

Observed results

A single clip works as above. The ffmpeg command works for multiple clips as above. I've had various attempts to pass arrays to output and map without success.

Checklist

ChrisMICDUP commented 5 months ago

Forgot about ChatGPT... In the spirit of OpenAI ripping off github, I present to you a working example (note the loop on .input and .output):

const extractClipCommand = ffmpeg(); jsonClips.clips.forEach((clip, index) => { extractClipCommand.input(localMP4File); }); let complexFilterArray = []; console.log("create complexFilterArray"); jsonClips.clips.forEach((clip, index) => { complexFilterArray.push([0:v]trim=start=${utils.parseTime(clip.startTimestamp)/1000}:end=${utils.parseTime(clip.endTimestamp)/1000},setpts=PTS-STARTPTS[v_clip_${index}]); complexFilterArray.push([0:a]atrim=start=${utils.parseTime(clip.startTimestamp) / 1000}:end=${utils.parseTime(clip.endTimestamp) / 1000},asetpts=PTS-STARTPTS[a_clip_${index}]); }); extractClipCommand.complexFilter(complexFilterArray); console.log("create output and maps"); // Map the outputs jsonClips.clips.forEach((clip, index) => { extractClipCommand.output(clip_${index}.mp4) .map([v_clip_${index}]) .map([a_clip_${index}]); }); // Run the command console.log("Run the command"); extractClipCommand .on('error', function(err) { console.log('An error occurred: ' + err.message); }) .on('end', function() { console.log(Extracted ${complexFilterArray.length/2} clips in ${Date.now()-startTime}); }) .run();

I should point out however that with a 1GB mp4 and more than 3 clips, ffmpeg runs out of memory and is killed...