Ahosting Logo
Knowledge Base

How to Trim, Cut and Concatenate Video with FFmpeg

Why a stream-copy cut lands in the wrong placeVideo is not independent frames· most frames describe changes from an earlier keyframe· a copy can only cut at a keyframe· so the cut moves to the nearest oneTwo answers· accept the nearest keyframe, and keep the speed· re-encode, and get the exact frame you asked forFor joiningThe concat demuxer joins files without re-encoding when they share codecs and parameters; theconcat filter re-encodes and works when they do not.

Cutting a section out of a video and joining clips together are the two most common things anyone does with FFmpeg. Both have a fast method and a slow method, and choosing the wrong one is why people end up waiting an hour to trim ten seconds.

Trimming without re-encoding

When the output format matches the input, FFmpeg can copy the streams rather than decode and re-encode them:

ffmpeg -ss 00:01:30 -i input.mp4 -t 60 -c copy output.mp4

-ss is where to start, -t is how long to take. -c copy is what makes it fast; the data is moved rather than processed, so a two-hour file is trimmed in seconds and the quality is untouched.

You can also use -to for an end time instead of a duration:

ffmpeg -ss 00:01:30 -i input.mp4 -to 00:02:30 -c copy output.mp4

Why the cut is not exactly where you asked

This is the part that confuses everyone the first time.

Video is not made of independent frames. Most frames describe changes from a previous one, and only occasional keyframes stand alone. A stream copy can only cut at a keyframe, because starting anywhere else leaves the decoder without the reference it needs.

So FFmpeg moves your cut to the nearest keyframe, which can be a second or two away. Videos are commonly encoded with keyframes every two to ten seconds.

The symptom is either a cut in the wrong place, or a start that shows a frozen frame or a black flash before playback settles.

When you need the cut exactly

Re-encode. That allows a cut at any frame, because a new keyframe is created at the start:

ffmpeg -ss 00:01:30 -i input.mp4 -t 60 -c:v libx264 -crf 20 -c:a aac output.mp4

Slower, and it costs a generation of quality, since the video is decoded and compressed again.

The rule: copy when the exact frame does not matter, re-encode when it does. For removing an intro or taking a rough segment, copy is right. For matching a cut to a spoken word, re-encode.

Where -ss goes matters

Put -ss before -i and FFmpeg jumps directly to that point, which is nearly instant on a long file.

Put it after -i and FFmpeg decodes from the beginning and discards everything up to that point, accurate, and very slow on a two-hour file.

Before the input is right in almost every case. Modern FFmpeg is accurate in both positions, so the old advice to put it after for precision no longer applies.

Joining files that match

When the clips share the same codec, resolution and frame rate, typically because they came from the same camera or the same encode. The fast path works.

Create a text file listing them:

file 'clip1.mp4'
file 'clip2.mp4'
file 'clip3.mp4'

Then:

ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4

No re-encoding, no quality loss, and it completes about as fast as the disk can read.

Quote the filenames in the list. A path containing a space breaks it otherwise, and the error is not obviously about quoting.

Joining files that do not match

Different resolutions or codecs cannot be copied together. The result is a file that plays the first clip and then fails, or one with no audio after the join.

Two options. Convert each clip to matching settings first, then use the fast concat above. This is better when the clips are long, because the conversion happens once per clip.

Or use the concat filter, which handles the mismatch in one pass:

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]" output.mp4

That re-encodes everything, so it is slower, but it is one command and it works when the inputs are inconsistent.

Removing a section from the middle

There is no single command for this. Cut the part before, cut the part after, then join the two.

Use matching settings for both cuts so the join can be a copy. If you re-encoded one and copied the other, they will not concatenate cleanly.

Extracting a clip for a preview

A common job: a short sample from the middle of a file.

ffmpeg -ss 00:05:00 -i input.mp4 -t 30 -c copy preview.mp4

For a web preview, re-encoding at a lower resolution usually makes more sense than copying. The point is a small file, and a copied clip keeps the original's bitrate.

Check the result, not the exit code

FFmpeg reports success on files that do not play correctly, particularly after a concat of mismatched inputs.

Check the duration is what you expected, and play the join points rather than only the start. Audio drift after a concatenation appears gradually and is invisible in the first few seconds. Using ffprobe explains reading duration and stream properties without opening a player.

If something failed, the error is usually higher up the output than the last line. Troubleshooting common FFmpeg errors deals with finding it.

Cutting without re-encoding is one case of a broader technique that also changes containers and fixes web playback in seconds. How to Remux and Stream Copy Without Re-encoding has the detail.

Cut on a keyframe rather than fighting it

Rather than accepting where a stream copy lands, you can find the nearest keyframe and cut there deliberately.

ffprobe -v error -select_streams v -skip_frame nokey -show_entries frame=pkt_pts_time \
  -of csv=p=0 input.mp4 | head -40

That lists the timestamps where a clean cut is possible. Choosing the nearest one to your intended point gives an exact, fast, lossless cut instead of an approximate one.

For material you control, the better answer is upstream: encoding with a fixed keyframe interval means every cut point is predictable. A file with keyframes every two seconds can be cut anywhere within two seconds of the intended point, which is close enough for almost every purpose. Remuxing and stream copy goes into why the copy behaves this way.

Batch trimming from a list

Cutting many clips from one source by hand is where mistakes enter, and a list makes the whole operation reviewable.

# cuts.txt:  baslangic  sure  cikti
00:01:30  00:00:45  clip1.mp4
00:12:05  00:01:20  clip2.mp4

while read start dur out; do
  ffmpeg -nostdin -ss "$start" -i input.mp4 -t "$dur" -c copy "$out"
done < cuts.txt

The list can be checked before anything runs, kept afterwards as a record of what was produced, and rerun if the source changes.

The option preventing standard input from being consumed matters inside a loop. Without it the first job reads the rest of the list and the remaining clips are never produced, which is a confusing failure with an obvious cause.

Concatenating without a generation loss

Joining files that came from different sources tempts a re-encode, and there is usually a cheaper route.

ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,pix_fmt,r_frame_rate \
  -of csv=p=0 part1.mp4 part2.mp4

Compare the lines. If codec, dimensions, pixel format and frame rate all match, the files can be joined by copying and the result is bit for bit identical to the inputs.

Where only one property differs, changing that one property on the odd file out is far cheaper than re-encoding everything. A single clip at a different resolution costs one short encode; re-encoding the whole set to be safe costs the entire runtime and degrades material that was already correct.