The strip of small frames that appears when you drag a video's seek bar is not a hundred images. It is one grid image and a small text file, and FFmpeg produces the grid in a single pass.
Why one image
Extracting a frame every few seconds and serving each separately means the player makes one request per thumbnail. For an hour-long video at five-second intervals that is 720 requests, which is slow no matter how small each file is.
Combining them into a grid (a sprite sheet) makes it one request. The accompanying file tells the player which rectangle corresponds to which moment.
Producing the grid
ffmpeg -i input.mp4 -vf "fps=1/5,scale=160:-1,tile=10x10" -qscale:v 5 sprite_%03d.jpg
Three filters doing the work. fps=1/5 takes one frame every five seconds. scale=160:-1 shrinks each to 160 pixels wide, keeping the proportions. tile=10x10 arranges them in a grid.
A hundred frames fit in each sheet, so a video longer than 500 seconds produces several, hence the numbered output. For most videos one sheet is enough.
Understanding FFmpeg filters deals with the chain syntax.
Choosing the interval
The interval decides both the usefulness and the size.
Five seconds is a reasonable default for most content. Ten is fine for a long recording where fine seeking is not expected. Two produces a much larger image for a marginal improvement.
Work out the arithmetic before running it: a two-hour video at two-second intervals is 3,600 thumbnails, which is thirty-six sheets and an unhelpful amount of data.
The caption file
Players commonly expect a WebVTT file describing which region of the image belongs to which time:
WEBVTT 00:00:00.000 --> 00:00:05.000 sprite_001.jpg#xywh=0,0,160,90 00:00:05.000 --> 00:00:10.000 sprite_001.jpg#xywh=160,0,160,90
Each entry names the image and the rectangle within it. This is generated rather than written: the numbers follow directly from the interval, the thumbnail size and the grid dimensions.
Getting the thumbnail height right matters: for 16:9 source scaled to 160 wide, the height is 90, and using the wrong figure offsets every rectangle after the first row.
Know the exact dimensions
ffprobe -v error -select_streams v:0 -show_entries stream=width,height,duration -of csv input.mp4
The duration determines how many thumbnails there will be, and the proportions determine the scaled height. Both are needed to write the caption file correctly, which is why this comes first rather than after.
Using ffprobe to inspect media files explains reading it.
Keep the sheet a sensible size
A very large sprite image is one request and a slow decode, particularly on phones, where a sheet several thousand pixels square costs noticeable memory and time.
Staying under roughly 2,000 pixels in each direction is a reasonable rule, which for 160-pixel thumbnails means grids around 12 by 12. Beyond that, produce several sheets in place of one enormous one.
JPEG at a moderate quality is right here. These are small images displayed small, and the quality setting matters far less than the count.
Where it belongs in the pipeline
Generating the sprite requires decoding the whole video, so it belongs in the same processing run as the encode rather than as a separate pass over the file afterwards.
It is also work that can proceed alongside other non-encoding tasks, since it is comparatively light. Running FFmpeg jobs in parallel walks through deciding what can safely run together.
For the single poster image a video also needs, the approach is different: creating video thumbnails and previews goes over picking a frame worth showing, and detecting scenes walks through avoiding the blur between shots.
Match the sheet to what the player expects
A sprite sheet is only useful if the player can work out where each frame sits, and that depends on numbers agreeing exactly.
ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 sprite.jpg ffmpeg -i in.mp4 -vf "fps=1/10,scale=160:-2,tile=5x5" -q:v 3 sprite_%03d.jpg
Three values have to match between the sheet and its description: the frame width, the frame height, and how many frames each row holds. A single pixel difference shifts every frame after the first.
The scale expression producing an even height matters here. An odd number is rejected by some encoders and rounds unpredictably in others, which is how a sheet ends up one pixel off.
Keep the whole thing small enough to be worth it
The point of a sprite sheet is one request instead of hundreds, and a sheet that is several megabytes defeats the purpose.
ls -la sprite_*.jpg | awk '{s+=$5} END {printf "toplam %.1f KB\n", s/1024}'
identify sprite_001.jpg 2>/dev/null || ffprobe -v error -show_entries stream=width,height -of csv=p=0 sprite_001.jpg
Small thumbnails at moderate quality are enough, since the viewer sees them at a fraction of the video size while dragging. A quality setting chosen for a still image is far higher than this needs.
For a long video, several sheets are better than one very large image, because the player fetches only the section being scrubbed. Splitting also keeps each file within the dimensions older devices will decode.
Generate the sheet and its description together
The image and the file describing it have to agree, and producing them in separate steps is how they drift apart.
INT=10; W=160 ffmpeg -i in.mp4 -vf "fps=1/$INT,scale=$W:-2,tile=5x5" -q:v 3 sprite_%03d.jpg ffprobe -v error -show_entries format=duration -of csv=p=0 in.mp4
Keep the interval, the width and the grid in variables and use the same values to write the description. A change to one that is not reflected in the other produces frames offset by a fixed amount, which looks like a bug in the player.
Record the values alongside the output so a sheet regenerated months later matches the description already deployed.
Handle videos that are too short or too long
The command works on typical material and behaves badly at both extremes.
D=$(ffprobe -v error -show_entries format=duration -of csv=p=0 in.mp4) echo "sure $D saniye, beklenen kare: $(echo "$D/10" | bc)"
A video shorter than the interval produces a single frame, and a grid expecting twenty five is mostly empty. Calculate the interval from the duration rather than fixing it, so short clips still produce a usable strip.
Very long material produces many sheets, which is correct, and the player has to know how many. Counting the generated files and recording that number is part of the same step rather than something to work out later.