Trim a video, and see where the keyframes actually cut

Set the cut, see where it really lands

Drop a video anywhere on this page. Set the in and out points, then choose a lossless stream copy or a frame-exact re-encode — with the keyframe drift measured in your file before you commit.

  • MP4
  • MOV
  • WebM
  • MKV
Or drop a file anywhere on this page
01Measured drift
Where a stream copy will really start, read from this file’s key packets, not estimated.
02Your choice of cut
Instant and bit-identical, or exact to the frame. Both trade-offs stated up front.
03No upload
The file is read and written in this tab. Nothing leaves the device.
Trimming, in depth

Keyframes, stream copies and the cut you actually get

Why -c copy gives you the wrong start time

You run the command everyone recommends, the one that trims without touching quality:

ffmpeg -ss 00:00:12.400 -i input.mp4 -to 00:00:28.900 -c copy output.mp4

It finishes instantly, exits zero, prints no warning. Then you open the result and there is footage at the front you never asked for — sometimes a fraction of a second of it, sometimes eight seconds of it. Nothing went wrong. This is the documented behaviour of a stream copy, and the reason is in how the video was compressed in the first place.

Keyframes, and why a copy cannot start between them

H.264, HEVC, VP9 and AV1 all store video as a repeating group of pictures, a GOP. Each group starts with an I-frame — a keyframe — which is a complete picture that can be decoded on its own, like a JPEG. Everything after it in the group is stored as a difference:

  • P-frames describe how the picture changed from an earlier frame.
  • B-frames describe how it differs from frames both before and after, which is why they compress so well and why they cannot be decoded in the order they are stored.

A P-frame or B-frame in isolation is not a picture. It is a set of motion vectors and residuals that only mean something once the frames they reference have been decoded. On typical footage a keyframe costs ten to twenty times what a P-frame costs, which is the entire reason video files are not a hundred times larger than they are.

Now consider what -c copy asks for: hand the compressed packets straight to a new container, do not decode, do not re-encode. That is what makes it instant and perfectly lossless. It also means the first packet written must be one that a decoder can start on, and the only packets that qualify are keyframes. So when the keyframe grid has nothing at 12.400, ffmpeg does the only thing it can and starts at the previous keyframe instead. Your cut moves backwards.

How far backwards: the arithmetic

Worst case, the error is one full keyframe interval. Two common intervals bracket what you will meet in the wild:

  • A two-second interval, which is what streaming presets and most cameras use, because HLS and DASH need segment boundaries on keyframes. At 29.97 fps that is a keyframe every 60 frames, or 2.002 s. Asking for 12.400 gets you the keyframe at 12.012 — 0.388 s early. Ask for 14.010 and you get 12.012 again: 1.998 s early.
  • x264's default -g 250, which is what you get from a plain ffmpeg -i in.mov out.mp4 with no GOP setting. 250 frames at 29.97 fps is 8.342 s. Keyframes at 0.000, 8.342, 16.683. Asking for 12.400 gets you 8.342 — over four seconds of footage you did not want.

Real files are less tidy than either, because encoders also insert keyframes on scene changes. That cuts both ways: a busy edit may have keyframes close enough that you never notice the problem, and a long static shot may have none for the whole duration of -g.

Check your own file instead of guessing

No need to guess — the positions are readable:

ffprobe -select_streams v -skip_frame nokey -show_entries frame=pts_time -of csv=p=0 input.mp4

Every line is a timestamp a lossless cut is permitted to begin on, and the largest gap between consecutive lines is your worst-case error on that file. Older guides spell the field pkt_pts_time; that name was removed, and on FFmpeg 7 and later it prints a blank line per frame rather than erroring — which looks exactly like a file with no keyframes.

-ss before -i versus after -i

This is the detail that turns a five-minute job into an afternoon: the two commands look almost identical and behave nothing alike.

# Input seeking: fast, snaps to a keyframe
ffmpeg -ss 12.400 -i input.mp4 -to 28.900 -c copy output.mp4

# Output seeking: frame-accurate, decodes and discards the head of the file
ffmpeg -i input.mp4 -ss 12.400 -to 28.900 -c:v libx264 -crf 20 -c:a aac output.mp4

Before -i, the seek is applied to the input: ffmpeg jumps to the nearest keyframe at or before your timestamp and starts reading from there. It is fast because it never touches the earlier part of the file, and it is keyframe-snapped for the reason above.

