Skip to content

Commands

Every command is uv run studio <command> <slug>, run from the repository root. <slug> is the folder name under episodes/. uv run studio --help lists them all.

setup

uv run studio setup [--check] [--no-verify] [--no-download]

First-time setup. It reports which tools are installed (ffmpeg, ffprobe and npx are required; uv, Chromium, rclone, git-lfs, the DINPro fonts and a workspace are optional), then asks a few questions and writes .env: an optional ElevenLabs API key (tested online without spending credits) and an optional rclone path for studio archive. It can also download the narration models so the first sync doesn't stall. Pressing Enter skips or keeps the current value, so it is safe to re-run. Keys are typed without echo, never printed back, and saved with owner-only permissions; it refuses to write .env if a git repository doesn't ignore it.

Each missing tool comes with an install hint for your system (winget on Windows, apt or pacman on Linux, brew on macOS). The Install page has the full list. A browser for thumbnails can be Chrome, Chromium or Edge.

--check only prints the report, one ok|missing|optional name hint line per item, and exits 1 if a required tool is missing. It is also what runs when input isn't a terminal, so scripts and coding agents can use it as a health check.

new

uv run studio new <slug> ["Title"]

Scaffolds episodes/<slug>/ from templates/script.md. The slug must be lowercase letters, digits and hyphens. The title defaults to the slug, title-cased. Fails if the episode already exists.

status

uv run studio status <slug>

Prints a table of scenes with template, word count, estimated and actual length, and cues, then a total. The estimate assumes 2.5 words a second plus 1.5s per scene; actual lengths appear after a sync. Scenes without a file yet are flagged.

sync

uv run studio sync <slug> [--scene ID ...]

Narrates (using the cache), aligns words to audio, creates any missing scene files from their templates, rewrites each scene's timing block, copies brand/ into the episode, and writes index.html and timing.json.

--scene re-narrates only the named scenes and reuses cached timings for the rest.

preview

uv run studio preview <slug> [-- extra hyperframes args]

Runs sync, then opens HyperFrames Studio in the browser with the narration. Anything after the slug is passed to hyperframes preview.

check

uv run studio check <slug>

Runs sync, then hyperframes check: lint, layout and contrast audits. Exits non-zero on errors.

render

uv run studio render <slug> [--draft] [--force [ID ...]] [--no-verify]

Runs sync, renders changed scenes, assembles and masters the episode into out/.

Flag Meaning
--draft Fast, lower-quality render to out/<slug>-draft.mp4
--force Re-render every scene
--force ID … Re-render only the named scenes
--no-verify Skip the verify pass that otherwise runs on the finished master

Requires ffmpeg. See Rendering and publishing.

verify

uv run studio verify <slug> [--draft] [--no-speech]

QA of the file you would upload (out/<slug>.mp4), also run at the end of render. It checks the resolution, frame rate and length against timing.json; integrated loudness and true peak against loudness and true_peak; black frames (error) and long frozen video (warning); silences longer than the script's own pauses; that a transcript of the mastered audio matches the script (so the music bed isn't burying the voice, and it lists the words it didn't hear); and that the .srt and .chapters.txt exist with chapters that meet YouTube's rules. Exits 1 if anything is an error; warnings don't fail it.

Flag Meaning
--draft Check out/<slug>-draft.mp4, without the captions and chapters
--no-speech Skip the transcription check, the slowest step

It reads timing.json and the existing render, so it doesn't re-sync: after editing the script, render first.

thumbnail

uv run studio thumbnail <slug>

Screenshots episodes/<slug>/thumbnail/thumbnail.html with headless Chromium to out/<slug>-thumbnail.jpg (1280×720). Creates the HTML from the template on first use.

voices

uv run studio voices <slug> [--scene ID] [--voices SPEC ...] [--speed N]

Synthesises one scene with each candidate voice, using the episode's other settings, and writes loudness-matched samples to out/voice-samples/.

Flag Meaning
--scene Scene to use. Defaults to the first narrated scene
--voices Kokoro voices or blends, such as "bf_emma*0.6 + bf_lily*0.4". With tts: chatterbox, clip paths or default
--speed Override the script's speed (ignored by Chatterbox)

With no --voices, Kokoro auditions six built-in blends, and Chatterbox auditions every brand/voices/*.wav plus default.

music

uv run studio music <slug> --prompt "sombre cello and piano, slow, no percussion" [--variants N] [--out PATH]

Generates an instrumental music bed with ElevenLabs Music and prints the music: line to put in script.md. Run sync first: the bed is sized to the episode's timing.json (length + 2 s). It uses the same ELEVENLABS_API_KEY as narration, but the key needs the music_generation permission, and a paid plan for commercial use (film and TV excluded). Cost is roughly $0.0083 a second, so a five-minute bed is about $2.50. Results are cached by prompt, length and model, so a re-run is free.

Flag Meaning
--prompt Genre, mood, tempo, instruments. Defaults to music_prompt: in the front matter. Don't name artists or songs
--name File name in episodes/<slug>/audio/music/ (default bed)
--length Seconds (3–600). Defaults to the episode length + 2 s; a shorter bed loops
--model music_v1, music_v2 (default) or music_v2_5
--variants Make 1–4 different takes, suffixed -a, -b…, to audition
--out Write here instead, such as brand/music/my-bed.mp3 to share across episodes

Log the track in brand/music/CREDITS.md (source, prompt, date). Check ElevenLabs' music rights for your plan and use before publishing.

archive

uv run studio archive <slug> [--remote gdrive:explainer-masters]

Copies the finished deliverables (out/<slug>.mp4, .srt, .chapters.txt and -thumbnail.jpg) to <remote>/<slug>/ with rclone, so masters live in cloud storage rather than in git. The remote defaults to STUDIO_ARCHIVE in the environment or .env. Set one up once with rclone config (Google Drive, Backblaze B2, S3, Dropbox…). Re-running only uploads files that changed.

HyperFrames directly

The studio drives the HyperFrames CLI at a pinned version (HF_VERSION in tools/studio/common.py; override with STUDIO_HF_VERSION). You can call it yourself, for example to take snapshots:

npx hyperframes snapshot episodes/<slug> --at 12.5,40