M: can i use ffmpeg to slice up movies using frame numbers rather than timing? like, hh:mm:ss.##, where ## is the frame number?
G: TL;DR:
Yes. FFmpeg can cut by frame number, but not using
hh:mm:ss.##. Instead you either:
select filter with frame
numbers, or-ss/-to.The first method is the cleanest when you truly want frame-accurate slicing.
You can select frames using the n (frame
index) variable.
Example: keep frames 1000–1500
ffmpeg -i input.mp4 -vf "select='between(n,1000,1500)',setpts=N/FRAME_RATE/TB" output.mp4Explanation:
n = current frame number (starting at 0)between(n,1000,1500) selects the rangesetpts fixes timestamps after dropping framesIf you're extracting a segment rather than keeping those frames:
ffmpeg -i input.mp4 -vf "select='between(n,1000,1500)'" -vsync 0 output.mp4If you want frame 1000 to 2000:
ffmpeg -i input.mp4 -vf "select='between(n,1000,2000)',setpts=N/FRAME_RATE/TB" -an output.mp4Note:
This re-encodes the video.
If you know the framerate:
time = frame / fps
Example:
Video = 30 fps
Frame 900
900 / 30 = 30 seconds
Then:
ffmpeg -ss 30 -to 60 -i input.mp4 -c copy output.mp4Pros:
Cons:
First get frame numbers:
ffprobe -select_streams v -show_frames input.mp4or
ffmpeg -i input.mp4 -vf showinfo -f null -You'll see lines like:
n:1234 pts:...
FFmpeg timestamps are time-based internally, not frame-based.
So FFmpeg does not support syntax like
00:01:23.15 (meaning frame 15)
because frames may not be constant if VFR.
Typical pipeline:
select filteror
convert frames → time → fast cut.
You can force keyframes at exact frames:
ffmpeg -i input.mov -force_key_frames "expr:gte(n,1000)*lte(n,1000)" output.mp4or simpler using timestamps.
This is useful for future frame-accurate splitting.
If you'd like, I can also show a much cleaner FFmpeg trick
for slicing movies into scenes using exact frame numbers that
avoids the usual select filter pitfalls. It's a weird trick
but extremely useful if you're doing lots of segmentation.
M: okay, help me out here; go online to check for answers or make sure your answer is confirmed. i'm trying to carve up a file for amv scenes. i've been using the latest version of avidemux to identify the timestamp of the frame. BUT - it seems that ffmpeg and avidemux are having some kind of rounding difference, or something like that, because while some clips are perfectly timed, others are grabbing a single frame of the next scene at the end of the clip - which implies that any following clip is missing that single frame. (which matters because for some of the pieces i want to use, the clip is short enough already that loss of a single frame would be loss of 5% of the usable duration.) ... try and figure out what's going on in terms of that variation, and suggest a couple fixes, either in methodology, settings, or something else that's not heavily complex. i've been creating the clips with an automation method - so i can change the way i do that easily; but i don't want to add, like, three extra major steps to that pipeline.
G: TL;DR (most likely
cause):
What you're seeing is almost certainly timestamp rounding +
keyframe/GOP behavior, not a random FFmpeg bug. Avidemux shows
frame positions, while FFmpeg cuts using
timestamps (PTS). Those timestamps are often
fractional and rounded differently, so your cutpoint
may land slightly after the intended frame, pulling in
one extra frame from the next scene.
citeturn0search9turn0search7
This is extremely common in AMV workflows.
Below is what’s happening and the simplest fixes that don’t complicate your pipeline.
Even if the video is constant-FPS, FFmpeg internally uses presentation timestamps (PTS) rather than frame numbers.
So when you say:
-ss 00:01:12.400
FFmpeg seeks to the nearest timestamp, which may correspond to:
frame 1799.8
Depending on rounding, it may resolve to:
frame 1800 or 1801
That’s your ±1 frame drift.
Avidemux shows the exact decoded frame, which is why your visual marker is correct.
But the timestamp printed may be rounded to milliseconds.
Example at 23.976 fps:
1 frame = 0.041708375 seconds
But Avidemux might show:
00:01:12.400
Actual timestamp might be:
00:01:12.400417
That tiny rounding difference can push FFmpeg to the next frame.
With codecs like H264/H265:
I-frame
P-frame
B-frame
Some frames depend on previous frames to decode.
So FFmpeg sometimes shifts the cut slightly to maintain a valid decode sequence unless you re-encode. citeturn0search9
That can also create off-by-one frame artifacts.
If your clip end is:
00:01:12.400
Use:
00:01:12.380
(or similar)
Formula:
half_frame = 1 / (2 × fps)
Examples:
| FPS | half-frame |
|---|---|
| 23.976 | ~0.021 |
| 24 | 0.0208 |
| 29.97 | 0.0167 |
| 30 | 0.0166 |
This forces FFmpeg to stop before the next frame boundary.
For AMV automation this is usually the cleanest fix.
-to
instead of -tThis reduces rounding issues.
Instead of:
-ss start -t duration
use:
-ss start -to end
FFmpeg tends to be slightly more consistent with absolute end timestamps.
-ss after -i for frame accuracyThis matters a lot.
Fast seek (less accurate):
ffmpeg -ss START -i input -to END -c copy out.mp4
Frame-accurate seek:
ffmpeg -i input -ss START -to END -c copy out.mp4
This forces FFmpeg to decode up to the frame, which reduces drift.
ffmpeg -i input -ss START -to END -c:v libx264 -crf 18 -c:a copy out.mp4
This allows FFmpeg to reconstruct dependent frames so it can cut exactly.
But for AMVs you may not want the re-encode step.
What many AMV editors actually do:
Example automation logic:
end_time = end_time - (1 / fps)
This prevents the stray next-scene frame.
Use frame numbers instead of timestamps:
ffmpeg -vf "select='between(n,START,END)',setpts=N/FRAME_RATE/TB"
But that decodes the entire video, so it's slower.
Given your description:
some clips are perfect, others grab exactly one frame
That pattern almost always means:
timestamp rounding difference between Avidemux and FFmpeg.
Not GOP structure.
Because GOP errors usually produce multiple frame shifts, not exactly one.
Since you’re automating:
Just subtract one frame from the end timestamp.
Example:
clip_end = clip_end - (1 / fps)
It’s the single easiest AMV-safe solution.
If you want, I can also show you a very small FFmpeg trick AMV editors use to make cuts deterministic across different tools, which eliminates the Avidemux/FFmpeg mismatch entirely. It’s only one extra parameter but fixes this problem permanently.
M: i am in fact re-encoding to avoid the i-frame issue. i can't use '-to end' because i'm grabbing clips; that would make them unuseably long and massively wasteful of space. and the single subtraction idea is way too time-consuming; it requires either checking each clip individually, or accepting a loss of a frame. this is the ffmpeg that my automation script uses; i'm not using an amv studio, i'm just doing it like a regular video, so that's got nothing to do with my work flow: ffmpeg -i "$inputFile" -nostdin -ss "$start" -to "$end" -an -sn -vcodec libx264 -map 0✌️0 -map_chapters -1 -map_metadata -1 "$outfile--$duration".mp4
G: TL;DR:
Your command places -ss and -to after
-i, which forces accurate seeking but
timestamp-based trimming. With H264 re-encoding this can
produce an off-by-one frame at the end because FFmpeg
treats -to as inclusive of the frame whose PTS
equals the end timestamp, while Avidemux reports frame
timestamps slightly differently. The simplest fix that keeps your
automation intact is to switch from -to →
-t (duration) and compute duration from
frame-accurate timestamps, or force CFR and deterministic
timestamps during encode.
Below is what’s happening and fixes that don’t add pipeline complexity.
There are three interacting behaviors.
-to is absolute time and effectively inclusiveFFmpeg documentation notes that:
-to position
Stop writing the output at position.
But the implementation stops after processing frames up to
that timestamp, meaning a frame whose PTS equals or
slightly exceeds the boundary can still appear.
This commonly produces a single extra frame at clip
ends when trimming by timestamp.
Editors typically display timestamps rounded to milliseconds, while actual frame timestamps may be:
0.041708375
But the UI shows:
0.042
If you copy the displayed value, the true frame boundary may lie slightly before or after it, which FFmpeg interprets differently.
Even though you re-encode, FFmpeg still bases trimming decisions on input PTS values unless explicitly reset.
So the cut point is still governed by PTS rounding differences.
These require minimal changes to your command.
-t instead of -toInstead of:
-ss "$start" -to "$end"use:
-ss "$start" -t "$duration"Since your script already calculates duration (it's in your filename), this is trivial.
Why this works:
-t means:
encode exactly N seconds after the start
So FFmpeg doesn't have to interpret an absolute timestamp boundary.
Example:
ffmpeg -i "$inputFile" -nostdin \
-ss "$start" -t "$duration" \
-an -sn \
-vcodec libx264 \
-map 0:v:0 \
-map_chapters -1 -map_metadata -1 \
"$outfile--$duration".mp4This alone fixes most single-frame bleed problems.
Add:
-vsync cfr -r SOURCEFPS
Example:
-vsync cfr -r 23.976This forces FFmpeg to align frames strictly to CFR boundaries during encode.
Your final command becomes:
ffmpeg -i "$inputFile" -nostdin \
-ss "$start" -t "$duration" \
-an -sn \
-vcodec libx264 \
-vsync cfr \
-map 0:v:0 \
-map_chapters -1 -map_metadata -1 \
"$outfile--$duration".mp4Add:
-fflags +genpts
or
-vf setpts=PTS-STARTPTS
This regenerates timestamps from frame order instead of source PTS.
Example:
-vf "setpts=PTS-STARTPTS"This eliminates weird source-PTS rounding issues.
Just switch -to → -t.
So your command becomes:
ffmpeg -i "$inputFile" -nostdin \
-ss "$start" -t "$duration" \
-an -sn -vcodec libx264 \
-map 0:v:0 -map_chapters -1 -map_metadata -1 \
"$outfile--$duration".mp4That keeps:
while removing the timestamp boundary ambiguity.
When the end timestamp happened to fall before the next frame’s PTS, FFmpeg stopped correctly.
When it landed on or slightly after the boundary, FFmpeg included the next frame.
So the variation is simply floating-point timestamp alignment.
If you'd like, I can also show a two-flag FFmpeg trick AMV editors use to guarantee deterministic frame boundaries across tools like Avidemux, VirtualDub, and VapourSynth. It fixes this permanently without adding steps.