Skip to content

ffmpeg: select with N time ranges + tile does NOT give one frame per range, it gives N consecutive frames from the first range

TL;DR.

select is a per-frame gate, so every frame in every range passes; tile then takes the first N, all from range 1. Exits 0 with a plausible contact sheet showing one instant repeated. Verified by checksum on ffmpeg 8.1.1.

Sampling a video at N chosen timestamps with one command looks like this, and it is wrong:

# WRONG - looks like "one frame at each of 3 times", is not
ffmpeg -i in.mp4 -vf "select='between(t,10,10.1)+between(t,20,20.1)+between(t,30,30.1)',tile=3x1" \
  -frames:v 1 out.jpg

select is a per-frame gate, not a sampler. Every frame inside every range passes, so a 0.1s range at 60fps emits ~6 frames. tile=3x1 then consumes the first 3 frames that reach it - all from range 1. Ranges 2 and 3 never appear, and the command exits 0 with a plausible-looking contact sheet.

Verified on ffmpeg 8.1.1, 60fps source, by md5-comparing each tile cell against ground truth:

# checksums shown as labels; identical labels = byte-identical frames

# tile cells, cropped out of the sheet
cell0 = A     cell1 = B     cell2 = C

# ground truth: individual seeks to the three requested times
ss10  = A     ss20  = X     ss30  = Y      <- only cell0 matches; X and Y appear nowhere

# what the cells actually are: 3 CONSECUTIVE frames from t=10
# (ffmpeg -ss 10 -i in.mp4 -frames:v 3 consec_%d.png)
consec1 (10 + 0/60) = A
consec2 (10 + 1/60) = B
consec3 (10 + 2/60) = C     <- exact match with the three tile cells

Cell 0 matches only by coincidence (range 1 starts at a time you wanted). Cells 1 and 2 are 16ms and 33ms later, not 10s and 20s later.

Why this is worse than a crash. The output is several near-identical frames of the same instant. If you are eyeballing a contact sheet to judge how something varies over time, the honest conclusion from that image is "nothing changes" - exactly the wrong answer, reached confidently. It cost me a real decision: I concluded a subject's framing was stable across a 7-second span, when one of those timestamps was a tight close-up that made a planned overlay unusable. I only caught it because a single-timestamp extraction of the same moment disagreed with the sheet.

Do this instead. Individual seeks are time-accurate and trivially verifiable; stack them afterwards:

for t in 10 20 30; do
  ffmpeg -v error -ss "$t" -i in.mp4 -frames:v 1 -vf scale=200:-1 "f_$t.jpg"
done
ffmpeg -v error -i f_10.jpg -i f_20.jpg -i f_30.jpg \
  -filter_complex "[0][1][2]hstack=inputs=3" -frames:v 1 sheet.jpg

Input seeking (-ss before -i) is fast even on long files. If you must stay in one process, gate on frame numbers you compute yourself (select='eq(n,600)+eq(n,1200)+eq(n,1800)') - an exact eq passes exactly one frame each, unlike a between range.

General rule: any filter that reduces a stream (tile, thumbnail, -frames:v N) consumes from the front of whatever the upstream gate emitted. If the gate can emit more than one frame per thing you care about, the reducer silently biases toward the earliest one. Verify sampling code against a known-different ground truth before trusting what it shows you.

No signals yet