What does explainer do?
A spec file lists the scenes; the scripts capture the material, voice the narration, build one HyperFrames sub-composition per scene, and verify everything before it ships.
1. understand read the code / docs / app the doubt is about
2. capture scripts/capture.py screenshots, element boxes, recordings, redaction
3. spec you write explainer.json (scenes + narration + claims with sources)
4. verify facts scripts/verify.py facts (gate: nothing is voiced until it passes)
5. build scripts/build_explainer.py (voice, captions, scenes; no character by default)
6. verify scripts/verify.py all (assets, build, render, audio) - loop until clean
7. render scripts/render.py
Paths are relative to this package's root: the folder that holds .claude-plugin/, two levels above
this file (resolve symlinks first). Requires the public HyperFrames skills (hyperframes,
hyperframes-cli; hyperframes-registry and media-use for extras). Read hyperframes once before
editing generated compositions by hand.
0. Preflight (always first)
Run python3 scripts/setup.py doctor (add --json to parse it). It lists every dependency, what it is
for, and the exact install command for this machine; it exits 1 when a required item is missing.
- Required missing (ffmpeg, Node.js 18+, the HyperFrames skills, and the ElevenLabs key or
saywhen that voice is configured): tell the user in one line what each one is for and propose the printed command. Run it only after the user says yes. Never type or ask for an API key yourself: tell the user to exportELEVENLABS_API_KEYin their shell. - Optional missing: offer them once and say what each unlocks. Playwright gives web captures with
element zoom boxes and full-page shots, tesseract (OCR) gives the secrets scan on screenshots, and
ASR (mlx-whisper / openai-whisper) gives the audio check and exact word timings. Skip them if the
user declines; the matching checks then become manual
reviewsteps. - HyperFrames skills (
hyperframes,hyperframes-cli,hyperframes-registry,media-use): if missing, recommendnpx hyperframes skills. Readhyperframes(andhyperframes-clifor render problems) before building or editing any composition.
Setup (first run, or when the user wants to change something)
python3 scripts/setup.py show prints the active config. If there is none, interview the user (one
short question at a time, offer the default) and write it:
| Question | Flag | Options |
|---|---|---|
| Which parts? | --features | explainer alone (no character file needed), or character,explainer to make a presenter possible |
| Narration language | --language | ISO code (en, es, ...): picks a clear say voice and tells the ASR what to expect |
| Narration voice | --narration-voice | voice (the configured provider), say (free, macOS), none (captions only) |
| Voice provider | --voice | elevenlabs (their ELEVENLABS_API_KEY from env + --voice-id), say, none |
| Credit guards | --max-chars, --max-seconds, --daily-credit-cap | the user's numbers |
| Render | --render | local, cloud, preview |
| Size | --size | 1080x1920 (default), 1920x1080, 1080x1080 |
python3 scripts/setup.py init --features explainer --voice say --render local --yes
init runs the doctor first and offers the same installs. If a config exists it asks before overwriting
(pass --force when running non-interactively). setup.py voices --lang <code> lists macOS voices
for a language and the user's ElevenLabs voices; --say-voice "<name>" picks one. Never ask for,
print or write API keys.
1. Understand, then script
- Get the facts from the source first: read the code, PR, doc or app. A wrong explainer is worse than none. Show the shape of the thing before the details, walk one real case through it (actual names, actual values), and keep each scene to a single idea.
- Shape (≤
styles.explainer.max_seconds): 3-5 scenes of about 6-12 s each and a closing title. TTS reads about 2.4 words per second: keep a scene's narration under ~25 words.
Choose a style for this video
The package ships only neutral defaults. Pick a style per video from the topic, the audience and
the user's brand or preferences, and ask when it matters (a brand, a public channel, a series).
Do not repeat the previous video's look by habit. Put it in the spec's style block:
| Key | Values |
|---|---|
| palette | background, panel, ink, muted, accent, highlight (CSS colours; keep text contrast ≥ 4.5:1) |
| typography | font, mono (HyperFrames bundled fonts), title_scale (0.6-1.6) |
| layout | align (left, center), radius (0-60), background_style (solid, gradient) |
| motion | motion (calm, normal, lively): durations and easing of every move |
| captions | caption_style (box, outline, plain, none), caption_position (bottom, top) |
A brand kit in the config (same keys) overrides your choice key by key. The presenter's set
follows the video's background unless its block has its own style (see talking-clip).
2. Capture the material
python3 scripts/capture.py url http://localhost:3000/login --name login
python3 scripts/capture.py url http://localhost:3000/login --name login --box "#submit" # needs Playwright
python3 scripts/capture.py screen --name settings --interactive
python3 scripts/capture.py record --name signup --seconds 12 # macOS
python3 scripts/capture.py add --name diagram --file ./docs/arch.png
python3 scripts/capture.py redact --name login --box 820,410,360,48 # or --blur
python3 scripts/capture.py list
urluses Playwright when installed (required for--boxand--full-page), else headless Chrome. Without Playwright, give focus boxes as pixel rects[x, y, w, h]read from the PNG (twice the CSS pixels at the default scale). If Playwright lives in a virtualenv, run the scripts with that interpreter (.venv/bin/python scripts/capture.py ...).--boxrecords an element's box in image pixels so a scene can zoom straight to it. Default--scale 2keeps text sharp when zoomed.- Capture the smallest region that tells the story; a full desktop shrunk into a phone frame is unreadable. Use focus boxes to zoom.
- A whole website as a project instead of single shots:
npx hyperframes capture <url>(hyperframes-cli).
3. Write the spec
examples/explainer/explainer.json is a complete, working example. Put the spec at the root of the
material: code files and captures it references must be inside the spec's folder. Scene types:
| type | needs | what it shows |
|---|---|---|
screen | capture, optional focus: [{"box": "#sel" or [x,y,w,h], "label": "...", "at": s}] | the screenshot, then a camera move to each box with a highlight and a label |
recording | capture (a video), optional focus | the screen recording, muted, same camera and labels |
code | file, lines: "a-b" (≤ 24, lines ≤ ~49 chars in vertical), highlight: [{"lines": [a, b], "at": s}] | the code on a card, lines appearing, bands on the key lines |
diagram | nodes: [{"id","label","sub"?,"highlight"?}], edges: [[a, b]], optional flow: [ids] | boxes and arrows building up, a token travelling along flow |
title | text, optional sub, dur | a closing or section card |
Motion, on any scene (optional; colours and timing come from the style, so vary them per video like the rest of the look):
| Key | What |
|---|---|
callouts: [{"type": "circle" or "arrow", "box": [x,y,w,h] or "#sel", "at": s, "note": "..."}] | hand-drawn loop or arrow that draws on; image pixels on screen/recording scenes, frame pixels elsewhere |
kinetic: [{"text", "at", "dur", "y"?, "from"?}] | words slamming in for emphasis; use sparingly (one or two per video) |
counter: {"to", "from"?, "at", "dur", "prefix"?, "suffix"?, "label"?} | a count-up that lands on the exact value |
top-level transition: "wipe" or "fade" | a transition at every cut (default none) |
They follow the registry items count-up, caption-kinetic-slam, hw-callout-circle and
hw-arrow (see /hyperframes-registry). style.hand_font: "Caveat" fetches that handwriting font
with npx hyperframes add. Do not make every video use every feature; pick what the story needs.
Every scene with narration must carry claims: each technical statement it makes, with a
source (path:lines relative to the spec, capture:<name>, https://..., doc:<name>) and
status: "supported" once you checked it against that source. "claims": [] means the narration
makes no technical claim. Unsupported claims are fixed or cut, never voiced.
No character by default. An explainer is narration, captions and visuals. Add the character as
presenter only when the user asks for it, or when you judge it adds value (a series with a host, a
friendly onboarding) and you say so to the user with the reason. Then add a presenter block
(intro / outro, each {"text", "plan"?, "mood"?, "style"?, "claims"}; plans follow the
talking-clip format) and build with --presenter. It also needs features.character. Presenter
scenes get captions too (unless the spec sets "captions": false). examples/explainer/explainer-with-presenter.json is
the example.
4-5. Facts gate, then build
python3 scripts/verify.py facts --spec explainer.json
python3 scripts/build_explainer.py --spec explainer.json --project out/explainer --estimate
python3 scripts/build_explainer.py --spec explainer.json --project out/explainer [--confirm] [--presenter]
--estimate prints length and ElevenLabs credits; tell the user before spending, then build with
--confirm. The builder also runs the facts gate and stops if it fails. Captions show the script's
own words (case, punctuation, numerals) timed from ElevenLabs' alignment, ASR, or an estimate.
6. Verify (required loop)
python3 scripts/render.py out/explainer --out out/explainer.mp4 --quality draft
python3 scripts/verify.py all --project out/explainer --spec explainer.json --video out/explainer.mp4
| Stage | Passes when | If it fails |
|---|---|---|
| facts | every narrated claim has an existing source and status supported | check the source; fix or cut the claim |
| assets | captures and code are legible at the output size; OCR (tesseract) / text scan finds no emails, keys, tokens or card numbers | add focus boxes or recapture smaller; capture.py redact; replace secrets in code with placeholders |
| build | npx hyperframes check --json has no errors or warnings (except verify.ignore_rules) | fix the spec or the generated composition |
| render | you looked at the contact sheet (start, every scene change, mid-scene, end): no overflow, overlap, cut-off or unreadable text; visuals match the narration | fix, rebuild, re-render |
| audio | ASR of the rendered audio matches the script (verify.min_asr_similarity) and the duration is within the limit | regenerate the voice or shorten |
How to drive the loop:
- Run
verify.py all. Each run is one iteration (counted in<project>/.verify/state.json). renderalways comes back asreviewfirst: open the contact sheets it lists, check every frame, then record what you saw:python3 scripts/verify.py mark render pass --project out/explainer --note "<what you checked>"(orfail, then fix). The mark is tied to those exact files; any rebuild invalidates it.- Without OCR or ASR installed,
assets/audiobecomereviewtoo: check by eye or ear and say so in the report. - Fix what failed, rebuild or re-render, and run
verify.py allagain. Exit 0 = ship. - When the count reaches
verify.max_iterations(default 3) it exits 2: stop and tell the user what still fails. Do not ship silently. Delete.verify/state.jsonto start a fresh count after a major change.
VERIFY.md shows numbers: OCR images scanned and hits, ASR similarity with words heard and
expected, duration against the limit. Quote them when you report. Scenes are lengthened
automatically so the last caption stays readable; if a line_unread warning remains, give that
scene more min_dur.
Keep the user's normal HOME when running the scripts: a temporary HOME makes npx, Playwright and
whisper download everything again, and breaks gh/npx auth.
Audio mismatches on numerals, acronyms or foreign words: set language in the config and write
those words in the narration the way they should sound ("fifteen", "A P I"). VERIFY.md in the
project is the checklist report; include its summary when you hand the video over.
7. Render
python3 scripts/render.py out/explainer --out out/explainer.mp4 uses render.backend
(local, cloud or preview); render.max_mb re-encodes to fit an upload limit. Never commit mp4
or wav files.