TechEarl

How to Trim or Cut a Video with ffmpeg

Trim or cut a video from the command line with ffmpeg: the fast lossless -c copy way, the keyframe gotcha that makes your cut land early, and the frame-accurate re-encode. With -ss and -to/-t explained.

Ishan Karunaratne⏱️ 9 min readUpdated
Share thisCopied
Trim or cut a video from the command line with ffmpeg using -ss and -to or -t, the fast lossless stream-copy way and the frame-accurate re-encode.

The fast way to cut a clip out of a video with ffmpeg is to seek to the start, set an end point, and copy the streams without re-encoding:

bash
ffmpeg -ss 00:00:10 -to 00:00:20 -i input.mp4 -c copy out.mp4

That pulls seconds 10 to 20 out of input.mp4 into out.mp4 in a fraction of a second, with zero quality loss, because -c copy (stream copy) muxes the existing video and audio packets straight through instead of decoding and re-encoding them. For most "grab this segment" jobs it is the whole answer.

There is one catch worth understanding before you trust the output, and it is the source of nearly every "my cut started in the wrong place" complaint: stream copy can only cut on keyframes. The rest of this page covers that gotcha, the frame-accurate alternative, and what -ss, -to, and -t actually mean.

The fast, lossless cut (-c copy)

Two forms, depending on whether you think in end-time or duration. Use -to for an absolute end timestamp:

bash
# Keep everything from 00:00:10 to 00:00:20 (10 seconds of output)
ffmpeg -ss 00:00:10 -to 00:00:20 -i input.mp4 -c copy out.mp4

Or use -t for a duration measured from the start point:

bash
# Start at 00:00:10 and keep 10 seconds
ffmpeg -ss 00:00:10 -t 10 -i input.mp4 -c copy out.mp4

-to and -t are not the same flag. -to is an end position (stop at this point); -t is a duration (run for this long). The wrinkle is the seek point. When -ss comes before -i (input seeking, as in every command on this page), ffmpeg resets the timeline to zero at the seek point, so -to is measured from there, not from the start of the original file. That is why -ss 00:00:10 -to 00:00:20 gives you ten seconds, the same clip as -ss 00:00:10 -t 10: with input seeking, -to 20 and -t 10 describe the identical out point. (Put -ss after -i and -to is measured against the original timeline instead, so there -ss 10 -to 20 is also a ten-second clip but for a different reason.) -to and -t are mutually exclusive; if you pass both, -t wins. If a clip ever comes out longer than expected, you have probably reached for -to thinking it meant duration.

This stream-copy command is instant and lossless: nothing is re-compressed, so there is no generation loss and no codec choice to make. It is the right tool for splitting a long screen recording, lopping dead air off the front, or carving a rough segment out of a large file.

The keyframe gotcha (why your cut lands early)

Video is not stored as independent frames. It is stored in groups of pictures (GOPs), each beginning with a keyframe (an I-frame, a complete standalone image), followed by predicted frames that only describe what changed. You cannot start decoding mid-GOP, so a stream copy cannot begin output anywhere except on a keyframe.

That means when you ask for -ss 00:00:10 with -c copy, ffmpeg snaps the start back to the nearest keyframe at or before 10 seconds. If the closest keyframe sits at 00:00:08, your clip begins two seconds early. Nothing is broken and no quality is lost, the cut point is just constrained to where the keyframes are. Keyframe spacing depends on how the source was encoded (often one every 2 to 10 seconds), so the error varies by file.

This is exactly the "result quality varies with -c copy, sometimes it is better without it" confusion you see in old forum threads. It was never random. It is the keyframe constraint. When the start needs to be exact, you re-encode.

The frame-accurate cut (re-encode)

Drop -c copy and ffmpeg decodes the source and re-encodes the output, so it can start on any frame you ask for, not just a keyframe:

