TechEarl

Extract a Thumbnail or Frame From a Video with ffmpeg

Grab a thumbnail or a single frame from a video with ffmpeg: pull a frame at a timestamp, let the thumbnail filter pick a representative one, sample one every N seconds, and control JPEG quality with -q:v.

Ishan Karunaratne⏱️ 7 min readUpdated
Share thisCopied
Extract a thumbnail or single frame from a video with ffmpeg: grab a frame at a timestamp with a fast -ss seek, use the thumbnail filter for a representative frame, sample one every N seconds, and set JPEG quality with -q:v 2.

To pull a single frame out of a video as a thumbnail, seek to a timestamp and tell ffmpeg to write one frame:

bash
ffmpeg -ss 00:00:05 -i in.mp4 -frames:v 1 thumb.jpg

That grabs the frame at the five-second mark and writes it to thumb.jpg. The whole trick is the placement of -ss: putting it before -i makes ffmpeg seek the input by keyframe before it starts decoding, which is near-instant even on a two-hour file. -frames:v 1 then stops after the first decoded frame. The rest of this page is the variations: letting ffmpeg pick a representative frame for you, sampling one frame every N seconds, controlling JPEG quality, and scaling the output.

Grab a frame at a specific timestamp

The command above is the one you want most of the time. A few notes on the parts:

bash
# Five seconds in, written as a JPEG
ffmpeg -ss 00:00:05 -i in.mp4 -frames:v 1 thumb.jpg

# Same idea, written as PNG (lossless, larger file)
ffmpeg -ss 00:00:05 -i in.mp4 -frames:v 1 thumb.png

# Seconds work too: 90 = 1:30
ffmpeg -ss 90 -i in.mp4 -frames:v 1 thumb.jpg

-ss before -i is a fast input seek: ffmpeg jumps to the nearest keyframe at or before the timestamp, so it is quick but lands on a keyframe rather than the exact requested moment. For a thumbnail that almost never matters. When you need the frame at that exact time (frame-accurate), use two -ss flags: an input seek before -i to jump quickly to a keyframe a little before the target, then an output seek after -i to decode forward the remaining distance to the exact timestamp:

bash
# Input-seek to ~4s (fast, keyframe), then decode forward 1s to land on exactly 5s
ffmpeg -ss 00:00:04 -i in.mp4 -ss 00:00:01 -frames:v 1 thumb.jpg

The two add up to the target time (4s + 1s = 5s), so you get the speed of input seeking plus the frame accuracy of an output seek. Accurate seeking is on by default; pass -noaccurate_seek to turn it off. For a single still this is overkill, but it is the pattern to reach for when the precise frame matters.

Let ffmpeg pick a representative frame

If you do not have a timestamp in mind and just want a frame that looks like the video (not a black fade-in or a transition), use the thumbnail filter. It analyses a batch of frames and picks the one most representative of the set:

bash
ffmpeg -i in.mp4 -vf "thumbnail" -frames:v 1 thumb.jpg

By default it considers 100 frames at a time and scores them against the batch average, so it tends to skip the near-black opening frames that a naive first-frame grab lands on. It is the right default for auto-generating a poster image when you cannot hand-pick the moment. You can widen the analysis window by passing a frame count, for example thumbnail=300, at the cost of a little more decoding.

Sample one frame every N seconds

For a series of stills (a storyboard, a set of preview images, the input for a contact sheet) use the fps filter with a fractional rate. fps=1/10 means one frame every ten seconds:

bash
# One frame every 10 seconds, numbered shot-001.jpg, shot-002.jpg, ...
ffmpeg -i in.mp4 -vf "fps=1/10" shot-%03d.jpg

The %03d in the output name is a printf-style counter that ffmpeg fills in, zero-padded to three digits. Change the fraction to change the cadence: fps=1 is one frame per second, fps=1/60 is one per minute. Note that here -ss does not help much, because you are walking the whole file by design, so leave it off and let the filter sample across the entire duration.

