ffmpeg concat: Join Video Files in CI
ffmpeg -f concat -safe 0 -i list.txt -c copy out.mp4 joins clips that share the same codec without re-encoding.
There are two ways to concatenate in ffmpeg: the demuxer (fast, needs identical codecs) and the filter (re-encodes, handles mixed inputs). Knowing which to use avoids most failures.
What it does
The concat demuxer reads a text file listing inputs and stitches them end to end; with -c copy it does so without re-encoding, so all clips must share codec, resolution, and timebase. The concat filter (-filter_complex concat) decodes and re-encodes, so it tolerates differing inputs.
Common usage
# demuxer: same-codec clips, no re-encode
printf "file 'a.mp4'\nfile 'b.mp4'\n" > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy out.mp4
# filter: differing inputs, re-encodes
ffmpeg -i a.mp4 -i b.mp4 \
-filter_complex "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" out.mp4Options
| Flag | What it does |
|---|---|
| -f concat | Use the concat demuxer |
| -safe 0 | Allow absolute/relative paths in the list file |
| -i list.txt | The text list of file '...' lines |
| -c copy | Stream copy (demuxer only, same codecs) |
| -filter_complex concat=n=N:v=1:a=1 | Concat filter for mixed inputs |
| -map "[v]" -map "[a]" | Select the filter outputs to mux |
In CI
Generate the list file with printf, not echo, so the file 'name' quoting is exact. Use -safe 0 whenever paths are not plain relative names. If your clips were produced by separate ffmpeg steps they already share codecs, so the fast -c copy demuxer path works.
Common errors in CI
"Unsafe file name" means the list has paths the demuxer rejects by default; add -safe 0. "Impossible to open '...'" means a path in the list is wrong relative to the working directory. A concat that plays only the first clip or has A/V drift usually means the inputs differ in codec/timebase, so use the concat filter instead of -c copy. "Operation not permitted" on the demuxer can mean the list uses a protocol; keep it to plain files.