Programmatically combine videos into a merged output with a dynamic layout.
- FFmpeg
- Node 20
npm install yaffu@latestRun the example/grid.js script which demos genericCombine (mixes all audio and stacks all video on a centered grid):
git clone https://github.com/erwinv/yaffu
cd yaffu
corepack enable
corepack install
# build modules
pnpm install
pnpm build
# run examples
cd example
pnpm install
node grid.js
Output (1080p)
grid_layout.mp4
Using genericCombine is as simple as:
import { ffmux, genericCombine } from 'yaffu'
const inputPaths = [
//...
]
await ffmux(genericCombine(inputPaths, 'combined.mp4'))Suppose audio/video inputs are given with the following timeline:
0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80
--------|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|--
alice [===========] [=======================================]
bob [===========] [========================]
charlie [===========] [==============]
david [===========] [====]
screen [=======================================]
See example/timeline.js to see how to encode the above timeline using the API.
Output (720p)
timeline.mp4
mixAudiodownmixes and mixes all input audio to stereocompositeGridstacks all video input on a grid layout (max 16 or 4x4 grid)compositePresentationmain tile (presentation) on the left, vertically stacked tiles on the right (max 4)genericCombineis a convenient wrapper that does bothmixAudioandcompositeGrid
FilterGraphfilter graph builder classffmuxexecutes the filter graph and muxes to the defined outputffconcatDemuxconcatenates all input files using FFmpeg's concat demuxerffprobeprobe media file's metadata
import { FilterGraph, ffmux } from 'yaffu'
const inputPaths = [
//...
]
const graph = new FilterGraph(inputPaths)
// build filter graph by piping streams through filters to output streams
graph
.pipe(['0:a', '1:a'], ['aout']) // input stream ids, output stream ids
.filter('amix', [], { normalize: 0 }) // filter name, direct options, key-value options
.filter('dynaudnorm') // filter with no opts (use default opts)
// map stream/s to output file
graph.map(['aout'], 'output.aac')
// run the muxer
await ffmux(graph)