"I want to start the clip at 00:38, but the export starts at 00:35." Anyone who has trimmed with FFmpeg has hit this. Usually the command isn't wrong — you just didn't realize that where -ss goes, and whether -c copy is involved, change what "accurate" means. This post maps the boundary between seeking and genuinely lossless cutting.
-ss before or after: two seek semantics
-ss (seek) can go before the input or before the output, and they don't behave the same:
Input seek (-ss 00:00:38 -i input.mp4): jumps fast to roughly the target — it only honors keyframes, no per-frame work. Since FFmpeg 2.1, when followed by a re-encode it defaults to accurate_seek: after landing on the keyframe it decodes and discards the content before the target, so it's frame-accurate — fast and precise. The recommended form.
Output seek (-i input.mp4 -ss 00:00:38): decodes the whole input and discards everything before the target timestamp. Frame-accurate, but every earlier frame gets decoded, which is very slow on big files.
| Form | Speed | Accuracy |
|---|---|---|
-ss before input (re-encode) | Fast | Frame-accurate |
-ss after input | Slow | Frame-accurate |
-ss before input + -c copy | Fast | Keyframe-only |
The lossless ceiling: -c copy can't be frame-accurate
-c copy means "don't re-encode, just copy the compressed bytes" — fast, and it avoids the quality hit of a second quantization. But it has a hard constraint: it can only start a clip at a keyframe (I-frame).
In long-GOP codecs like H.264, only a few frames can be independently decoded; everything else depends on an earlier reference frame. Suppose you want to start at 10.000s and the previous keyframe is at 9.600s — -c copy can only cut from 9.600s, so the first frame is not the one you picked; the start silently drifts earlier. The error is set by the keyframe spacing, not by anything you can infer from the video length.
So "lossless" and "accurate" are two different promises that -c copy can't keep at once:
- You don't care about a few keyframe-slop and just want it fast →
-ssbefore input +-c copy - You need frame accuracy → re-encode
Working commands:
# Fast rough cut, head/tail only, no re-encode (start may shift back to a keyframe)
ffmpeg -ss 00:00:38 -i input.mp4 -t 00:00:15 -c copy out.mp4
# Frame-accurate cut, re-encode
ffmpeg -ss 00:00:38.450 -i input.mp4 -t 00:00:14.500 \
-c:v libx264 -crf 20 -preset medium -c:a aac -b:a 192k out.mp4
Cutting mid-file: avoid a negative start timestamp
When you cut from the middle, the new clip's start timestamp can be negative (relative to the cut point), which some players choke on — black screen or broken seeking. In MP4, -avoid_negative_ts make_zero realigns the start to 0:
ffmpeg -ss 00:00:38 -i input.mp4 -t 30 -c copy -avoid_negative_ts make_zero out.mp4
-t is duration, -to is an end timestamp. Pick one; don't pass both.
Concatenation: concat demuxer isn't a free lunch
Our Video Merger defaults to -f concat -c copy — fast and lossless. But it has a hidden requirement: every clip must match at the encoder level — codec, profile, resolution, frame rate, pixel format (yuv420p), SAR, time base, audio sample rate and channels. Any mismatch can throw Non-monotonous DTS errors, or produce desynced audio/video with silence in the second half.
Clips from different sources (recorded by different software/devices) usually don't match. The fix is to normalize first, then join:
# Normalize each clip to one spec
ffmpeg -i a.mp4 -c:v libx264 -r 30 -s 1920x1080 -pix_fmt yuv420p -c:a aac -ar 48000 -ac 2 a_std.mp4
ffmpeg -i b.mp4 ... b_std.mp4
# Then join losslessly via a list
printf "file 'a_std.mp4'\nfile 'b_std.mp4'\n" > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy out.mp4
If the clips can't be unified, fall back to the concat filter (re-encode, one generation of loss).
Try it
Video Trimmer cuts clips frame-accurately on a timeline; Video Merger joins several into one — both run locally in your browser.
FAQ
Q: Is -c copy truly lossless? A: Yes for the data — no re-encode, avoiding a second quantization loss. But "lossless picture" ≠ "accurate edit": it won't start on the frame you picked, and may include a few extra seconds. Treat the two as separate promises.
Q: Why is my -ss-before-input cut still off?
A: When -c copy is involved, the seek is pinned to keyframes and the start can only fall on one. For accuracy, re-encode (or seek precisely, then copy).
Q: Why is the second half of my merge silent? A: Most often the clips have different audio sample rates or channel counts, which concat demuxer can't reconcile. Normalize every clip to the same sample rate and channel layout first.