Stitching multiple clips into one file is a common editing task for demos, tutorials, and social reels. If you have recordings from different devices, or you want to join phone segments without re-encoding, FFmpeg can handle it quickly once you pick the right method.
Hi all,
I have a handful of MP4 clips and I want to join them into one video using FFmpeg. Some clips come from the same camera, others are from different sources, so codecs and frame rates might not match. I want to keep quality high, avoid re-encoding if possible, and keep audio in sync.
How can I concatenate videos with FFmpeg? What is the difference between ffmpeg concat demuxer and the concat filter, and when should I use each? Also, any tips for dealing with mismatched parameters or VFR footage?
FFmpeg offers two primary ways to concatenate clips. Pick based on whether your inputs are already identical at the stream level or if they need re-encoding. Let’s dig a little deeper:
Use this when all inputs share the same codec, resolution, pixel format, frame rate, and audio parameters. It does a stream copy, so there is no re-encode.
# list.txt
file 'a.mp4'
file 'b.mp4'
file 'c.mp4'
# Concat without re-encoding
ffmpeg -f concat -safe 0 -i list.txt -c copy -fflags +genpts output.mp4Code language: PHP (php)
Here’s what this command means:
-safe 0allows absolute paths in list.txt.-fflags +genptscan regenerate timestamps for smoother playback on some players.
If any stream parameter differs, this will fail or produce glitchy output. In that case, use the filter method.
Want a deeper primer on FFmpeg capabilities and tradeoffs? See FFmpeg features and use cases.
Use this when inputs differ. It re-encodes to a single, unified format. You can also insert scales, format fixes, and audio resampling to prevent drift.
# Three inputs, normalize video and audio, then concatenate
ffmpeg -i a.mp4 -i b.mp4 -i c.mp4 \
-filter_complex "\
[0:v]scale=1920:-2,format=yuv420p[v0];[0:a]aresample=async=1[a0];\
[1:v]scale=1920:-2,format=yuv420p[v1];[1:a]aresample=async=1[a1];\
[2:v]scale=1920:-2,format=yuv420p[v2];[2:a]aresample=async=1[a2];\
[v0][a0][v1][a1][v2][a2]concat=n=3:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" \
-r 30 -c:v libx264 -crf 20 -preset medium \
-c:a aac -b:a 160k -movflags +faststart output.mp4Code language: PHP (php)
Why these options?:
scale=1920:-2andformat=yuv420palign resolution and pixel format for broad compatibility.aresample=async=1helps keep audio in sync when source time bases differ.-r 30sets a consistent output frame rate.-movflags +faststartoptimizes MP4 for web playback by moving the moov atom to the front.
For encoder choices and settings, check these H.264 best practices and MP4 container features in our MP4 format guide.
If you prefer stream copy at the end, pre-normalize each clip once, then concat with -c copy.
# Normalize each input to a common profile
ffmpeg -i in1.mp4 -c:v libx264 -preset veryfast -crf 20 -r 30 \
-vf "scale=1920:-2,format=yuv420p" -c:a aac -ar 48000 -ac 2 -b:a 160k norm1.mp4
ffmpeg -i in2.mp4 -c:v libx264 -preset veryfast -crf 20 -r 30 \
-vf "scale=1920:-2,format=yuv420p" -c:a aac -ar 48000 -ac 2 -b:a 160k norm2.mp4
# Then concatenate losslessly
printf "file 'norm1.mp4'\nfile 'norm2.mp4'\n" > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy -fflags +genpts output.mp4Code language: PHP (php)
- One file lacks audio: Use concat with
a=0or add a dummy audio track for that input before concatenation. - Variable frame rate inputs: Explicitly set a target frame rate on output (
-r 30) or per input using-vsyncandfpsfilters. - Timebase or timestamp gaps: Add
-fflags +genptson concat demuxer outputs. - Absolute paths in list.txt: Include
-safe 0. Use single quotes around paths with spaces.
FFmpeg on its own is a fantastic tool, but if you need to concat videos at scale, you might want a service that stores clips, stitches them on demand, and delivers globally optimized streams. Cloudinary can do this with a simple transformation that appends videos using a splice step, then serves the result via a CDN.
# Example: append clip2 after clip1 on the fly
https://res.cloudinary.com/<cloud_name>/video/upload/l_video:clip2/fl_splice/clip1.mp4
# Chain more clips
https://res.cloudinary.com/<cloud_name>/video/upload/l_video:clip2/fl_splice/l_video:clip3/fl_splice/clip1.mp4Code language: PHP (php)
You can upload your sources once, generate concatenations dynamically, and inherit sensible encoding defaults for the web.
- Use the concat demuxer for identical streams and get a fast, lossless join.
- Use the concat filter when inputs differ, and re-encode to a unified format.
- Normalize resolution, pixel format, frame rate, and audio to avoid sync issues.
- Cloud workflows like Cloudinary can splice videos on demand and deliver via CDN.
Ready to streamline video concatenation, optimization, and delivery at scale? Create a free Cloudinary account and start building a faster, more reliable media pipeline today.