If you want a fixed number of evenly spaced frames regardless of length, compute the rate from the duration, or reach for the thumbnail filter per segment. For a single tiled preview image, the tile filter pairs with fps to lay the samples out in a grid in one pass.

Control JPEG quality with -q:v

JPEG thumbnails default to a middling quality that can look soft. The -q:v flag (also spelled -qscale:v) sets the quality on a scale where lower is better: 1 is the best, 31 is the worst. For a crisp thumbnail, 2 is the standard choice:

bash
ffmpeg -ss 00:00:05 -i in.mp4 -frames:v 1 -q:v 2 thumb.jpg

-q:v 2 is the sweet spot for stills: visually near-lossless, still a reasonable file size. Drop to -q:v 1 if you want the absolute best and do not care about bytes. This flag only applies to lossy formats like JPEG; for PNG output it does nothing (PNG is already lossless), so save it for .jpg.

Scale the thumbnail

Resize while you extract by adding the scale filter. Use -1 for one dimension to preserve the aspect ratio:

bash
# 320px wide, height auto to keep the aspect ratio
ffmpeg -ss 00:00:05 -i in.mp4 -frames:v 1 -vf "scale=320:-1" -q:v 2 thumb.jpg

Combine it with the thumbnail filter by chaining them in one -vf chain (comma-separated, applied left to right):

bash
ffmpeg -i in.mp4 -vf "thumbnail,scale=320:-1" -frames:v 1 -q:v 2 thumb.jpg

If a dimension comes out odd and your encoder complains, use -2 instead of -1 to round to the nearest even number, which some codecs require.

-frames:v versus -vframes

You will see both spellings in older guides. They do the same job for video, but -frames:v is the current form and -vframes is an obsolete alias:

bash
# Current, preferred
ffmpeg -ss 00:00:05 -i in.mp4 -frames:v 1 thumb.jpg

# Old alias, still works but deprecated
ffmpeg -ss 00:00:05 -i in.mp4 -vframes 1 thumb.jpg

Per the official documentation, -vframes is a deprecated alias and the stream-specifier form -frames:v is the one to use in new commands. Both stop writing after the given number of frames; reach for -frames:v so your commands age well.

FAQ

Placing -ss before -i is an input seek: ffmpeg jumps to the nearest keyframe before it starts decoding, so it is fast even on long files. Placing it after -i decodes every frame up to the timestamp, which is frame-accurate but slow. For a thumbnail, before--i is almost always what you want.

Seek to zero (or omit the seek): ffmpeg -i in.mp4 -frames:v 1 thumb.jpg. Be aware that the first frame is often a fade-in or near-black; if you want a frame that represents the video, use the thumbnail filter instead.

It sets JPEG quality, where lower is better. The scale runs 1 (best) to 31 (worst), so -q:v 2 is near-lossless with a sensible file size. It has no effect on PNG output, which is already lossless.

Use the fps filter with a fraction: ffmpeg -i in.mp4 -vf "fps=1/10" shot-%03d.jpg writes one frame every ten seconds, numbered by the %03d counter. Change the fraction to change the cadence.

Functionally yes for video, but -vframes is an obsolete alias. The official docs recommend the stream-specifier form -frames:v, so prefer that in new commands.

See also

Sources

Authoritative references this article was fact-checked against.

Tagsffmpegvideo thumbnailextract framescreenshot videoCLILinuxvideo

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Software Systems Architect · Senior Software Engineer · Engineering Leadership

Software systems architect and senior software engineer with more than two decades designing, building, and running production software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Now a CTO, though what I write here is drawn from the full arc of that work, across architecture, engineering, and operations, not any single job.

Keep reading

Related posts

Extract audio from a video with ffmpeg by copying the stream untouched (fast, lossless) or re-encoding to MP3, AAC, or WAV when you need a different format.

How to Extract Audio From a Video with ffmpeg

Pull the audio out of a video with ffmpeg: copy the stream untouched when you just want to demux (fast, lossless), or re-encode to MP3, AAC, or WAV when you need a different format. Plus how to check the source codec first and grab a single segment.