FFmpeg can find the cuts in a video with the scdet filter, or with the scene value of the select filter.
Basic Command
ffmpeg -i input.mp4 \
-vf "scdet=t=10,metadata=mode=print:key=lavfi.scd.score" \
-f null /dev/null
-f null /dev/null means no output file is written. metadata=print prints the score of every frame to stderr. On each frame that scdet judges to be a cut, scdet also prints its own line with lavfi.scd.score and lavfi.scd.time.
Reading the Output
[Parsed_metadata_1 @ 0x...] frame:149 pts:76288 pts_time:4.966667
[Parsed_metadata_1 @ 0x...] lavfi.scd.score=0.131
[Parsed_scdet_0 @ 0x...] lavfi.scd.score: 25.146, lavfi.scd.time: 5
[Parsed_metadata_1 @ 0x...] frame:150 pts:76800 pts_time:5
[Parsed_metadata_1 @ 0x...] lavfi.scd.score=25.146
| Field | Description |
|---|---|
lavfi.scd.score |
Scene-change score (higher = larger change) |
lavfi.scd.time |
Timestamp of the detection (seconds) |
Tuning threshold
| Parameter | Default | Description |
|---|---|---|
t / threshold |
10 |
Scores at or above this value are reported as scene changes |
ffmpeg -i input.mp4 -vf "scdet=t=20" -f null /dev/null
- Low value (for example 5): small changes are detected too, so you get more false detections
- High value (for example 40): keeps only high-score candidates. More cuts are missed, and flashes can still cause false detections
- For documentaries and films, start with
8–14
Save a Frame at Every Scene Change
To save a still image for each detected cut candidate, use the select filter:
ffmpeg -i input.mp4 -vf "select='gt(scene,0.35)'" -fps_mode vfr scene_%04d.png
Note:
selectcomputes its ownscenevalue. It uses a different scale fromscdet’slavfi.scd.score, so tune the two thresholds separately.
Print Timestamps via metadata
ffmpeg -i input.mp4 -vf "scdet=t=10,metadata=print" -f null /dev/null
Threshold Suggestions by Content Type
The FFmpeg documentation suggests values between 8 and 14. Start with the default, 10. Raise it if busy footage gives false hits, and lower it if cuts are missed.
Caveats
Fades and Dissolves Are Hard to Detect
Even hard cuts (instant changes) are missed if their score stays below the threshold. Fades and dissolves (gradual changes) differ little from one frame to the next, so scdet often misses them.
Long Videos Take Time
Scene detection decodes every frame, so long videos take a while to analyze. To analyze only part of a video, limit it with -ss and -to.
Measured: time and size
The timing below is for this command. The 7.09 s measurement used -v error to suppress detection scores and timestamps in the log.
ffmpeg -y -v error -i input.mp4 -vf "scdet=t=12,metadata=mode=print:key=lavfi.scd.time" -f null -
| Measured | |
|---|---|
| Time | 7.09 s (16.92x realtime) |
| Output file | none — analysis only |
Test setup: a 1920x1080, 30 fps, 120 s, 351.4 MiB H.264 source (Big Buck Bunny, looped, CC BY 3.0), a Core i9-14900KF (32 threads) and FFmpeg 8.1 (gyan.dev build), measured on 2026-09-05. The raw numbers are in the dataset.
Why 7.09 seconds
This command decodes every frame and passes it through scdet. The default video encoder for -f null - is wrapped_avframe, which wraps frames in packets. It skips video compression and media file writes. If audio is selected, it is encoded as PCM. For comparison, a plain H.264 re-encode of the same clip on the same machine, with no filter (-c:v libx264 -crf 23 -preset medium), took 27.97 s. The filters, encoders, and file output differ, so this comparison alone cannot identify the cause of the time difference.
Commands that never decode a video frame finish in about a second: stream mapping took 0.41 s, moving the moov atom 0.49 s, and an audio fade 1.03 s. They copy the video stream unchanged (the audio fade re-encodes only the audio). The scdet timing fell between these runs and the H.264 re-encode. It decodes every frame but skips video compression. Time spent on decoding and other individual stages was not measured.
The speed of 16.92x realtime on 32 threads applies to 1080p 8-bit H.264 read from a local disk. 4K or 10-bit HEVC is much slower to decode. Dividing the 351.4 MiB source size by the 7.09 s elapsed time gives about 49.6 MiB/s, so a network connection slower than that becomes the limit before the CPU does.
The 7.09 s run wrote no images. Saving each detected scene as a PNG adds image encoding and disk writes to that time. If you use the detected times as edit points, leave 0.2–0.5 seconds of padding. Scene detection only looks at the picture and knows nothing about the timing of the audio or subtitles.
Frequently Asked Questions
What threshold should I use for scene detection?
Start with select='gt(scene,0.35)'. Lower it to 0.25–0.3 if too many cuts are missed, or raise it to 0.45–0.5 to keep only high-score candidates. Even high thresholds can miss cuts or mistake flashes for cuts. This scale is different from scdet’s t value.
How do I export the timestamps to a file?
Run ffmpeg -i in.mp4 -filter:v "select='gt(scene,0.4)',showinfo" -f null - 2> scenes.log, then search the log for pts_time (for example with grep).
Why does scene detect miss obvious cuts?
Cross-fades and flash cuts can stay below the threshold. To catch fades to black that work as cuts, combine scene detection with blackdetect.
Can I split the video at every detected scene automatically?
Yes. Feed the timestamps to a script that runs ffmpeg -ss A -to B -i input.mp4 -c copy partN.mp4 for each detected range. Because -c copy does not re-encode, a part can start at the keyframe before A. For frame-accurate cuts, drop -c copy and re-encode.
Does scene detect work on animation?
Yes, but adjust the threshold. With select, 0.25–0.3 is another range to try for scene. Check each source for missed cuts and false detections. scdet’s t uses a different scale; start from the default, 10, and adjust from there.