Skip to content

Instantly share code, notes, and snippets.

@nihonjinrxs
Last active April 19, 2021 05:22
Show Gist options
  • Save nihonjinrxs/5897141d69938bbc247f3ee921754645 to your computer and use it in GitHub Desktop.
Save nihonjinrxs/5897141d69938bbc247f3ee921754645 to your computer and use it in GitHub Desktop.
segment & concat with ffmpeg via node + fessonia
/* original command:
ffmpeg -i video.mp4 -filter_complex "[0:v]trim=1:2,setpts=PTS-STARTPTS[v0]; [0:a]atrim=1:2,asetpts=PTS-STARTPTS[a0]; [0:v]trim=120:125,setpts=PTS-STARTPTS[v1]; [0:a]atrim=120:125,asetpts=PTS-STARTPTS[a1]; [v0][a0][v1][a1]concat=n=2:v=1:a=1[out]" -map "[out]" output.mp4
*/
/*
Rewrite using Fessonia library in node.js
Get Fessonia: https://www.npmjs.com/package/@tedconf/fessonia
Fessonia Docs: https://tedconf.github.io/fessonia/
*/
const {
FFmpegCommand,
FFmpegInput,
FFmpegOutput,
FilterNode,
FilterChain
} = require('@tedconf/fessonia')({
// options here if you have them... location of ffmpeg binary, etc.
});
// trim stream returning stream segment from start to end
// default is video stream, unless audio = true
function trimStream (start, end, audio = false) {
return new FilterNode(`${audio ? 'a' : ''}trim`, {
start: start,
end: end
});
}
// Adjust frame presentation timestamps (PTS) by subtracting
// the PTS of the first frame in the segment
function adjustTimestamps (audio = false) {
return new FilterNode(`${audio ? 'a' : ''}setpts`, {
expr: 'PTS-STARTPTS'
});
}
// Concatenate the video (and audio) streams together
function concatenateStreams ({ video, audio }) {
hasAudio = typeof audio !== 'undefined' && audio.length > 0
if (hasAudio && video.length !== audio.length) {
throw new Error('Must have same number of streams of each type.')
}
const concatNode = new FilterNode('concat', {
n: video.length,
v: 1,
a: hasAudio ? 1 : 0
});
const concatenator = new FilterChain([concatNode]);
for (let n = 0; n < video.length; n++) {
concatenator.addInput(video[n].streamSpecifier());
if (hasAudio) concatenator.addInput(audio[n].streamSpecifier());
}
return concatenator;
}
const input = new FFmpegInput('video.mp4');
const segment1Video = new FilterChain([trimStream(1, 2), adjustTimestamps()]);
segment1Video.addInput(input.streamSpecifier('v'));
const segment1Audio = new FilterChain([trimStream(1, 2, true), adjustTimestamps(true)]);
segment1Audio.addInput(input.streamSpecifier('a'));
const segment2Video = new FilterChain([trimStream(120, 125), adjustTimestamps()]);
segment2Video.addInput(input.streamSpecifier('v'));
const segment2Audio = new FilterChain([trimStream(120, 125, true), adjustTimestamps(true)]);
segment2Audio.addInput(input.streamSpecifier('a'));
const concatenator = concatenateStreams({
video: [segment1Video, segment2Video],
audio: [segment1Audio, segment2Audio]
});
const cmd = new FFmpegCommand();
cmd.addInput(input);
cmd.addFilterChain(segment1Video);
cmd.addFilterChain(segment1Audio);
cmd.addFilterChain(segment2Video);
cmd.addFilterChain(segment2Audio);
cmd.addFilterChain(concatenator);
const output = new FFmpegOutput('output.mp4');
output.addStreams([
concatenator.streamSpecifier('v'),
concatenator.streamSpecifier('a')
]);
cmd.addOutput(output);
console.log(cmd.toString());
/* output:
ffmpeg -i "video.mp4" -filter_complex "[0:v]trim=start=1:end=2,setpts=expr=PTS-STARTPTS[chain0_setpts_0];[0:a]atrim=start=1:end=2,asetpts=expr=PTS-STARTPTS[chain1_asetpts_0];[0:v]trim=start=120:end=125,setpts=expr=PTS-STARTPTS[chain2_setpts_0];[0:a]atrim=start=120:end=125,asetpts=expr=PTS-STARTPTS[chain3_asetpts_0];[chain0_setpts_0][chain1_asetpts_0][chain2_setpts_0][chain3_asetpts_0]concat=n=2:v=1:a=1[chain4_concat_0][chain4_concat_1]" -map "[chain4_concat_0]" -map "[chain4_concat_1]" "output.mp4"
*/
/*
// To execute:
cmd
.execute()
.then(({ stderr, stdout }) => {
// do something with logs from the run
});
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment