video-director-previs
Make expensive creative decisions at the cheapest stage. This skill owns pre-production: direction, story, script, storyboard, styleframes, animatic, music, SFX, voice, engine choice, QA, review, versions and approval. Engines (HyperFrames, video-shotcraft, Remotion, motion-reel, bang-motion, Diffusion Studio) only build what the owner approved.
Worked example: examples/crumb-previs/ (a made-up bill-splitting app). Its data pack is the demo film in Previs Studio (_shared/previs/studio/films/011-crumb/).
The app never changes per film. Previs Studio is one fixed React + TypeScript app (source _shared/previs/app/, see its README). npm run build there type-checks, bundles and inlines it into _shared/previs/studio/index.html and _shared/previs/page/index.html (template); add a target in scripts/inline.mjs for each per-film page. npm run smoke drives the built page in real Chrome at desktop, tablet, phone and full screen (screenshots with --out). Never hand-edit the built index.html.
What the owner gets: Watch (video-style player with true full screen, hover-scrub preview, end card; comment mode that names the smallest part under the pointer, scroll for bigger or smaller, drag a box around several parts, pen and arrow, drag across the shots row for a time range; sound lanes with waveforms, beats, the duck curve, solo/mute/volume and draggable SFX that snap to the beat), choices that replay their moment in place with sound, side-by-side compare, hold-to-hear-the-original, Storyboard (cards that play their shot in place, Play all, per-shot choices, a shot sheet), Plan (chief director, story, director team, look, sound, assets, production, checks, versions with "ask to undo", spec export), a guided review, Ask the director, ⌘K search, undo/redo, light and dark themes, tooltips on every control. Films arrive only as data packs.
Two homes, same app
- Previs Studio (all films):
_shared/previs/studio/. Add a film = addfilms/<id>/previs.json+films/<id>/audio/...and a row infilms.json. Locally:python3 -m http.server --directory _shared/previs/studio(or theprevis-studioentry in.claude/launch.json). - Per-film page:
projects/NNN/previs/page/holds a copy of the sameindex.html(title<Product> Previs),films.jsonwith one row whosepathis"",previs.jsonandaudio/. - As a claude.ai Artifact (Claude only): publish the page with
capabilities: {"db": {}, "user": {}, "sample": {}, "comments": {}, "downloads": true}.dbstores notes and picks,samplepowers Ask the director,commentscarries "Send to Claude Code",downloadsthe spec export. Without them the page still works and saves in the browser. - Database paths: comments (change requests pinned to the film; the collection keeps the old name)
films/<id>/notes(kindframe, region, range, sound, shot, revert or request;target,targets,box,strokes,quickchips,t/t2,picksandversionat the time); picks and sound tweaksfilms/<id>/state/picks(tweaks:<scene>.<sfx>{dt, db},lane.vo|music|sfx{db},mix.duck{on}); added optionsfilms/<id>/state/custom; approvalfilms/<id>/state/approval; Claude's statusfilms/<id>/state/claude; activityfilms/<id>/activity; notes (free thoughts about the whole film: read them as background, never as tasks)films/<id>/pad.
Libraries: more than three options on demand
Every decision ships three director options; D['libraries'] adds more the reviewer (or the Ask tab) can pull in: direction (looks: token sets + fonts_url), music (CC0 beds with bpm, phase, tags, level), motion (ease, spring {f,d} or bezier), pacing, camera, transition, sfx (every sound in the film) and voice (Fish voices: dir, durs, texts for other languages, intro, sample). The global voice decision picks the narrator; a voice resolves each line to <dir><scene>-<A|B|C>.mp3, and a voice with needs and no dir plays as captions until it is recorded. Added options live in the page database, are undoable, and go into the approved spec. Build libraries with examples/crumb-previs/libraries.py as the model:
- Music: search Freesound CC0 (
freesound.py search ... --min-dur 20),freesound.py get, thenpython3 _shared/previs/music_lib.py <page>/audio/music/lib <oggs>(beat-aligned 24 s excerpts, measured BPM); write tags and a why per bed; ledger rows in_shared/ASSETS.md. - Voices: pick public Fish voices with
search_voices(no real-person or character clones), list them invoices/voices.tsv, writevoices/lines-<lang>.txt, runvoices/make_voices.sh(free model), then spot-check takes with whisper-cli and regenerate any that drop words. - The AI director (Ask tab,
lib/director.ts) can also design looks (contrast-checked, allowed Google fonts), motion feels and new line wordings (those need a recording: it files a request note for Claude Code).
Claude Code's side of the notes (do this every round)
The owner sends notes from the page ("Send to Claude Code": a live comment that wakes a watching session, or a copied message). Then:
0. Pull before you change. Claude on claude.ai can answer a "Send to Claude Code" thread by itself, edit the data file and publish a new version. Before building, read the published previs.json, compare it with your local build, and port any change into build_previs.py so the next build keeps it.
- Set
films/<id>/state/claude {state:"reading"|"working"|"publishing"|"idle", at, message, version, open}so the page shows what you are doing. - For each open note:
updateit withclaude: {state:"seen"}when read,{state:"working"}while changing, thenstatus:"done",claude:{state:"done", at, msg}and append{by:"claude", text, at}toreplies(pinif_version). Usestate:"question"(with the question inmsg) when you need the owner,"wontfix"when you recommend keeping it. Notes withkind:"request"are jobs from the Ask tab (record a new line, find a sound);preferpoints at the option the owner likes;intent:"question"wants an answer, not a change. Everyatis the real time: take it fromdate -u +%Y-%m-%dT%H:%M:%SZ, never a guess. - Add lines to
films/<id>/activity({at, by:"claude", kind:"seen"|"applied"|"published"|"question", text, version}): what you read, what you changed, what you published. - Reply in the page comment thread if the owner used Send to Claude Code, then resolve it.
Rules
- One director, no committee. Work through the specialist checklist below yourself, in order. No workflows or sub-agents unless the owner asks (cost rule).
- Infer aggressively; ask only true blockers (product truth, a missing asset, a licence question). Never a long questionnaire before the owner sees something.
- Three options on every decision, by default. Direction, music, motion feel, pacing, and per scene: transition, camera, voice line, each SFX event. Options must be genuinely different. Mark the director's pick and give each option one plain sentence of why or trade-off. "Nothing" is a valid SFX option.
- Product truth. Every scene is labelled
real_ui,conceptual_ui,conceptual_visualorexternal_footage; anything not real shows a label in the player. - House rules still apply (AGENTS.md, OWNER.md): banned clichés, carries, two text levels, recorded foley, the default voice, licences. The page only plays CC0 or self-made audio; Mixkit (render-only) appears by name only.
- Scale down. A 6 s sting can be one direction card, 3 music options and a 6 s animatic. A 40 s product film gets the full set.
- Locks. Once the owner approves, story, copy, order, timing and picks are locked. A later note changes only its target; list what you preserved.
Specialist checklist (the director covers each, briefly)
Story (hook by 2 s, one state per beat) · script and voice lines (words per second ≤ 3.4) · editorial pacing (vary shot length, rests, not every cut on the beat) · art direction (3 directions as token sets: bg, on-bg, card, on-card, muted, line, paper, on-paper, accent, on-accent, frame, display, ui, mono) · typography · references (borrow / avoid / where used; grammar, never content) · camera (every move has a reason) · motion (purpose, ease, overshoot ≤ 4 % on UI, none on type) · transitions (a simple cut is valid) · assets (existing / new / generated / placeholder, with licence) · music (measured BPM and phase, energy curve, where the drop lands) · SFX (asset, time, trigger, gain, why) · mix (voice leads, duck 8 dB, impact loudest) · engine (chosen, why, alternative, fallback) · QA.
Files (one folder per film)
projects/NNN-name/previs/
build_previs.py # writes page/previs.json; copy examples/crumb-previs/build_previs.py and edit (scenes = HTML styleframe + anim tweens)
vo-lines.txt # 3 wordings per line, for fish_tts.py
page/index.html # built by _shared/previs/app (npm run build; add this page as a target in scripts/inline.mjs)
page/films.json # [{"id": "NNN-name", "name": "Product", "path": ""}]
page/previs.json # the whole plan
page/audio/{music,vo,sfx}/
changes.jsonl # append-only change log, one op per line
approved-video-spec.json # written by _shared/previs/freeze_spec.py after approval
Steps
- Read the brief or one-line ask and the product source (real UI, tokens, copy). Write
BRIEF.mdwith the inferred decisions. - Audio (cheap, local):
- Beds: measure tempo and phase by comb fit over 85–150 BPM on a low-band onset envelope (
AGENTS-tooling.md, "Previs"); cut 24 s excerpts starting on a beat, loudnorm to −18 LUFS:ffmpeg -ss <phase> -t 24 -i bed.wav -af "loudnorm=I=-18:TP=-2,afade=t=out:st=22.5:d=1.5" page/audio/music/A.mp3. - Voice:
python3 _shared/tools/fish_tts.py --lines previs/vo-lines.txt --out-dir previs/vo-wav(free model), then trim silence intopage/audio/vo/sN-{A,B,C}.mp3. - Foley: copy CC0 previews from
_guide/prompt-studio/media/foley/or fetch withfreesound.py.
- Beds: measure tempo and phase by comb fit over 85–150 BPM on a low-band onset envelope (
- Plan in
build_previs.py(see the example for every field):project,summary,chief,story(beats → scenes),directors,assets,gates,pipeline,changes,directions(3),global.music|motion|pacing|voice(3 each),engine,references,style_rules,audio_notes,qa,versions,film_css(all colours via tokens, classes prefixedf-),fonts_url, andscenes[]withhtml,anim(tweens{s, t:[a,b], f, to, e}on x, y, s, sx, sy, r, o, clip, w, blur;{s, set, at, until}class toggles;{s, count:[from,to], t, p, d}counters;e:'m'follows the Motion pick),decisions(transition types cut, push, slide, zoom, iris; camera{s,x,y,origin}; vo{text, src, dur, at, copy}),sfx(atmay be a list for runs),key(the storyboard still's moment). Give every element the owner might point at a plaindata-name("Receipt total", "Request button"). - Check locally: serve the page folder, look at a still of every scene and one mid-transition, switch all three directions once, confirm no console errors. One pass, no loop.
- Share the page (locally, or as a claude.ai Artifact with the capabilities above) and add the film to Previs Studio. Give the owner the link and a five-line summary. Stop for review.
- Review loop (each round is one republish):
- Read notes, picks and approval.
- For each open note: change only its target, append an op to
changes.jsonl({"v","target","t","change","from","to","reason","preserve":[...]}), then mark the note done with a one-line reply (protocol above). Arevertnote asks to undo a listed change. - Bump
versionsand add the round'schangesinbuild_previs.py, rerun it, copyprevis.jsoninto the Studio copy, republish. The player shows a "what changed" banner and the Plan lists each change with an Undo request.
- Approval: when
state/approvalhasapproved: true(it carriespicks,tweaksandcustom), save it, runpython3 _shared/previs/freeze_spec.py projects/NNN-name/previs --approval <file>(it resolves added options and the voice, and listsneeds_before_buildsuch as unrecorded lines), writeSHOTLIST.mdfrom the spec, then hand off to the engine skill named in the spec. The engine builds exactly the spec (polish allowed, decisions not).
Cost
Solo work only. A 20 s previs is a few hours of one session (roughly $30–60 API-equivalent), with Fish on the free model and no renders. The production render and its checks come after approval.