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:

The first method is the cleanest when you truly want frame-accurate slicing.


1. Direct frame-number selection (most literal)

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.mp4

Explanation:

If you're extracting a segment rather than keeping those frames:

ffmpeg -i input.mp4 -vf "select='between(n,1000,1500)'" -vsync 0 output.mp4

2. Cutting at specific frames (start/end)

If you want frame 1000 to 2000:

ffmpeg -i input.mp4 -vf "select='between(n,1000,2000)',setpts=N/FRAME_RATE/TB" -an output.mp4

Note:
This re-encodes the video.


3. Fast cut using frame → time conversion

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.mp4

Pros:

Cons:


4. If you want frame numbers with timestamps

First get frame numbers:

ffprobe -select_streams v -show_frames input.mp4

or

ffmpeg -i input.mp4 -vf showinfo -f null -

You'll see lines like:

n:1234 pts:...

5. Important limitation

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.


6. Practical workflow (what editors actually do)

Typical pipeline:

  1. detect frames
  2. mark frame numbers
  3. cut with select filter

or

convert frames → time → fast cut.


Bonus (very relevant to your earlier keyframe question)

You can force keyframes at exact frames:

ffmpeg -i input.mov -force_key_frames "expr:gte(n,1000)*lte(n,1000)" output.mp4

or 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. citeturn0search9turn0search7

This is extremely common in AMV workflows.

Below is what’s happening and the simplest fixes that don’t complicate your pipeline.


What’s actually happening

1. FFmpeg cuts by timestamp, not frame index

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.


2. Avidemux displays the frame index

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.


3. Codec prediction can add weird behavior

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. citeturn0search9

That can also create off-by-one frame artifacts.


The easiest fixes (minimal pipeline changes)

Fix 1 — subtract half a frame from the end time (best quick fix)

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.


Fix 2 — use -to instead of -t

This 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.


Fix 3 — move -ss after -i for frame accuracy

This 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.


Fix 4 — if you can tolerate re-encoding (most accurate)

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.


The best workflow for AMV editors (very simple)

What many AMV editors actually do:

  1. mark frames in editor
  2. export timestamp
  3. subtract 1 frame from the end timestamp automatically

Example automation logic:

end_time = end_time - (1 / fps)

This prevents the stray next-scene frame.


If you want a really robust solution

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.


My guess about your exact situation

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.


My recommended fix for your pipeline

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.


What’s causing the 1-frame leak

There are three interacting behaviors.

1. -to is absolute time and effectively inclusive

FFmpeg 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.


2. Avidemux frame timestamps are rounded

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.


3. Re-encoding still respects the source timestamps

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.


Fixes that fit your workflow

These require minimal changes to your command.


Fix 1 (most reliable): use -t instead of -to

Instead 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".mp4

This alone fixes most single-frame bleed problems.


Fix 2: force deterministic CFR timestamps

Add:

-vsync cfr -r SOURCEFPS

Example:

-vsync cfr -r 23.976

This 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".mp4

Fix 3 (optional but powerful): reset timestamps

Add:

-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.


Recommended minimal change

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".mp4

That keeps:

while removing the timestamp boundary ambiguity.


Why some clips worked before

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.