After -i, the seek applies to the output: ffmpeg decodes from the beginning and throws away every decoded frame before your timestamp. That is accurate to the frame, but it has to walk the head of the file, so trimming at 55 minutes into an hour-long recording means decoding 55 minutes of video to produce nothing. Modern ffmpeg mitigates this by seeking to a keyframe first and decoding forward from there, so the penalty is usually one GOP rather than the whole file — but accurate seeking still requires decoding, which is why it cannot be combined with -c copy.

Stream copy versus re-encode, honestly

 Stream copy (-c copy)Re-encode
SpeedLimited by disk, not by the codec. Effectively instant.Proportional to the clip length and resolution.
Start accuracyNearest keyframe at or before your in point.Exactly your in point.
QualityBit-identical to the source. No loss at all.One generation of loss, set by the bitrate or CRF you choose.
File sizeThe same bitrate as the source, so size scales with the kept duration.Whatever your settings produce — can be smaller or larger.
Audio syncAudio is copied too; its frames sit on their own grid, so the start can drift by a few tens of milliseconds.Audio is re-encoded onto the new timeline and stays locked.

The gotcha that looks like a frozen first frame

Occasionally a copied clip plays as though it starts with a still: the first image sits there for a second, then motion begins. The cause is timestamps, not video. A copy keeps the original presentation timestamps, so the first packet may carry a PTS of 12.012 rather than 0, and the container then describes a stream that starts partway through a timeline nothing else occupies. Some players render that as an initial freeze, some drop the leading frames, some ignore it entirely.

ffmpeg -ss 12.400 -i input.mp4 -to 28.900 -c copy -avoid_negative_ts make_zero output.mp4

-avoid_negative_ts make_zero shifts the timestamps so the clip starts at zero. It is close to free and belongs in your muscle memory for every copy-mode trim. Add -fflags +genpts if the source has no usable timestamps at all, which happens with some screen recorders and most raw streams.

Generation loss: once is fine, repeatedly is not

The fear of re-encoding is largely inherited from the DV and DVD era. One H.264 pass at CRF 18-20, or an equivalent bitrate, is genuinely hard to distinguish from the source on normal footage. The problem is compounding: each pass quantises what the previous pass already quantised, so its own artefacts become the detail the next encoder tries faithfully to preserve. Trim, then compress, then trim again, and by the fourth generation the banding and mosquito noise are obvious to anyone.

The practical rule: decide the whole edit first, then encode once. If you need both a trim and a size reduction, do them in a single operation rather than chaining tools. If a clip only needs to lose a top and tail and the exact frame does not matter, copy the stream and accept the keyframe grid — that is the one path with no loss at all.

Limits of doing this in a browser tab

The tool above decodes and encodes through the WebCodecs API, on the same hardware media engine a native editor uses, and no byte of your file is uploaded. The cost of that is memory: the output is assembled in the tab before it is handed to you as a download, so a very long or very large source can exhaust the page where a command-line tool streaming to disk would not. Clips of a few minutes at 1080p are comfortable on an ordinary laptop; an hour of 4K is not. Codec support also follows the browser rather than ffmpeg's list, so an exotic source may decode on one machine and not another. When a file is too big for the tab, the commands above do the same job locally.

Questions

Why does ffmpeg -c copy start earlier than the time I asked for?
Because a stream copy cannot decode, and an H.264 frame in the middle of a group of pictures is meaningless without the keyframe it references. So the cut moves back to the previous keyframe. With a two-second keyframe interval your cut can land up to two seconds early, silently — the command succeeds and nothing warns you.
So how do I get a frame-exact cut?
Re-encode. There is no third option: either you accept the keyframe grid, or you decode and re-encode so a new keyframe can be placed exactly where you asked. The honest trade is speed and generation loss against precision.
Does re-encoding visibly damage the video?
One pass at a sensible bitrate is usually imperceptible. Repeated re-encoding of the same file is what accumulates visible generation loss, so do your cuts in one operation rather than trimming a trim.
Does trimming re-encode the audio too?
Only when it has to. The exact mode forces a re-encode of the picture, not the sound: in either mode an audio track the output container can hold as-is is carried across, and one it cannot is re-encoded at 96 kbps. Untick Keep audio to leave the soundtrack out altogether.

Other local video tools

All tools

Start processing

Drop a clip in and set the cut. The keyframe drift is measured before anything is written.