bash
# Frame-accurate: exact start, exact length, re-encoded
ffmpeg -ss 00:00:10 -to 00:00:20 -i input.mp4 -c:v libx264 -crf 18 -c:a aac out.mp4

This is slower (it does real encoding work) and technically lossy (you are re-compressing), but -crf 18 for libx264 is visually near-transparent, so the quality cost is small. Use it when a couple of stray frames at the head of the clip would be obvious: cutting on a line of dialogue, trimming for social media, or stitching segments that have to line up.

Rule of thumb: reach for -c copy first for speed and zero loss, and only re-encode when the start point has to be precise.

Where -ss goes: before -i vs after -i

You will see -ss written both before and after -i, and the placement used to matter a lot more than it does now.

bash
# -ss before -i: fast input seek (jumps to a keyframe near the time, then reads)
ffmpeg -ss 00:00:10 -i input.mp4 -t 10 -c copy out.mp4

# -ss after -i: ffmpeg reads from the start and discards frames up to -ss
ffmpeg -i input.mp4 -ss 00:00:10 -t 10 -c:v libx264 -crf 18 out.mp4

The old story was: -ss before -i is fast but inaccurate, -ss after -i is slow but accurate. That changed with ffmpeg 2.1, which made input seeking frame-accurate when you are re-encoding. So in any modern ffmpeg (you should be on 6.x or 7.x today), -ss before -i is both fast and frame-accurate for a re-encode, and it is the better default: it seeks straight to a keyframe near the target instead of decoding the whole file up to that point.

The placement only really matters in two cases now. With -c copy, accuracy is bounded by keyframes no matter where -ss sits, so put it before -i for the speed. With a re-encode, before--i is faster and just as accurate; the after--i form (decode-and-discard from frame zero) is only worth reaching for if you hit a pathological file where input seeking misbehaves. If you want the legacy keyframe-only input-seek behavior back, -noaccurate_seek restores it.

Timestamp formats

ffmpeg accepts time values in two forms anywhere it wants a time (-ss, -to, -t):

  • HH:MM:SS.ms, for example 00:01:30.500 for one minute thirty and a half seconds. Hours and the fractional part are optional, so 01:30 and 90 and 00:01:30 all mean the same point.
  • Plain seconds, for example 90 or 90.5. Handy for short clips: -ss 5 -t 3 is "start at 5 seconds, keep 3."

Both work in either flag, so mix whichever reads more clearly. Sub-second precision (.ms) matters most on the re-encode path, where the cut can actually land on that exact frame.

Splitting one file into several segments

If the goal is to chop a long file into equal pieces rather than extract one clip, the segment muxer does it in a single pass without re-encoding:

bash
# Split into ~60-second chunks, stream-copied, cut at keyframes
ffmpeg -i input.mp4 -c copy -f segment -segment_time 60 -reset_timestamps 1 part%03d.mp4

The same keyframe rule applies: each split lands on the nearest keyframe, so the chunks are approximately 60 seconds, not exactly. For exact-length segments you would re-encode with a forced keyframe interval, but for most "break this up so it uploads" tasks the copy version is what you want.

FAQ

See also

Sources

Authoritative references this article was fact-checked against.

Tagsffmpegtrim videocut videoCLIvideo editingstream copykeyframe

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

How to Crop a Video with ffmpeg

Crop a video with ffmpeg's crop filter: crop=w:h:x:y from the top-left origin, centered crops with in_w/in_h expressions, square crops for social, and cropdetect to strip black bars automatically.

How to Reverse a Video with ffmpeg

Reverse a video with ffmpeg using the reverse filter for picture and areverse for sound. Why you must write to a new output file, and why you trim before you reverse.

How to Extract Audio From a Video with ffmpeg

Pull the audio out of a video with ffmpeg: copy the stream untouched when you just want to demux (fast, lossless), or re-encode to MP3, AAC, or WAV when you need a different format. Plus how to check the source codec first and grab a single segment.