name: cut-talking-takes description: >- Cut raw talking-head video (retakes of the same lines, OR a full walkthrough of a whole script) into a tight, modern jump-cut assembly — transcribe locally, drop dead footage/fumbles/false-starts/redundant re-explanations, keep the clean takes, and preserve 10-bit HDR color. Use when the user drops a .MOV/.mp4 of intro/script takes and asks to "cut the dead footage", "put together the best takes", or assemble a tight cut.
cut-talking-takes
Raw take footage = one locked-off camera, the same line(s) attempted many times, with warmups, throat-clears, false starts, and long resets between. Goal: a tight jump-cut of only the clean, complete takes, with all dead air gone.
takecut.py (in this dir) does the deterministic parts. Picking which takes are
clean/best is editorial — that's the agent's job, done by reading the take map.
Two footage shapes (tell them apart first)
- Intro retakes — the same one or two lines attempted ~20×. Group by beat, take the last clean take of each, butt-join. Offer both a selects-reel (all clean takes) and a best cut (one take per beat).
- Full-script walkthrough — one pass of the whole video, hook → build → outro, mostly
delivered continuously with retake clusters at the hook and the outro and the
occasional restart mid-body. Here: keep the continuous good run,
apply "last clean take" only at the retake clusters, and cut the redundant
re-explanations — people often explain the same beat twice (a detailed pass and a
tighter recap, or restate "wrap it in a UI" two ways). Keep the clearer/more-complete one,
drop the dupe. Deliver one
full-cut.mov. (If several sources cover the same script, offer to compare and pick a master — the latest take is usually the tightest.)
Hard-won facts (don't relearn these)
- Cut points come from energy, not words. whisper word-end times drift up to
~1s; ffmpeg
silencedetectis the truth. Snap every in/out into the surrounding silence so no consonants clip.takecut.pydoes this (-38dB, 0.30s). - iPhone footage is 10-bit HEVC, BT.2020, HLG (
arib-std-b67) HDR. You MUST carry the color tags or the cut looks washed out / dark. takecut.pyencodes withlibx265.hevc_videotoolboxdoes open 10-bit HLG/BT.2020 sessions on this Mac (verified 2026-09-02 at 4K), so the software encoder is a script choice, not a hardware limit — a candidate speed-up for the script, not a rule to carry.prores_videotoolboxworks but is ~193 Mbps.- Map only
[v][a]— iPhone .MOV carries extra data streams that break naive concat. - "Best take" means on-voice: dry, concise, no hype. If the project keeps a voice or positioning doc, read it before picking.
Workflow
-
Analyze (transcribe + take map):
python3 ~/.claude/skills/cut-talking-takes/takecut.py analyze /path/clip.MOVPrints a table
Tn IN OUT dur textand writes<clip>_takes/takes.json. Warmups/restarts are obvious (same phrase repeated; very short fragments). -
Pick the clean takes. A take is clean = complete thought, no false start, no repeated word, no mid-sentence throat-clear, no trailing flub. Group the takes by which line/"beat" they are (the footage is usually the same few sentences attempted over and over).
- DEFAULT SELECTION RULE: take the LAST clean/complete take of each beat — that's almost always the one they're happy with. Walk the beats in order, pick the final complete attempt of each, and string them in narrative order.
- Exceptions to "last": if the last take starts mid-sentence / breaks a referent, or is a redundant terse recap of a beat already delivered better earlier, prefer the clean complete earlier take (or splice an anchor in front). Last is the default, not a law.
- Truncated text ≠ incomplete take.
large-v3-turbomakes thetakes.jsonlabels reliable, but a label can still look clipped where whisper timestamp drift cuts the window text — the window in/out are accurate and usually already contain the whole sentence. Before discarding a take as "trailing off", re-transcribe its exact window — it's usually complete:ffmpeg -ss A -to B -i clip -ar 16000 -ac 1 x.wav && whisper-cli -m MODEL -f x.wav -nt. (sentences.srtis fine for reading content, but its timestamps drift — never cut on them.) - Trailing/leading flub or a dangling pronoun? Trim with an explicit in/out: find
the gap with a fine pass
ffmpeg -ss A -to B -i clip -af silencedetect=noise=-34dB:d=0.12 -f null -and cut inside it. (e.g. a take that opens "I used it…" needs the prior "I used this framework called X" take spliced in front so "it" has a referent.) - Common trim targets: a leading throat-clear or false start ("And then you can also give it some—"), a trailing connector ("…and then it's working. So yeah,"), doubled CTA tails ("give this a shot… check this, give this a shot"), and stray edge blips — when a window's duration is much longer than its content, there's often a false-start syllable at the head or a mumble at the tail; cut it off.
- Two silence thresholds:
analyzegroups takes at-38dB:0.30; for fine trim edges inside a take use-34dB:0.12(catches word/sentence gaps the coarse pass merges).
-
QC framing (optional but cheap): extract a mid-frame per candidate take, tonemap for viewing, and tile a contact sheet to confirm eye-contact/framing:
-vf "zscale=t=linear:npl=100,tonemap=hable,zscale=p=bt709:t=bt709:m=bt709,format=yuv420p,scale=303:540". -
Render the assembly (tight butt-cuts, HDR preserved):
# by explicit seconds (use for flub trims / custom order): python3 .../takecut.py render /path/clip.MOV --segments "74.1-80.9,372.06-382.77" --out best-intro.mov # or by take id from the map: python3 .../takecut.py render /path/clip.MOV --workdir /path/clip_takes --keep T3,T8 --out reel.movDefault deliverables to offer: a selects-reel (all clean takes, chronological) and a best cut (single strongest take of each line). Always write a
TAKES.mdnext to the outputs — final wording, per-beat source timecodes, what was dropped and why, offered swaps — the user uses it to request swaps.Tight modern pacing —
--tighten. By default render only tightens between segments; the speaker's pauses inside a kept span stay verbatim. Fine for intro retakes (short spans), but it leaves the long full-video spans loose. Add--tightento cap every pause inside each span at--max-pauseseconds (default 0.30): pauses ≤ that pass through (natural rhythm kept), longer dead gaps collapse to it. Cuts land inside detected silence, so no words clip and no clicks.python3 .../takecut.py render clip.MOV --segments "..." --tighten --out full-cut-tight.mov python3 .../takecut.py render clip.MOV --segments "..." --tighten --max-pause 0.22 --out tighter.mov--max-pauseguide: 0.30 natural-but-tight (default), 0.20–0.25 snappy/aggressive, 0.38–0.42 relaxed (only kills the long dead air). It's a uniform cap (no special sentence-end allowance), so below ~0.18 starts to sound breathless.- Locked-off camera makes the micro jump-cuts read as intentional; jumpier on handheld.
- Worth it mainly on full-video passes; intro retakes barely change. Keep both the natural and tightened renders so the user can compare.
-
Verify end-to-end: re-transcribe the finished cut and confirm every line survived complete, in order, and flubs are gone.
- False-alarm watch: the full-cut transcription occasionally drops a clause right after a hard cut (whisper artifact at the boundary) even though the audio is fine. If a line looks missing, re-transcribe just that tail/segment (or the source window) before re-cutting — the audio is almost always present.
Output location
Put outputs in a new folder next to the source (e.g. ~/Downloads/<slug>-cut/),
not in the Obsidian vault (too large). Outputs are 10-bit HEVC .mov, ~6 Mbps,
play in QuickTime and drop straight into an editor.
Notes
- Model:
ggml-large-v3-turbo(~1.6 GB). This machine keeps whisper models in~/Models/whisper.cpp/(shared with footage-index);takecut.pylooks there first, then~/.cache/whisper-cpp/, and downloads into~/Models/whisper.cpp/if neither has it.WHISPER_MODEL=<path>overrides the search.ggml-small.en.binin the same dir trades label accuracy for speed. Needsbrew install whisper-cpp+ffmpeg. - Transcribes once (word-level); the readable
sentences.srtis rebuilt from those words in-code, not a second decode. - Tighter cuts: lower
--head/--tailin analyze (defaults 0.08 / 0.12s of breath). - Non-HDR / 8-bit sources are handled too — color flags adapt to the source tags.
- Dolby Vision: iPhone clips carry a DOVI profile-8.4 RPU; libx265 drops the RPU but keeps the HLG/BT.2020 base layer, so it still looks correct. Don't panic about "losing DV".
- Render time:
renderdecodes the whole source once per run, so a 7-min clip takes ~3–5 min. Run it in the background and poll.
