Programmatically combine videos into a merged output with a dynamic layout.
- FFmpeg
- Node 20
npm install yaffu@latest
Run 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
mixAudio
downmixes and mixes all input audio to stereocompositeGrid
stacks all video input on a grid layout (max 16 or 4x4 grid)compositePresentation
main tile (presentation) on the left, vertically stacked tiles on the right (max 4)genericCombine
is a convenient wrapper that does bothmixAudio
andcompositeGrid
FilterGraph
filter graph builder classffmux
executes the filter graph and muxes to the defined outputffconcatDemux
concatenates all input files using FFmpeg's concat demuxerffprobe
probe 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)