Asset preprocessing for HyperFrames compositions — text-to-speech narration (Kokoro), audio/video transcription (Whisper), and background removal for transpa...
---
name: hyperframes-media
description: Asset preprocessing for HyperFrames compositions — text-to-speech narration (Kokoro), audio/video transcription (Whisper), and background removal for transparent overlays (u2net). Use when generating voiceover from text, transcribing speech for captions, removing the background from a video or image to use as a transparent overlay, choosing a TTS voice or whisper model, or chaining these (TTS → transcribe → captions). Each command downloads its own model on first run.
---
# HyperFrames Media Preprocessing
Three CLI commands that produce assets for compositions: `tts` (speech), `transcribe` (timestamps), and `remove-background` (transparent video). Each downloads a model on first run and caches it under `~/.cache/hyperframes/`. Drop the output into the project, then reference it from the composition HTML — see the `hyperframes` skill for the audio/video element conventions.
## Text-to-Speech (`tts`)
Generate speech audio locally with Kokoro-82M. No API key.
```bash
npx hyperframes tts "Text here" --voice af_nova --output narration.wav
npx hyperframes tts script.txt --voice bf_emma --output narration.wav
npx hyperframes tts --list # all 54 voices
```
### Voice Selection
Match voice to content. Default is `af_heart`.
| Content type | Voice | Why |
| ----------------- | --------------------- | ----------------------------- |
| Product demo | `af_heart`/`af_nova` | Warm, professional |
| Tutorial / how-to | `am_adam`/`bf_emma` | Neutral, easy to follow |
| Marketing / promo | `af_sky`/`am_michael` | Energetic or authoritative |
| Documentation | `bf_emma`/`bm_george` | Clear British English, formal |
| Casual / social | `af_heart`/`af_sky` | Approachable, natural |
### Multilingual
Voice IDs encode language in the first letter: `a`=American English, `b`=British English, `e`=Spanish, `f`=French, `h`=Hindi, `i`=Italian, `j`=Japanese, `p`=Brazilian Portuguese, `z`=Mandarin. The CLI auto-detects the phonemizer locale from the prefix — no `--lang` needed when the voice matches the text.
```bash
npx hyperframes tts "La reunión empieza a las nueve" --voice ef_dora --output es.wav
npx hyperframes tts "今日はいい天気ですね" --voice jf_alpha --output ja.wav
```
Use `--lang` only to override auto-detection (stylized accents). Valid codes: `en-us`, `en-gb`, `es`, `fr-fr`, `hi`, `it`, `pt-br`, `ja`, `zh`. Non-English phonemization requires `espeak-ng` system-wide (`brew install espeak-ng` / `apt-get install espeak-ng`).
### Speed
- `0.7-0.8` — tutorial, complex content, accessibility
- `1.0` — natural pace (default)
- `1.1-1.2` — intros, transitions, upbeat content
- `1.5+` — rarely appropriate; test carefully
### Long Scripts
For more than a few paragraphs, write to a `.txt` file and pass the path. Inputs over ~5 minutes of speech may benefit from splitting into segments.
### Requirements
Python 3.8+ with `kokoro-onnx` and `soundfile` (`pip install kokoro-onnx soundfile`). Model downloads on first use (~311 MB + ~27 MB voices, cached in `~/.cache/hyperframes/tts/`).
## Transcription (`transcribe`)
Produce a normalized `transcript.json` with word-level timestamps.
```bash
npx hyperframes transcribe audio.mp3
npx hyperframes transcribe video.mp4 --model small --language es
npx hyperframes transcribe subtitles.srt # import existing
npx hyperframes transcribe subtitles.vtt
npx hyperframes transcribe openai-response.json
```
### Language Rule (Non-Negotiable)
**Never use `.en` models unless the user explicitly states the audio is English.** `.en` models (`small.en`, `medium.en`) **translate** non-English audio into English instead of transcribing it. This silently destroys the original language.
1. Language known and non-English → `--model small --language <code>` (no `.en` suffix)
2. Language known and English → `--model small.en`
3. Language unknown → `--model small` (no `.en`, no `--language`) — whisper auto-detects
**Default model is `small`, not `small.en`.**
### Model Sizes
| Model | Size | Speed | When to use |
| ---------- | ------ | -------- | ------------------------------------- |
| `tiny` | 75 MB | Fastest | Quick previews, testing pipeline |
| `base` | 142 MB | Fast | Short clips, clear audio |
| `small` | 466 MB | Moderate | **Default** — most content |
| `medium` | 1.5 GB | Slow | Important content, noisy audio, music |
| `large-v3` | 3.1 GB | Slowest | Production quality |
Music with vocals: start at `medium` minimum; produced tracks often need manual SRT/VTT import. For caption-quality checks (mandatory after every transcription), the cleaning JS, retry rules, and the OpenAI/Groq API import path, see [hyperframes/references/transcript-guide.md](../hyperframes/references/transcript-guide.md).
### Output Shape
Compositions consume a flat array of word objects. The `id` field (`w0`, `w1`, ...) is added during normalization for stable references in caption overrides; it's optional for backwards compatibility.
```json
[
{ "id": "w0", "text": "Hello", "start": 0.0, "end": 0.5 },
{ "id": "w1", "text": "world.", "start": 0.6, "end": 1.2 }
]
```
## Background Removal (`remove-background`)
Remove the background from a video or image so the subject (typically a person — avatar, presenter, talking head) sits as a transparent overlay in a composition.
```bash
npx hyperframes remove-background subject.mp4 -o transparent.webm # default: VP9 alpha WebM
npx hyperframes remove-background subject.mp4 -o transparent.mov # ProRes 4444 (editing)
npx hyperframes remove-background portrait.jpg -o cutout.png # single-image cutout
npx hyperframes remove-background subject.mp4 -o subject.webm \
--background-output plate.webm # both layers in one pass
npx hyperframes remove-background subject.mp4 -o transparent.webm --device cpu
npx hyperframes remove-background --info # detected providers
```
Uses `u2net_human_seg` (MIT). First run downloads ~168 MB of weights to `~/.cache/hyperframes/background-removal/models/`.
### Layer separation (`--background-output`)
Pass `--background-output` (or `-b`) to emit a **second** transparent video alongside the cutout: same source RGB, alpha is `255 − mask` instead of `mask`. The cutout is the subject with a transparent background; the plate is the original surroundings with a transparent hole where the subject was.
| File | Alpha is… | Use it for |
| -------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------- |
| `-o subject.webm` | The mask — subject opaque, background transparent | Foreground layer, place on top |
| `--background-output plate.webm` | Inverse — surroundings opaque, subject region transparent | Bottom layer; put text or graphics between this and the subject |
Both outputs share the same `--quality` preset and run from a single inference pass — encode cost roughly doubles, segmentation cost stays the same. Only valid for video inputs and `.webm`/`.mov` outputs.
**Hole-cut plate, not an inpainted clean plate.** The subject region in `plate.webm` is fully transparent — composite something opaque under it to fill the hole. The single test for whether `--background-output` is the right tool: _will anything ever be visible through the subject's silhouette where the subject used to be?_
| Use case | Right tool |
| ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| Text/graphics between the cutout and the plate (this command's reason for existing) | **Hole-cut** (`--background-output`) |
| Subject onto an unrelated scene | Just `subject.webm`; ignore the plate |
| Show the room _without_ the person, alone over no other content | **Clean plate** — needs an inpainter (LaMa, ProPainter, E2FGVI). Not this command. |
| Replace the subject with a different subject | **Clean plate** — same as above |
If a user asks for "the room with the person removed" and intends to display it standalone, do **not** reach for `--background-output`. Tell them they need an inpainter.
Typical layered composition (the canonical hole-cut use case):
```html
<!-- z=1 the inverse-alpha plate fills everything except the subject region -->
<video
src="plate.webm"
data-start="0"
data-duration="6"
data-track-index="0"
muted
playsinline
></video>
<!-- z=2 graphics / text live between the two layers -->
<h1 id="headline" style="z-index:2; ...">MAKE IT IN HYPERFRAMES</h1>
<!-- z=3 the cutout floats the subject back over the headline -->
<div class="cutout-wrap" style="position:absolute;inset:0;z-index:3">
<video
src="subject.webm"
data-start="0"
data-duration="6"
data-track-index="1"
muted
playsinline
></video>
</div>
```
This is functionally equivalent to the text-behind-subject pattern below, but you don't need the original `presenter.mp4` in the project — the plate replaces it. Useful when you want to ship just the two transparent layers and let the user drop arbitrary content between them.
### Output Format
| Format | When |
| --------------------- | ------------------------------------------------------------- |
| `.webm` (VP9 + alpha) | Default. Compositions play this directly via `<video>`. |
| `.mov` (ProRes 4444) | Editing in DaVinci/Premiere/FCP. Large files. |
| `.png` | Single-image cutout (still subject, layered over a backdrop). |
Chrome decodes VP9 alpha natively, so the `.webm` plugs into a composition like any other muted-autoplay video — see the `hyperframes` skill for the `<video>` track conventions.
### Quality presets
`--quality fast|balanced|best` controls only the VP9 encoder's CRF — segmentation quality is fixed.
| Preset | CRF | When |
| ---------- | --- | ----------------------------------------------------- |
| `fast` | 30 | Iterating, smaller file, looser color match |
| `balanced` | 18 | Default. Visually identical for most uses |
| `best` | 12 | Master / final delivery. Largest file, tightest match |
### Compositing patterns — pick the right one
The cutout webm is a **re-encoded copy** of the source mp4's RGB. That choice has consequences depending on what you put behind it:
| Pattern | What's behind the cutout | Result |
| -------------------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Cutout over a different scene** (most common) | Static image, gradient, or unrelated video | Looks great. The cutout's RGB is the only source of the subject — no doubling, no edge halo. This is what `remove-background` is built for. |
| **Cutout over its own source mp4** (text-behind-subject) | Same mp4 the cutout was generated from | Two RGB sources for the same person. At default `--quality balanced` (crf 18) the doubling is barely visible; at `--quality fast` (crf 30) you'll see a faint color shift / edge halo. Use `--quality best` (crf 12) for masters. |
| **Cutout over a _different_ take of the same person** | Footage of the same subject | Will look like two separate people overlapping. Don't do this. |
**Text-behind-subject** (headline behind a presenter):
```html
<video
src="presenter.mp4"
id="bg"
data-start="0"
data-duration="6"
data-track-index="0"
muted
playsinline
></video>
<h1 id="headline" style="z-index:2; ...">MAKE IT IN HYPERFRAMES</h1>
<div class="cutout-wrap" style="position:absolute;inset:0;z-index:3;opacity:0">
<video
src="presenter.webm"
data-start="0"
data-duration="6"
data-track-index="1"
muted
playsinline
></video>
</div>
```
Two key rules:
1. **Wrap the cutout video in a non-timed `<div>`** and animate the wrapper's opacity, not the video element's. The framework forces opacity:1 on active clips (any element with `data-start`/`data-duration`), so animating the video's opacity directly is silently overridden. The wrapper has no `data-*` attributes, so it's owned by your CSS/GSAP.
2. **Both videos use `data-start="0"` and `data-media-start="0"`** so the framework decodes them in sync from t=0. Late-mounting the cutout (`data-start=3.3`) introduces a seek + warm-up that lands a frame off the base mp4 — visible as one frame of misalignment at the cut.
Then GSAP-flip the wrapper opacity at the cut: `tl.set(cutoutWrap, { opacity: 1 }, 3.3)`.
## TTS → Transcribe → Captions
When there's no pre-recorded voiceover, generate one and transcribe it back to get word-level timestamps for captions:
```bash
npx hyperframes tts script.txt --voice af_heart --output narration.wav
npx hyperframes transcribe narration.wav # → transcript.json
```
Whisper extracts precise word boundaries from the generated audio, so caption timing matches delivery without hand-tuning.
don't have the plugin yet? install it then click "run inline in claude" again.
Three CLI commands that produce assets for compositions: tts (speech), transcribe (timestamps), and remove-background (transparent video). Each downloads a model on first run and caches it under ~/.cache/hyperframes/.
use this skill when you need to preprocess media assets for hyperframes compositions. three commands cover the main flows: generate voiceover narration from text via kokoro tts, extract word-level timestamps from speech via whisper, or segment a person/subject from video/image for transparent overlays via u2net. each command is self-contained, downloads its own model on first use, and outputs artifacts you drop directly into the composition html. chain these (e.g., tts → transcribe → captions) to automate narration + caption timing.
espeak-ng for phonemization. install via brew install espeak-ng (macos) or apt-get install espeak-ng (linux)kokoro-onnx, soundfile (tts); openai-whisper or faster-whisper (transcribe); torch, torchvision, onnxruntime (remove-background)~/.cache/hyperframes/step 1: choose or infer the voice id based on content type and language.
af_heart, bf_emma, jf_alpha--lang override needed. if you need a stylized accent or the text language doesn't match voice locale, use --lang to overridestep 2: prepare input text.
.txt file (long scripts)step 3: set speech speed if non-default.
--speed flag value (0.7-1.5 range)step 4: run tts command.
.wav file at specified pathnpx hyperframes tts "<text>" --voice <id> --output <file.wav> or npx hyperframes tts <script.txt> --voice <id> --output <file.wav> --speed <0.7-1.5>~/.cache/hyperframes/tts/. subsequent runs use cache--lang is non-english, command fails with instruction to install itstep 5: verify audio output.
.wav filestep 1: determine the language of the audio.
es, en, ja, fr-fr) or "unknown"--language flag and whisper auto-detectsstep 2: select whisper model size.
small (466 MB, moderate speed). use tiny (75 MB) for preview/testing, base for short clips, medium (1.5 GB) for noisy audio/music, large-v3 (3.1 GB) for production. never suffix with .en unless you explicitly know audio is english and you want whisper to translate non-english into english (destructive)step 3: run transcribe command.
transcript.json in current directorynpx hyperframes transcribe <file> (auto-detects language, uses model small), or npx hyperframes transcribe <file> --model <size> --language <code> (explicit language + model)~/.cache/hyperframes/transcribe/models/. subsequent runs use cachesmall on 10 min audio takes ~30 sec; large-v3 takes ~3 minstep 4: inspect transcript.json for accuracy.
transcript.jsonmedium or large-v3)step 5: drop transcript.json into composition.
transcript.jsonstep 1: prepare the source video or image.
.mp4 (or .mov, .webm, .mkv) or .jpg/.pngstep 2: choose output format and layer strategy.
.webm, .mov, or .png) and --background-output flag (yes/no).webm (vp9 + alpha, native browser decode). use .mov (prores 4444) only if you're editing in davinci/premiere/fcp. use .png only for single-image cutouts. if you want both the subject layer (opaque subject, transparent background) and the plate layer (transparent subject, opaque background), add --background-output <plate.webm> flagstep 3: set quality preset if non-default.
--quality flag valuebalanced (crf 18, good balance). use fast (crf 30) for iteration and smaller files, best (crf 12) for final delivery and tightest color match. segmentation quality is fixed; crf only affects vp9 encodestep 4: optionally specify device.
--device flag value--device cpu to force cpu (slower but no cuda/mps dependency)step 5: run remove-background command.
.webm (or .mov/.png) at specified path, optionally a second plate filenpx hyperframes remove-background <source> -o <output.webm> (basic cutout), or npx hyperframes remove-background <source> -o <transparent.webm> --background-output <plate.webm> --quality best (both layers, high quality)~/.cache/hyperframes/background-removal/models/. subsequent runs use cachestep 6: verify segmentation and composite structure.
.webm files--quality best. if segmentation missed a limb or include stray background, re-run with a better-lit source or crop the source to focus the frame--background-output plate, verify the subject region in plate is fully transparent and the surroundings are opaque. plan what content (text, graphics, solid color) will sit between the plate and cutout layersstep 7: drop cutout (and plate) into composition.
.webm file(s)step 1-5: run tts command (see command 1, steps 1-5).
narration.wavstep 6: run transcribe command on the generated narration.
narration.wavnpx hyperframes transcribe narration.wavtranscript.json with precise word-level timestamps extracted from the generated speech--language or --model if default small + english auto-detect works (it will for english voices)step 7: use transcript.json for caption timing in composition.
transcript.json, composition htmltts language / voice mismatch:
ef_dora voice), no --lang needed. whisper auto-detects from voice idjf_alpha voice, or stylized accent needed), pass --lang to override. valid codes: en-us, en-gb, es, fr-fr, hi, it, pt-br, ja, zh--lang is non-english, tts fails. install espeak-ng and retrytranscribe language rule (non-negotiable):
--model small --language <code> (no .en suffix). .en models translate non-english audio into english (destructive)--model small.en (optional; small without .en also auto-detects english correctly)--model small (no .en, no --language). whisper auto-detectssmall, not small.en. never guess .en if unsuretranscribe input format:
.srt or .vtt, command normalizes to transcript.jsonremove-background output format:
.webm (vp9 + alpha, native browser support, default).mov (prores 4444, larger file, lossless).pngremove-background layer strategy:
--background-output <plate> to emit both cutout and plate. subject layer goes on top (z-index 3), plate on bottom (z-index 1), text/graphics in middle (z-index 2)remove-background quality preset:
--quality fast (crf 30)--quality balanced (crf 18, default)--quality best (crf 12) to minimize color shift / edge halo when cutout overlays re-encoded version of same sourceremove-background device selection:
--device flag (auto-detect)--device cpu (slower, 2-3x latency)remove-background with multiple layers (text-behind-subject):
<div> and animate wrapper opacity, not video opacity. framework forces opacity:1 on active clips (elements with data-start/data-duration), so video-level opacity is silently overriddendata-start="0" and data-media-start="0" so they decode in sync. late-mounting cutout introduces seek warm-up that lands a frame off the source , visible as misalignmenttts outputs:
.wav file at path specified in --output flag<audio> elements)transcribe outputs:
transcript.json in current working directorytext, start, end (float seconds) and optional id field (w0, w1, ..., for caption override references)[{"id":"w0","text":"Hello","start":0.0,"end":0.5},{"id":"w1","text":"world.","start":0.6,"end":1.2}]remove-background outputs:
.webm (vp9 + alpha, default) or .mov (prores 4444) or .png (single image)<video> elements. file size: ~2-5 mb per minute of hd 1080p video depending on quality preset--background-output flag): plate file (inverse alpha, same format as primary). when both exist, sum of both files is ~1.3x size of single cutout-o flag (or current directory if relative path)remove-background segmentation quality:
--quality preset). u2net_human_seg trained on portrait photography, works best on people. objects, animals, or non-portrait framing may require manual refinementtts success:
.wav file exists at specified output pathtranscribe success:
transcript.json exists in current directorytext, start, end fields all populated