Video craft
Two jobs, one standard. Edit footage someone recorded, or make a film from a brand kit and copy. Either way the work is planned before it is rendered, rendered by scripts that are exact, and checked by measurement before anyone is told it is done.
What makes a video look finished, in order:
- Structure. A hook in the first 3 seconds, one idea, nothing that does not earn its time.
- Rhythm. Cuts and moves land on speech and on the beat; nothing is dead, nothing rushes.
- Craft details. Air around every cut, captions that read, motion that decelerates into place, sound that is loud enough and never clips.
- Proof.
qa.pypasses and the contact sheet has been looked at.
Quick path
| The ask | Do |
|---|---|
| "Tighten / clean up this recording" | transcribe → plan_cuts → review EDL → render → qa |
| "Make a Short/Reel/TikTok from this" | as above, plus captions.py --style punch, render.py --preset shorts --fit crop (or blur for screen recordings) |
| "Add captions" | transcribe.py → captions.py → render.py --captions (or ship the .srt) |
| "Add music" | render.py --music bed.mp3 (ducked under speech automatically) |
| "Make it loud enough / fix the audio" | render.py --voice --denoise, target from platforms.md |
| "Brand intro / logo reveal / sting" | copy films/brand-sting, fill the brand kit, stills → draft → master → qa |
| "Launch video / promo / ad" | copy films/launch, write the copy, pick formats, stills → draft → master → qa |
| "Check this video" | qa.py video.mp4 --sheet sheet.jpg [--safe] |
| Project already on Remotion | references/remotion.md |
Requirements
ffmpeg 6+ and Python 3.9+ for editing. Transcription: pip install faster-whisper (or
openai-whisper, or whisper.cpp). Films: Node 18+ and Playwright
(npm i -D playwright && npx playwright install chromium). Captions are burned with libass
when ffmpeg has it, else drawn with Pillow (pip install pillow). Run python3 scripts/doctor.py
to see what is installed.
All scripts take --help. Work in a folder per job: work/ for intermediates, out/ for
deliverables.
Editing footage
python3 scripts/transcribe.py raw.mp4 -o work/words.json # word-level transcript
python3 scripts/plan_cuts.py raw.mp4 --words work/words.json -o work/edl.json
python3 scripts/captions.py work/words.json --edl work/edl.json --style clean -o work/cap
python3 scripts/render.py work/edl.json -o out/final.mp4 --preset youtube \
--captions work/cap.cues.json --music bed.mp3 --voice --zoom auto
python3 scripts/qa.py out/final.mp4 --edl work/edl.json --sheet out/sheet.jpg \
--words work/words.json --words-check
- Read the transcript first and decide the structure: what goes entirely (section cuts
read clean; many mid-sentence cuts read nervous), and what the hook is. Put whole-section
decisions into the EDL's
segmentsby hand if needed. plan_cuts.pyremoves dead air (shortened to a breath, not to zero), fillers and abandoned retakes (keeps the last take). Silence is measured per recording, every edge is snapped to real audio, padding is asymmetric (80 ms before, 140 ms after) and never reaches into a removed word. Read its list: emphatic repetition can look like a retake.- Checkpoint with the person on anything long: before/after duration, sections cut, retakes dropped. Their notes become EDL edits.
render.pydoes it all in one ffmpeg graph: frame-exact trims, 8 ms fades at every cut, constant frame rate, punch-ins, reframing, captions, voice polish, ducked music and two-pass loudness.qa.py, then look at the sheet.--words-checkre-transcribes the result and names any word a cut swallowed.
Judgement, numbers and failure modes: references/editing.md. Audio: references/sound.md.
Making films (brand intros, launch spots, ads)
cp -r films/brand-sting my-film && python3 scripts/sfx.py --out my-film/sfx
# edit PARAMS in my-film/index.html (or pass --params '{"name": "...", ...}')
node scripts/render_film.mjs my-film/index.html --stills 0.5,1.6,2.4,3.5 -o work/stills # look at them
node scripts/render_film.mjs my-film/index.html --draft -o work/draft.mp4 # judge rhythm
node scripts/render_film.mjs my-film/index.html --blur 4 -o out/intro.mp4 # master
python3 scripts/qa.py out/intro.mp4 --no-gaps --sheet out/intro.sheet.jpg --safe
- Brand kit first (
references/brand-intro.md): the official logo file, hex colours, font, exact words, register, formats, sound, and a source for every number. Never redraw or recolour a logo; never invent claims, customers or product UI. - Beat sheet before code: each beat with its time, what moves, the ease and the sound. Choose one register (premium, clean, tech, bold, playful) and scale the whole timeline with it.
- Build on
assets/film.js: every pixel is a function oft.Film.tween,Film.spring,Film.stagger,Film.random(seed); no CSS animations, timers orDate.now. Open the HTML in a browser to scrub it in real time. - Stills at every beat, then a draft, then the master. Fix what the stills show before rendering video. Masters render ~2–5 frames a second; drafts are 10× faster.
- Sound is part of the film.
scripts/sfx.pysynthesises the kit (hit, boom, whoosh ×3, riser, pop, tick, chime, bed); cues sit in the film config and are mixed and loudness-set by the renderer. The hit's peak lands on the reveal frame.
Templates: films/brand-sting (3–7 s logo reveal, five registers, 16:9 / 9:16 / 1:1) and
films/launch (15 s hook → problem → reveal → proof → CTA, the three formats, 9:16 inside
the platforms' safe area, *word* emphasis). Use them as starting points; change the
structure when the brief needs it.
Doctrine (timings, easing by role, stagger, type sizes, colour, transitions with a cause,
the generic looks to avoid and the tests that catch them): references/motion.md.
Rules that always apply
- Re-encode every trim. Stream-copy trims snap to keyframes.
- Constant frame rate in everything delivered; H.264 + yuv420p + AAC +
+faststart. - Loudness by measurement: -14 LUFS / ≤ -1 dBTP for web and social, -16 for podcasts.
- Text inside safe areas: on 9:16 nothing in the top 288 px, bottom 672 px or right 192 px (at 1080×1920). Type ≥ 28 px at 1080.
- Honest content: real logos, real numbers with sources, real product UI; say when music, voice or footage is generated.
- Licensed music only. Never rip audio from videos or streaming services.
- Done means checked:
qa.pypasses, the sheet was looked at, and anything that could not be checked is said plainly.
Files
| Path | What |
|---|---|
scripts/transcribe.py | words.json with word times (faster-whisper / whisper / whisper.cpp) |
scripts/plan_cuts.py | EDL: pauses, fillers, retakes removed, with reasons |
scripts/captions.py | SRT, styled ASS and cue JSON on the edited timeline |
scripts/render.py | EDL → finished video (presets, reframe, captions, music, loudness) |
scripts/qa.py | format, length, black, freeze, dead air, loudness, words, contact sheet |
scripts/sfx.py | synthesised sound kit |
scripts/render_film.mjs | HTML film → MP4 (draft, master with motion blur, stills, poster) |
scripts/doctor.py | what is installed and what is missing |
assets/film.js | deterministic motion engine + browser preview with scrubber |
films/ | brand-sting and launch templates |
references/ | editing, motion, brand-intro, sound, platforms, ffmpeg, remotion |