FFMPEG COMMAND

Detect scene changes with FFmpeg

FFmpeg scores how different each frame is from the previous one. Frames above a threshold are the cuts. The test file has hard cuts at 3, 6 and 9 s and both methods below found exactly those.

Verified with FFmpeg 9.0.1 on 26 August 2026. Search intent: ffmpeg scene detection.

the command
ffmpeg -i input.mp4 -vf "select='gt(scene,0.4)',showinfo" -f null -

select keeps only frames whose scene score is above 0.4 and showinfo prints one line per kept frame. The pts_time values in those lines are the cut timestamps: 3, 6 and 9 on the test file.

Flags, explained

select='gt(scene,0.4)'
Pass a frame only when its scene score (0 to 1) is greater than 0.4. Typical cuts score above 0.3.
showinfo
Log one line per frame with pts_time and other frame details.
-f null -
Run the filters without writing a video.
metadata=print:file=
Write the frame metadata (score and timestamp) to a file.
scdet=threshold=10
Scene change detector with its own scale (0 to 100); 10 is the default.
-fps_mode vfr
Variable frame rate output so only selected frames are written. Replaces -vsync vfr from older releases.

Variants

Write a clean list with the scores

shell
ffmpeg -i input.mp4 -vf "select='gt(scene,0.4)',metadata=print:file=scenes.txt" -f null -

scenes.txt gets one block per cut with pts_time and lavfi.scene_score (0.727, 0.688 and 0.828 for the three cuts). Easier to parse than the showinfo log.

The scdet filter

shell
ffmpeg -i input.mp4 -vf "scdet=threshold=10" -f null -

A dedicated detector with a 0 to 100 score. It logged lavfi.scd.score 28.4, 26.9 and 32.3 at lavfi.scd.time 3, 6 and 9. Add metadata=print:key=lavfi.scd.time:file=scdet.txt after it to get the times in a file.

Save one thumbnail per scene

shell
ffmpeg -i input.mp4 -vf "select='gt(scene,0.4)'" -fps_mode vfr scene-%03d.png

Each frame that starts a new scene is written as a PNG: scene-001.png to scene-003.png here. -fps_mode vfr stops FFmpeg from duplicating frames to keep a constant rate.

Looser threshold

shell
ffmpeg -i input.mp4 -vf "select='gt(scene,0.1)',showinfo" -f null -

Catches softer transitions and more false positives on fast motion. On the synthetic file it still returned the same three cuts; on real footage expect more.

Pitfalls we hit

  • The select scene score runs from 0 to 1, scdet from 0 to 100. A threshold of 0.4 in one is not 40 in the other; the same three cuts scored 0.69 to 0.83 and 26.9 to 32.3.
  • Dissolves and fades spread the change over many frames, each with a low score, so they are not reported as one cut. Lower the threshold or use scdet with a lower value and merge nearby hits.
  • The expression must be quoted: the shell removes the inner quotes and the comma inside gt(scene,0.4) then breaks the filter graph.
  • Log lines go to stderr. Pipe with 2>&1 or use the metadata file variant.
  • Fast motion, flashes and camera shake also score high. Check the thumbnails before trusting the list.

How we verified it

Run with FFmpeg 9.0.1 on 26 August 2026 against a 12 s, 1280x720, 30 fps H.264 file made of four different 3 s test patterns. select with threshold 0.4 printed pts_time 3, 6 and 9 with scene scores 0.727, 0.688 and 0.828. scdet with threshold 10 logged scores 28.4, 26.9 and 32.3 at 3, 6 and 9 s. The thumbnail variant wrote scene-001.png to scene-003.png. Threshold 0.1 returned the same three frames.

The same edit as a typed operation

detect_scenes returns the cut timestamps as JSON for one threshold value, and split_video_by_scenes renders one MP4 per scene in the same job, so an agent never parses filter logs or builds a segment list by hand.

MCP tool: detect_scenesPOST /api/v1/tools/detect-scenes
request body
{
  "inputs": [{ "id": "main", "url": "https://example.com/video.mp4" }],
  "options": { "threshold": 0.3 }
}

FAQ

Start at 0.3 to 0.4 with the select method. Raise it if fast motion produces false cuts, lower it to 0.2 for subtle edits. Always verify with a few thumbnails.

Take the pts_time values and pass them to the segment muxer with -segment_times, or cut each range with -ss and -to. Cuts with -c copy only land on keyframes, so re-encode for exact boundaries.

Both found the same cuts here. select with the scene score is available everywhere and composes with other filters; scdet is purpose-built, logs its own timestamps and lets you tune the score range.

Each frame of a fade differs only slightly from the previous one, so no single frame crosses the threshold. Lower the threshold and group consecutive hits, or detect the fade with a brightness check instead.

Related commands

All FFmpeg commands