Guide
PySceneDetect and ffmpeg scdet find the same hard cuts in a studio stream. Split each clip into shots at those cuts so a 9:16 crop never pans across one, or let clipsdk do it at $0.03 per source minute.
Scene cut detection with 2 detectors, and a fresh 9:16 crop at every cut
A vertical reframe follows a face with a smooth camera path, and that path has to restart at every cut or it slides across the edit.
GuidesUpdated
Or one API call
$0.03per source minute
- Every clip included, up to 1080p60 and 20 Mb/s
- $1 free to start, 33 minutes of video
- Prepaid credit, no subscription
Cut detectors on the same stream
Run on the full NASA in Silicon Valley Live episode, about 10 minutes at 1280x720.
| Detector | Setting | Cuts found | Runs in |
|---|---|---|---|
PySceneDetect AdaptiveDetector | Defaults: adaptive_threshold 3.0, min_scene_len 15 frames | 61 | Python, decodes with OpenCV |
ffmpeg scdet | threshold=8 on a 640 px copy | 61, all within 0.5 s of an AdaptiveDetector cut | Any ffmpeg filter graph |
clipsdk reframe: "auto" | scdet at 8, plus a check for slow fades | Clip 2 renders with the same 8 cuts | One API call |
Find the cuts and split a clip into shots
Four steps: get the stream, run PySceneDetect, run ffmpeg scdet, then split one clip at the cuts.
Get the stream
The NASA in Silicon Valley Live episode is a studio show that cuts between close-ups and two-shots of three people, so it has plenty of hard cuts.
terminal curl -o stream.mp4 https://api.clipsdk.com/f/lib/demo/silicon-valley-live-stream.mp4 pip install "scenedetect==0.7.2" "opencv-python-headless==5.0.0.93"Find cuts with PySceneDetect
AdaptiveDetectorcompares each frame's change with the change around it, which can cut down on false cuts from fast camera moves.detect()returns a start and end per scene, so every scene start after the first is a cut.adaptive.py import json from scenedetect import detect, AdaptiveDetector scenes = detect("stream.mp4", AdaptiveDetector()) cuts = [start.seconds for start, _ in scenes[1:]] json.dump(cuts, open("adaptive.json", "w")) print(len(cuts), "cuts") print([round(t, 2) for t in cuts[:8]]) # $ python adaptive.py # 61 cuts # [15.07, 32.47, 38.33, 40.77, 45.67, 62.87, 69.23, 74.37]Find cuts with ffmpeg scdet
scdetscores the change between frames from 0 to 100 and tags a frame as a cut when the score passes the threshold. The ffmpeg docs call 8 to 14 good values; we use 8 on a 640 px copy, the setting the clipsdk engine uses.terminal ffmpeg -i stream.mp4 -an -vf "scale=640:-2:flags=area,scdet=threshold=8,metadata=mode=print:key=lavfi.scd.time:file=cuts.txt" -f null - grep -c pts_time cuts.txt grep -o 'pts_time:[0-9.]*' cuts.txt | head -8 # output: # 61 # pts_time:15.066667 # pts_time:32.466667 # pts_time:38.333333 # pts_time:40.766667 # pts_time:45.666667 # pts_time:62.866667 # pts_time:69.233333 # pts_time:74.366667Split one clip into shots
The API ranked the stretch from 36 s to 92 s as its second clip, so we split that range. A cut less than 0.5 s after the last one is folded into the shot before, so a flash or a quick insert does not become a shot of its own.
shots.py import json import re adaptive = json.load(open("adaptive.json")) scdet = [float(t) for t in re.findall(r"pts_time:([\d.]+)", open("cuts.txt").read())] agree = [t for t in scdet if any(abs(t - a) < 0.5 for a in adaptive)] print(len(adaptive), "adaptive cuts,", len(scdet), "scdet cuts,", len(agree), "found by both") # Split one clip at every cut, and fold slivers under 0.5 s into the shot before CLIP_START, CLIP_END, MIN_SHOT_S = 35.95, 91.56, 0.5 edges = [CLIP_START] for t in scdet: if CLIP_START + MIN_SHOT_S <= t <= CLIP_END - MIN_SHOT_S and t - edges[-1] >= MIN_SHOT_S: edges.append(t) edges.append(CLIP_END) shots = list(zip(edges, edges[1:])) print(len(shots), "shots") for start, end in shots: print(f"{start:6.2f} {end:6.2f}") # $ python shots.py # 61 adaptive cuts, 61 scdet cuts, 61 found by both # 9 shots # 35.95 38.33 # 38.33 40.77 # 40.77 45.67 # 45.67 62.87 # 62.87 69.23 # 69.23 74.37 # 74.37 85.10 # 85.10 88.43 # 88.43 91.56
Or send one request
Send the stream with aspect: "9:16" and reframe: "auto". The engine runs scdet at threshold 8 on the same 640 px decode it uses for faces, drops cuts closer than 0.5 s to the last one, splits each clip into shots and restarts the crop in each.
It returned 3 clips from the 10-minute stream. Clip 2 is the range we split by hand above, and the rendered clip holds the same 8 cuts. At $0.03 per source minute, billed per second, the stream costs about $0.30. See pricing.
curl https://api.clipsdk.com/v1/clips \
-H "Authorization: Bearer $CLIPSDK_KEY" \
-H "Content-Type: application/json" \
-d '{
"video_url": "https://api.clipsdk.com/f/lib/demo/silicon-valley-live-stream.mp4",
"clips": 3,
"aspect": "9:16",
"reframe": "auto"
}'Why a reframe has to restart at every cut
A 9:16 crop that follows a face is smoothed so it does not shake. Smooth it across a hard cut and the crop eases from where the face was in the last shot to where it is in the next, a pan the camera never made. Viewers read it as a mistake.
So the camera path starts fresh in each shot and snaps to the new face on the first frame. The clipsdk engine splits each clip into shots at scene cuts and never pans across one; a cut or a speaker switch is always a hard cut in the output.
Hard cuts are the easy part
On this stream both detectors found 61 cuts and agreed on every one, because a studio show cuts cleanly between cameras. Fades and dissolves are harder: they spread the change over many frames, so no single frame pair may pass the threshold.
PySceneDetect's adaptive detector helps with camera moves, and its ThresholdDetector handles fades to black. The clipsdk engine adds its own check, looking for a big change of a coarse 4x4 colour grid across about a second, and only splits there when both shots last at least 1 s.
ffmpeg's select filter also has a scene score from 0 to 1. Its docs use select='gt(scene,0.4)' to build a mosaic of scene thumbnails and call 0.3 to 0.5 a sane range. That is handy for thumbnails, but scdet gives you the cut times directly.
Questions, answered
What threshold should I use for ffmpeg scdet?
The ffmpeg docs call 8 to 14 good values and default to 10. The clipsdk engine uses 8 on a 640 px copy, which found the same cuts as PySceneDetect on the studio stream above.
Is PySceneDetect better than ffmpeg for cut detection?
On this studio stream they agreed on all 61 cuts. PySceneDetect gives you several detectors and a Python list of scenes; scdet fits into an ffmpeg graph you already run, such as the decode that feeds your face detector.
Why does my vertical crop slide at a cut?
The camera path is smoothed across the cut, so it eases from the old face position to the new one. Split the path at every cut and snap to the face at the start of each shot.
Does the clipsdk API return the cut times?
No. Each clip comes back rendered, with its crop already split at the cuts. The result lists clips, scores, times, transcripts and files.
Keep reading
- Speaker reframe: 16:9 to 9:16 that follows whoever is talkingReframe wide video to 9:16 (1080x1920), 1:1, 4:5 or 16:9. Auto mode tracks faces and cuts to whoever is talking; center and none are one field away. $0.03 per source minute.
- Turn hours of stream VOD into ranked highlight clipsUpload a stream VOD of up to 3 hours and get up to 20 ranked highlight clips with captions. A full 3 hour VOD costs ~$5.40, and longer streams split into two jobs.
- Face tracking crop from 16:9 to 9:16Track a face with OpenCV YuNet and move an ffmpeg crop with sendcmd to turn a 1920x1080 lesson into a 1080x1920 vertical clip.
- Active speaker detection in PythonFind who is talking in a two-person shot on a CPU with YuNet landmarks and mouth motion, then check it against the transcript.
- TikTok safe zonesTikTok, Reels and YouTube safe zones for captions in pixels at 1080x1920, measured from TikTok's own template, with a script that checks your clips.
- Whisper word timestampsWord-level times from faster-whisper, whisper.cpp, WhisperX and openai-whisper, cut to one clip and paged into karaoke captions.
Sources
- PySceneDetect detectors, checked 7 November 2026
- AdaptiveDetector source, v0.7.2, checked 7 November 2026
- scenedetect on PyPI, checked 7 November 2026
- FFmpeg scdet filter, checked 7 November 2026
- FFmpeg select filter, checked 7 November 2026
Start free
Your back catalog is full of shorts
Your first $1 is free: 33 minutes of video, every clip included up to 1080p60 and 20 Mb/s.
