Hypernatural

TechniqueBy HypernaturalVersion 1, Oct 21, 2026

ffmpeg-scene-cuts

Find a video's shot boundaries with ffmpeg's scene score: hard cuts, and soft cuts or dissolves that a normal threshold misses, then a shot table with each shot's first and last frame and a contact sheet to confirm them. Use to split a source spot or a finished cut into shots before recreating, reframing, scripting or titling it.

SKILL.md

Find scene cuts

Give it a video. You get the times and frames of its cuts, any softer transitions worth a look, a shot table (first and last frame per shot) and a contact sheet of every shot's opening frame. The scene score is ffmpeg's measure of how different a frame is from the one before it. Hard cuts score high. Dissolves and cuts between similar shots score low and need a low threshold. Read the scores. A fixed threshold alone is not enough.

Inputs

The video, and its frame rate and exact frame count from ffprobe-inspect-media. The shot table is in the video's own frames.

Run it

1. Score the frames. Every frame's score is written to a text file in the project folder. Scaling to 320 px first makes it fast and ignores fine noise:

exec ffmpeg -hide_banner -v error -i <video> -an -vf "scale=320:-2,select='gte(scene,0)',metadata=print:file=scores.txt" -f null -

When you only need the hard cuts, print just those:

exec ffmpeg -hide_banner -v error -i <video> -an -vf "scale=320:-2,select='gt(scene,0.15)',metadata=print:file=-" -f null -

Each hit is a pts_time:<seconds> line (the first frame of the new shot) followed by lavfi.scene_score=<score>. To scan only a window, put -ss <start> -t <length> before -i. The times then start at 0, so add the start back.

2. Read the scores into a shot table. Write references/scene-scores.md's script into the project folder as scene_scores.py and run it:

exec python scene_scores.py scores.txt --fps 24 --frames 484
exec python scene_scores.py scores.txt --fps 24 --frames 484 --hard 0.3

It prints cuts (over --hard, default 0.15), soft (scores between 0.015 and --hard, the dissolve candidates) and shots. Cuts under 0.4 s apart count once, as a flash.

3. Look. Make a contact sheet of every shot's opening frame and check it:

exec ffmpeg -hide_banner -v error -i <video> -an -vf "select='eq(n\,0)+gt(scene\,0.15)',scale=320:-2,tile=6x4:padding=4" -fps_mode vfr cuts_sheet_%02d.png

Look at the soft candidates the same way, with frames taken either side of them.

Reading the scores

ScoreWhat it usually is
0.4–0.8A hard cut. Real cuts on a 24 fps spot all scored above ~0.39.
0.15–0.4Usually a cut between similar shots, but sometimes fast motion inside a shot. One 0.15 hit on a spot was motion, not a cut. Look at it.
0.02–0.06Soft cuts and dissolves. A scene > 0.25 pass misses them.
~0.03–0.05Also a camera move or a flicker on a screen in frame. These overlap with dissolves, so only looking at the frames separates them.
  • 0.15 is the threshold for cutting a short spot into shots. Confirm every hit on the sheet.
  • 0.3 is the threshold for cutting a long video into shots for a script. Fewer false hits, and anything it misses usually doesn't change the story.
  • 0.015, read by eye, is for a finished cut where titles or graphics must land on every shot change, dissolves included.

Then

  • Pull the first and last stills of a shot by frame number: exec ffmpeg -v error -i <video> -vf "select=eq(n\,<frame>)" -frames:v 1 <shot>_first.png.
  • Write the shot table (shot, first frame, last frame) into the job's notes, so every later step uses the same frame numbers.
  • Cut exact-length shot clips with ffmpeg-frame-exact-trim, not with -t from these times.

Check it

The shots run without gaps from frame 0 to the last frame. Their lengths add up to the frame count. Every shot on the contact sheet is a different picture from the one before.

Go make something cool.

Install the app, open your agent, describe the video. That's the whole onboarding.

Sticker HypernaturalSticker BannerSticker TVBoy