How to automatically detect scene cuts in a video
- Step 1Drop your video onto the detector — Drag any
.mp4,.mov,.mkv,.webm,.avi,.m4vor.tsfile onto the tool above. FFmpeg.wasm loads in your browser; the file's bytes stay local. The detector accepts one file at a time (acceptsMultiple: false). - Step 2Pick a sensitivity threshold — Set Threshold between
0.05and0.9. The default0.3catches most hard cuts in scripted and interview footage. Use0.5for talking-head footage where you only want true scene changes; use0.1to catch soft fades and dissolves (at the cost of more false positives). There is no other option — no min-scene-length, no fade mode. - Step 3Run detection — JAD executes
-vf select='gt(scene,T)',showinfo -f null -. FFmpeg decodes every frame and emits ashowinfolog line — includingpts_time— for each frame that exceeds the threshold. Larger files take longer because every frame is decoded; there is no fast-seek shortcut for detection. - Step 4Read the cut timestamps — The detector parses every
pts_time:value from the FFmpeg log into a numeric array. Output is a JSON file named<yourfile>-scenes.jsoncontaining{ "threshold": <your value>, "cuts": [<seconds>, ...] }. Timestamps are in seconds with sub-second precision (e.g.31.041). - Step 5Tune and re-run if the cut count looks wrong — Too many cuts (the reel jitters on a handheld shot)? Raise the threshold toward
0.5. Missed a soft dissolve or a fade-to-black? Lower it toward0.1. Detection is deterministic, so changing only the threshold and re-running gives a predictable, comparable result you can diff against the previous run. - Step 6Feed the JSON into the next step — Map the
cutsarray to chapter markers, or hand the timestamps to video-splitter to cut the file at those points, or to lossless-trimmer to extract one segment between two cuts. For a visual sanity check of the cuts, drop the same file into frame-grid-maker.
What the scene detector actually does
The real surface of the tool. There is one option only — everything else is fixed by the implementation.
| Aspect | Behaviour | Notes |
|---|---|---|
| Control | Threshold (number, 0.05–0.9, default 0.3) | The only UI control. Clamped server-side to the same range; values outside snap to the bounds |
| FFmpeg filter | select='gt(scene,T)',showinfo run as -f null - | Scores each frame's visual difference from the previous frame; logs frames above T |
| Output | A JSON file <name>-scenes.json | Shape: { "threshold": T, "cuts": [seconds, ...] } — NOT CSV, NOT SRT, NOT split clips |
| Timestamp unit | Seconds, sub-second precision | Parsed from each pts_time in the showinfo log |
| Re-encode? | No — detection is read-only | Your source bytes are never modified; this is analysis, not export |
| Engine | FFmpeg.wasm (multithread when available, else single-thread) | hardwareAccelerated: false — scene detection does not use the WebCodecs path |
Threshold behaviour cheat-sheet
How the 0.05–0.9 range maps to footage. These are practical starting points; every clip is different, so tune and re-run.
| Threshold | Catches | Best for |
|---|---|---|
0.1 (aggressive) | Hard cuts + soft fades, dissolves, big lighting shifts — plus false positives on motion | Music videos, montages, anything with crossfades you must not miss |
0.3 (default) | Most hard cuts; ignores gradual brightness drift | Scripted content, interviews, documentaries — the reliable starting point |
0.5 (conservative) | Only clear scene changes; tolerates camera movement and grading | Talking-head footage, single-cam podcasts, screen recordings |
0.7–0.9 (strict) | Only the most abrupt cuts (e.g. black frame to bright scene) | Pre-cut highlight assembling where you want very few boundaries |
Tier limits (video family)
Source-file size and batch caps. The detector processes one file per run regardless of tier.
| Tier | Max file size | Batch files |
|---|---|---|
| Free | 1 GB | 1 |
| Pro | 10 GB | 5 |
| Pro-media | 100 GB | 50 |
| Developer | 100 GB | Unlimited |
Cookbook
Real input/output pairs for the auto-detect workflow. The cuts arrays below are abbreviated; a real run can return dozens of timestamps.
Default run on a 12-minute interview
Drop the file, leave Threshold at 0.3, run. The detector logs each over-threshold frame's pts_time and writes them to JSON. A two-camera interview that switches angles produces a clean list of switch points.
Input: interview-2cam.mp4 (12:04, 720p)
Threshold: 0.3 (default)
Output file: interview-2cam-scenes.json
{
"threshold": 0.3,
"cuts": [ 8.34, 27.1, 54.88, 91.2, 142.5, ... ]
}Too many cuts on handheld footage — raise the threshold
A handheld run-and-gun shot produces dozens of spurious cuts at 0.3 because camera shake spikes the frame-difference score. Raising to 0.5 collapses the jitter into the real scene boundaries.
Run 1 (threshold 0.3): 47 cuts ← shake false-positives Run 2 (threshold 0.5): 9 cuts ← actual scene changes Which to use: 0.5 here. Re-running only changes the threshold value in the JSON; the file is never modified.
Missed a cross-dissolve — lower the threshold
Two scenes joined by a 1-second cross-dissolve change gradually, so at 0.3 the per-frame delta never crosses the line and the cut is missed. Drop to 0.1 to catch gradual transitions.
Threshold 0.3: cut between scene A and B NOT detected
Threshold 0.1: cut detected at 64.20
(but +14 extra cuts on a panning shot)
Trade-off: lower threshold = more soft cuts caught
AND more false positives. Tune per clip.Hand the JSON to the splitter
Detection finds the boundaries; it does not cut the file. Take the cuts array and use it to drive the splitter, which produces the actual clips. The two tools are a pipeline.
Step 1 scene-detector → interview-2cam-scenes.json
cuts: [8.34, 27.1, 54.88, 91.2]
Step 2 video-splitter (or lossless-trimmer per segment)
cut the source at those seconds into clips
The detector alone returns JSON only — no clips.Verify cuts visually with a contact sheet
Not sure whether a timestamp is a real cut or a false positive? Generate a frame grid of the same source to eyeball the shot changes against the JSON list.
Same source → frame-grid-maker (4x4 contact sheet) Compare the grid tiles against the cuts[] array: cuts say a boundary at ~55s; the tile near that point shows a new shot → confirmed real cut. Use this to decide whether to raise/lower threshold.
Edge cases and what actually happens
Output is JSON, not CSV or SRT or split clips
By designThe detector writes a single JSON file {threshold, cuts} with cut timestamps in seconds. It does not produce CSV, SRT, or chapter files, and it never splits the video into clips. If you need actual clips, hand the timestamps to video-splitter or lossless-trimmer. If you need YouTube chapter markers, format the seconds into HH:MM:SS yourself or see chapter-extractor for embedded-chapter reading.
Cross-dissolves and fades missed at the default threshold
ExpectedFFmpeg's scene score is calibrated for abrupt frame-to-frame change. A gradual cross-dissolve or fade-to-black changes slowly enough that no single frame exceeds 0.3, so the cut is not flagged. Lower the threshold toward 0.1 to catch them — but expect more false positives on motion. There is no dedicated fade-detection mode.
High-motion or handheld footage produces false-positive cuts
ExpectedCamera shake, fast pans, and rapid action spike the per-frame difference score and register as cuts even within a single continuous shot. Raise the threshold toward 0.5–0.6 to suppress them. The tool has no min-scene-length filter, so very short spurious gaps cannot be removed inside the tool — filter the cuts array yourself downstream.
Threshold values outside 0.05–0.9 are clamped
ClampedBoth the UI control and the processor clamp the threshold to [0.05, 0.9]. A value of 0 or 1.5 snaps to the nearest bound. This prevents 0 (which would flag every frame) and 1.0 (which would flag nothing). The threshold that was actually used is written into the JSON so the result is self-documenting.
Empty cuts array on a single-shot video
ExpectedA static screen recording or a single locked-off shot has no scene changes, so no frame exceeds the threshold and cuts comes back as []. This is correct, not an error. If you expected cuts, lower the threshold; if you genuinely have one continuous shot, the empty array confirms it.
Large files are slow because every frame is decoded
ExpectedDetection runs -f null -, which forces FFmpeg to decode the entire stream frame by frame — there is no keyframe-only fast path for scene scoring. A multi-hour 4K file can take several minutes in FFmpeg.wasm. The file is also read fully into memory (not streamed), so very large files are bounded by browser memory before they are bounded by the tier size cap.
File exceeds the tier size limit
RejectedFree tier caps source files at 1 GB; Pro at 10 GB; Pro-media and Developer at 100 GB. A file over your tier's cap is rejected before processing. Note the practical browser-memory ceiling can bite first for scene detection, since the whole file is loaded into memory and every frame decoded — a 4K multi-hour file may struggle even under the size cap.
Unsupported container extension
Falls back to mp4The engine recognizes mp4, mov, mkv, webm, avi, m4v and ts. An unrecognized extension is treated as mp4 internally. If your file is actually a different container with the wrong extension, detection may fail to decode — rename it to its true extension or remux first. FLV, WMV, and OGV are not in the recognized list.
Variable-frame-rate (VFR) source timestamps
Preservedpts_time reflects the presentation timestamp of each frame, so VFR sources report true wall-clock cut times — they are not assumed to be on a constant grid. This is exactly what you want when mapping cuts back onto the original timeline for splitting.
FFmpeg core failed to load
ErrorIf the FFmpeg.wasm core cannot be fetched (offline, or unpkg.com blocked by a network policy), the tool throws 'FFmpeg core failed to load.' The detector first tries the multithreaded core and silently falls back to single-thread on timeout; if both fail, you'll see this error. Self-hosting the @ffmpeg/core assets resolves it on locked-down networks.
Frequently asked questions
What format are the detected scene timestamps in?
A JSON file named <yourfile>-scenes.json with the shape { "threshold": 0.3, "cuts": [8.34, 27.1, ...] }. The cuts array holds the time of each detected cut in seconds with sub-second precision. It is not CSV, not SRT, and not a chapter file — convert downstream if you need another format.
Does this tool split my video into clips?
No. The scene detector only finds and lists cut timestamps; it never modifies or splits the source. To actually cut the file at the detected points, feed the timestamps to video-splitter, or extract a single segment between two cuts with lossless-trimmer.
What is the default threshold and what range can I use?
The default is 0.3, and the control accepts 0.05 to 0.9. 0.3 catches most hard cuts. 0.5 is conservative (talking-head footage). 0.1 is aggressive and catches soft fades plus more false positives. Values outside the range are clamped to the nearest bound.
Can it detect fades and cross-dissolves?
Only partially. FFmpeg's scene score is built for abrupt change, so gradual transitions often slip under the default threshold. Lower it toward 0.1 to catch them — but motion in the footage will then also trip more false cuts. There is no separate fade-detection mode in this tool.
Are there any options besides the threshold?
No. The only control is the Threshold number input. There is no min-scene-length, no soft-cut mode, no batch detection, and no choice of output format — the implementation exposes exactly one parameter.
Is my footage uploaded anywhere?
No. Detection runs entirely in your browser via FFmpeg.wasm. Your video bytes never leave your machine; the only network request is fetching the FFmpeg core (and you can self-host it). This makes the tool safe for NDA footage, unreleased cuts, and confidential screen recordings.
Why are there more cuts than I expected?
High-motion, handheld, or fast-panning footage spikes the frame-difference score and registers false-positive cuts at the default threshold. Raise the threshold toward 0.5–0.6. Because the tool has no min-scene-length filter, remove very short spurious gaps by filtering the cuts array yourself after download.
Why did it find no cuts at all?
If cuts is [], no frame exceeded your threshold — typical for a single continuous shot or a static screen recording. Lower the threshold to surface subtler changes, or accept the empty array as confirmation that the clip is one unbroken shot.
How big a file can I detect cuts in?
Free allows up to 1 GB per file, Pro 10 GB, Pro-media and Developer 100 GB. Because detection loads the whole file into memory and decodes every frame, the practical limit for very large or 4K multi-hour files is browser memory, which can bite before the size cap does.
Which video formats can I drop in?
MP4, MOV, MKV, WebM, AVI, M4V and TS are recognized. Other extensions are treated as MP4 internally and may fail to decode if the real container differs — remux or rename to the true extension first. FLV, WMV and OGV are not supported.
Is the result the same every time I run it?
Yes. Detection is deterministic: the same file at the same threshold produces the identical cuts array. This lets you change only the threshold and diff the new result against the previous run to see exactly which cuts appeared or disappeared.
How do I turn the timestamps into YouTube chapters?
The detector outputs seconds (e.g. 91.2). Convert each to HH:MM:SS and paste into your video description as chapter markers — the first marker must be 00:00. The tool does not write chapter files; for reading chapters already embedded in a file, see chapter-extractor.
Privacy first
Every JAD Video tool runs entirely in your browser via WebCodecs and FFmpeg (WebAssembly). Your video files never leave your device — verified by zero outbound network requests during processing.