If you want to shorten a video and keep the compressed bytes untouched, FFmpeg will do it in one command. The catch is that the command is order-sensitive in a way nothing warns you about, and three of the most commonly copy-pasted variants are subtly wrong.
This is the command, then everything you need to know about why it is written that way.
The command
ffmpeg -ss 00:01:30 -i input.mp4 -t 00:00:45 -c copy -avoid_negative_ts make_zero output.mp4
That takes 45 seconds starting from 1:30, copies both streams without decoding them, and writes a file whose timestamps begin at zero.
Piece by piece:
-ss 00:01:30placed before-iis an input seek. FFmpeg jumps into the file at that position rather than reading and discarding everything before it, so the command is near-instant regardless of how long the source is.-t 00:00:45is a duration, not an end time.-c copyis the whole point: copy every stream through without decoding. This is what makes the operation lossless and fast.-avoid_negative_ts make_zeroshifts the output timestamps so the file starts at 0. Without it some players open the result on a black frame while they wait for a presentation timestamp that is still 90 seconds in the future.
Why -t and not -to
-t takes a duration. -to takes an end position — but which timeline that position is measured against depends on whether your seek was an input option and whether -copyts is in play. That ambiguity is the single most common reason a copy-pasted trim command produces a clip of the wrong length.
Duration arithmetic is trivial to do in your head, and -t means the same thing in every FFmpeg version. Use -t.
Why the position of -ss matters
-ss before -i and -ss after -i are different operations that happen to share a name.
Before -i (input seeking). FFmpeg uses the container index to jump. With -c copy, it can only begin at a keyframe, so it lands on the nearest keyframe at or before the time you asked for. Your clip may therefore start slightly early. It will play correctly.
After -i (output seeking). FFmpeg reads the file from the beginning and discards everything before your timestamp. When you are re-encoding, this is exact. When you are using -c copy, it is a trap: it can start you on a P-frame that references data that was just discarded, and you get a few seconds of smeared, half-drawn picture at the head of the file — or, depending on the player, nothing at all until the next keyframe.
There is a second, historical wrinkle worth knowing if you are reading old Stack Overflow answers. Very old FFmpeg builds seeked inaccurately when re-encoding with -ss before -i, which is why so much advice insists on putting -ss after -i. That has not been true for many years — input seeking is accurate when re-encoding on any remotely modern build. The advice outlived the bug.
Finding the keyframes so the cut is not a surprise
Since a lossless cut can only land on a keyframe, it is worth knowing where they are before you complain about where the cut went:
ffprobe -v error -select_streams v:0 -skip_frame nokey \
-show_entries frame=pts_time -of csv=print_section=0 input.mp4
That prints one timestamp per keyframe. If they come every couple of seconds, a lossless trim will feel accurate enough. If your screen recording only has a keyframe every twelve seconds, you now know why -ss 00:01:30 gave you a clip that starts at 1:24 — and that this is the codec’s doing, not FFmpeg’s.
Cutting a section out of the middle, losslessly
There is no single-command lossless way to remove a middle section, because the two surviving pieces have to be produced separately and then joined. The three-step version:
1. Cut the two keepers.
ffmpeg -i input.mp4 -t 00:02:14 -c copy -avoid_negative_ts make_zero part1.mp4
ffmpeg -ss 00:02:19 -i input.mp4 -c copy -avoid_negative_ts make_zero part2.mp4
2. Write a list file.
file 'part1.mp4'
file 'part2.mp4'
3. Join with the concat demuxer.
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4
The concat demuxer (as opposed to the concat filter) works at the packet level, so -c copy stays lossless through the join. The requirement is that every input shares the same codecs, resolution, pixel format and timebase — which is automatic here, because all the parts came out of one source file.
Two things will bite you:
- Both cut points snap to keyframes. The join is only seamless if
part1ends on a clean boundary andpart2begins on a keyframe. In practice the resulting cut lands somewhere near, not exactly on, the times you typed. - Audio and video keyframe boundaries do not coincide. Audio frames are much shorter than a video GOP, so the two streams get trimmed at slightly different instants and the join can produce a small audio/video desync or a click. On speech it is usually inaudible; on music it usually is not.
If either of those matters — and for removing a fumbled sentence from the middle of a recording, they usually do — the honest answer is that you want a frame-accurate cut, which means re-encoding. The trade between the two is written up here, and the browser editor on this site does the frame-accurate version with a trim + concat filter graph rather than a packet copy.
A few flags that save trouble
-movflags +faststart (MP4/MOV only) rewrites the file so the index sits at the front. Do this on anything that will be played over HTTP; without it a browser has to fetch the tail of the file before it can start.
ffmpeg -ss 00:00:30 -i input.mp4 -t 60 -c copy -movflags +faststart output.mp4
-c:v copy -c:a aac copies the video losslessly while re-encoding only the audio. Useful when the source audio is in a codec the destination container will not accept — trimming an MKV with FLAC audio into an MP4, for instance. The picture is still untouched.
-map 0 copies every stream, including subtitles, extra audio tracks and attachments, which plain -c copy will silently drop for anything beyond the first stream of each type.
-fflags +genpts helps when a source has broken or missing presentation timestamps and the output ends up with a wildly wrong duration.
Choosing between the two approaches
Use -c copy when you are trimming the head or tail of a clip, a second of imprecision is acceptable, and you want the operation to be instant and mathematically lossless.
Re-encode when the cut has to land on a specific frame, when you are removing something from the middle, or when you are also changing speed or resolution — because all of those require the decoded frames anyway.
If you would rather not install anything, the same frame-accurate cut runs in a browser tab here using FFmpeg compiled to WebAssembly. It is the same library, the same filters and the same trade-offs; the file simply never leaves your machine. How that works is documented in full.