|
| 1 | +# TAGLINE |
| 2 | + |
| 3 | +Local renderer for narrated explainer videos |
| 4 | + |
| 5 | +# TLDR |
| 6 | + |
| 7 | +**Check** Node, ffmpeg, Chrome, and the speech models |
| 8 | + |
| 9 | +```explainroo doctor``` |
| 10 | + |
| 11 | +**Download** the voice and timing models (about 400 MB) |
| 12 | + |
| 13 | +```explainroo doctor --fetch``` |
| 14 | + |
| 15 | +**Start** a project in the chalk look, sized for YouTube |
| 16 | + |
| 17 | +```explainroo init [how-dns-works] --theme [chalk] --size [youtube] --title ["How DNS finds a website"]``` |
| 18 | + |
| 19 | +**Preview** in a browser; the page reloads when the script is saved |
| 20 | + |
| 21 | +```explainroo preview [how-dns-works]``` |
| 22 | + |
| 23 | +Generate **narration and word timings** |
| 24 | + |
| 25 | +```explainroo voice [how-dns-works]``` |
| 26 | + |
| 27 | +Save a **PNG** of one moment in a scene |
| 28 | + |
| 29 | +```explainroo still [how-dns-works] [intro@2.4]``` |
| 30 | + |
| 31 | +**Find** layout, timing, and pronunciation problems |
| 32 | + |
| 33 | +```explainroo check [how-dns-works]``` |
| 34 | + |
| 35 | +Render a **half-size draft** |
| 36 | + |
| 37 | +```explainroo render [how-dns-works] --draft``` |
| 38 | + |
| 39 | +Render the **full** MP4, using four Chrome workers |
| 40 | + |
| 41 | +```explainroo render [how-dns-works] --workers [4]``` |
| 42 | + |
| 43 | +**Check** the finished file for loudness, black frames, and a clear voice |
| 44 | + |
| 45 | +```explainroo verify [how-dns-works]``` |
| 46 | + |
| 47 | +Hear one **voice** |
| 48 | + |
| 49 | +```explainroo say ["DNS finds a website"] --voice [am_michael] --out [sample.wav]``` |
| 50 | + |
| 51 | +**Search** the built-in icons |
| 52 | + |
| 53 | +```explainroo icons [lock] [key]``` |
| 54 | + |
| 55 | +# SYNOPSIS |
| 56 | + |
| 57 | +**explainroo** _command_ [_project_] [_arguments_] [_options_] |
| 58 | + |
| 59 | +**explainroo** **--help** | **-h** |
| 60 | + |
| 61 | +**explainroo** **version** | **--version** |
| 62 | + |
| 63 | +# DESCRIPTION |
| 64 | + |
| 65 | +**explainroo** turns a project directory into a narrated MP4 on the local machine. `script.md` is the words the voice says. `scenes.js` draws the pictures in JavaScript, and a drawing can appear on a particular word. `video.json` sets the look, size, voice, and pace. Kokoro reads the script, a timestamped Whisper model marks when each word is spoken, Chrome draws the frames, and **ffmpeg** muxes picture, voice, music, and sound effects into `out/video.mp4`. |
| 66 | + |
| 67 | +Drawings can use hand-drawn lines, more than 1,800 Lucide icons, charts, code, terminal windows, your own screenshots, and optional AI illustrations. The tool also builds product-demo scenes: reconstructed app screens with a pointer that clicks and types. It does not shoot or import filmed footage. |
| 68 | + |
| 69 | +Most commands take the project directory as the first argument and use the current directory when it is omitted. `still` and `image` treat the first argument as a project only when that path contains `video.json`. **--json** prints machine-readable output on stdout; logs go to stderr. **--quiet** suppresses logs. |
| 70 | + |
| 71 | +The repository is written so a coding agent can author `script.md` and `scenes.js` by following `AGENTS.md`. The same commands work by hand. Inside a clone, `npm link` puts `explainroo` on `PATH`. Without a link, run `node bin/explainroo.js` from the repository. |
| 72 | + |
| 73 | +# COMMANDS |
| 74 | + |
| 75 | +**init** _dir_ [**--theme** _name_] [**--size** _size_] [**--pace** _N_] [**--voice** _id_] [**--title** _text_] |
| 76 | +> Create a project: `video.json`, a starter `script.md` and `scenes.js`, an `assets/` directory, and a `.gitignore`. Refuses if `video.json` already exists. Defaults: theme `paper`, size `16:9`, voice `af_heart`, title from the directory name. |
| 77 | +
|
| 78 | +**voice** [_project_] [**--force**] |
| 79 | +> Generate narration and word timings. Prints each scene's duration and how much of the speech check matched. Only changed scenes are redone unless **--force** is set. |
| 80 | +
|
| 81 | +**preview** [_project_] [**--port** _N_] |
| 82 | +> Serve a live preview (default port **4400**) and reload narration when `video.json` or `script.md` changes. Stop with Ctrl+C. |
| 83 | +
|
| 84 | +**still** [_project_] [_time_ ...] [**--scale** _N_] |
| 85 | +> Write PNG stills. A time is `12.5`, a scene id, `scene@2.4`, `scene@start`, or `scene@end`. With no times, saves the end of every scene. **--scale** defaults to 1. |
| 86 | +
|
| 87 | +**sheet** [_project_] [**--scene** _id_] [**--every** _seconds_] [**--cols** _N_] |
| 88 | +> Write a contact sheet of the whole video, or of one scene. |
| 89 | +
|
| 90 | +**check** [_project_] [**--step** _seconds_] |
| 91 | +> Look for layout, timing, and pronunciation problems. **--step** is the sample interval (default **0.25**). Exits **1** when any finding is an error. |
| 92 | +
|
| 93 | +**render** [_project_] [**--draft**] [**--from** _s_] [**--to** _s_] [**--workers** _N_] [**--scale** _N_] [**--out** _file_] |
| 94 | +> Write `out/video.mp4`. **--draft** writes a half-size `out/draft.mp4`. **--from** and **--to** are seconds. **--workers** is how many Chrome pages draw at once. **--out** sets the file. |
| 95 | +
|
| 96 | +**verify** [_project_] [**--file** _path_] |
| 97 | +> Check the rendered file for duration, size, loudness, true peak, black frames, silence, and whether the narration is understandable. **--file** checks another file. Exits **1** on an error-level finding. |
| 98 | +
|
| 99 | +**image** [_project_] _name_ _prompt_ [**--model** _best_|_cheap_] [**--aspect** _ratio_] [**--ref** _a,b_] [**--style** _text_] [**--no-style**] |
| 100 | +> Make one AI illustration through OpenRouter and save `assets/<name>.png`. Needs **OPENROUTER_API_KEY**. |
| 101 | +
|
| 102 | +**images** [_project_] |
| 103 | +> List generated images and what they cost. |
| 104 | +
|
| 105 | +**voices** |
| 106 | +> List the 28 English voices (American and British). |
| 107 | +
|
| 108 | +**say** _text_ [**--voice** _id_] [**--speed** _N_] [**--out** _file_] |
| 109 | +> Speak a short line to a WAV file. Default voice `af_heart`, speed **1**, output `<voice>.wav`. |
| 110 | +
|
| 111 | +**themes** |
| 112 | +> List the looks (`paper`, `clean`, `chalk`, `blueprint`, `midnight`) and the music styles. |
| 113 | +
|
| 114 | +**formats** |
| 115 | +> List platform sizes (YouTube, Shorts, TikTok, Reels, Instagram, LinkedIn, and square) and the safe content area of each. |
| 116 | +
|
| 117 | +**icons** [_word_ ...] |
| 118 | +> Search the built-in icons by name and tag. With no words, prints how many icons are shipped. |
| 119 | +
|
| 120 | +**doctor** [**--fetch**] |
| 121 | +> Check Node.js (20.11 or newer), ffmpeg, and Chrome or Chromium. Reports whether the Kokoro and Whisper models are cached. **--fetch** downloads them (about 400 MB the first time). Exits **1** when Node, ffmpeg, or Chrome is missing; missing models alone do not fail the command. |
| 122 | +
|
| 123 | +**help**, **version** |
| 124 | +> Print usage or the version from `package.json`. **--help** / **-h** and **--version** do the same. An unknown command exits **2**. |
| 125 | +
|
| 126 | +# PARAMETERS |
| 127 | + |
| 128 | +**--json** |
| 129 | +> Machine-readable output on stdout. Logs stay on stderr. |
| 130 | +
|
| 131 | +**--quiet** |
| 132 | +> Suppress log lines. |
| 133 | +
|
| 134 | +**--help**, **-h** |
| 135 | +> Print the command summary. |
| 136 | +
|
| 137 | +**--version** |
| 138 | +> Print the version and exit. |
| 139 | +
|
| 140 | +# CONFIGURATION |
| 141 | + |
| 142 | +A project is configured in `video.json`. Unknown or out-of-range values stop the command with an error. Useful keys: |
| 143 | + |
| 144 | +**title** |
| 145 | +> Title shown in the preview. Defaults from the script. |
| 146 | +
|
| 147 | +**theme** |
| 148 | +> Look: `paper` (default), `clean`, `chalk`, `blueprint`, or `midnight`. |
| 149 | +
|
| 150 | +**size** |
| 151 | +> `16:9` (default), a ratio (`9:16`, `1:1`, `4:5`), a platform name (`youtube`, `shorts`, `tiktok`, `reels`, `vertical`, `instagram`, `linkedin`, `square`), or `WIDTHxHEIGHT`. |
| 152 | +
|
| 153 | +**voice** |
| 154 | +> Voice id (default `af_heart`). `explainroo voices` lists them. |
| 155 | +
|
| 156 | +**speed** |
| 157 | +> How fast the voice speaks, from 0.6 to 1.6 (default **0.9**). |
| 158 | +
|
| 159 | +**pace** |
| 160 | +> Scales voice, pauses, and animation together, from 0.7 to 1.6 (default **1**). |
| 161 | +
|
| 162 | +**fps** |
| 163 | +> 24, 25, 30 (default), 50, or 60. |
| 164 | +
|
| 165 | +**music** |
| 166 | +> `true` (default) uses the look's music style. A style name (`warm`, `upbeat`, `calm`, `tech`, `playful`), an object `{ "style": "calm", "volume": 0.35 }`, or `false`. |
| 167 | +
|
| 168 | +**sfx** |
| 169 | +> Sound effects: `true` (default), `"minimal"`, or `false`. |
| 170 | +
|
| 171 | +**captions** |
| 172 | +> `true`, `false`, or `"auto"` (default). `"auto"` captions tall and square videos and leaves wide videos without captions. |
| 173 | +
|
| 174 | +**transition** |
| 175 | +> `"auto"` (default), `fade`, `slide`, `wipe`, `zoom`, `brush`, or `cut`. |
| 176 | +
|
| 177 | +**watermark** |
| 178 | +> Corner text, default `explainroo.com`. `false` turns it off. |
| 179 | +
|
| 180 | +**loudness** |
| 181 | +> Target loudness of the finished video in LUFS (default **-14**). |
| 182 | +
|
| 183 | +One scene can override timing in its `script.md` heading, for example `## outro {hold=2 transition=cut}` with `hold`, `lead`, `min`, or `transition`. |
| 184 | + |
| 185 | +# ENVIRONMENT |
| 186 | + |
| 187 | +**OPENROUTER_API_KEY** |
| 188 | +> Key for the **image** command. Also read from a `.env` file in the explainroo checkout or the project. |
| 189 | +
|
| 190 | +**EXPLAINROO_CHROME** |
| 191 | +> Path to Chrome or Chromium when auto-detection fails. |
| 192 | +
|
| 193 | +**EXPLAINROO_FFMPEG**, **EXPLAINROO_FFPROBE** |
| 194 | +> Paths to ffmpeg and ffprobe. |
| 195 | +
|
| 196 | +**EXPLAINROO_CACHE** |
| 197 | +> Where speech models are stored. Default `~/.cache/explainroo`. |
| 198 | +
|
| 199 | +**EXPLAINROO_OFFLINE** |
| 200 | +> Set to `1` to refuse model downloads. |
| 201 | +
|
| 202 | +**EXPLAINROO_TTS_DTYPE** |
| 203 | +> Voice-model precision. `fp32` is the default. `q8` is a smaller download and runs slower. |
| 204 | +
|
| 205 | +**EXPLAINROO_DEBUG** |
| 206 | +> Set to `1` to print a full stack trace on errors. |
| 207 | +
|
| 208 | +# CAVEATS |
| 209 | + |
| 210 | +Install from the Git repository (Node.js 20.11 or newer, ffmpeg, and Chrome or Chromium). The public npm registry has no `explainroo` package: |
| 211 | + |
| 212 | +```git clone https://github.com/vincentsch/explainroo.git && cd explainroo && npm install && npm link``` |
| 213 | + |
| 214 | +The first **voice**, **say**, or **doctor --fetch** downloads about 400 MB of models into `~/.cache/explainroo`. Voice and the pronunciation check are English only. Drawing, speech, and the speech check run on the CPU. **preview** listens on localhost. **image** spends OpenRouter credit (about 7 to 13 cents a picture); **images** prints the project total. Generated music and sound effects are synthesized by explainroo. Screenshots and AI pictures you add keep their own licenses. |
| 215 | + |
| 216 | +# HISTORY |
| 217 | + |
| 218 | +**explainroo** is an MIT-licensed Node.js tool by **Vincent Schmalbach**. The GitHub repository was created on **25 September 2026**. Speech is Kokoro (Apache-2.0), word timing is Whisper through Transformers.js, drawings use Rough.js and Lucide, Playwright drives Chrome, and ffmpeg writes the MP4. Fonts are under the SIL Open Font License. |
| 219 | + |
| 220 | +# SEE ALSO |
| 221 | + |
| 222 | +[ffmpeg](/man/ffmpeg)(1), [ffprobe](/man/ffprobe)(1), [whisper](/man/whisper)(1), [node](/man/node)(1), [npm](/man/npm)(1), [chromium](/man/chromium)(1), [playwright](/man/playwright)(1) |
| 223 | + |
| 224 | +# RESOURCES |
| 225 | + |
| 226 | +```[Source code](https://github.com/vincentsch/explainroo)``` |
| 227 | + |
| 228 | +```[Homepage](https://www.explainroo.com)``` |
| 229 | + |
| 230 | +```[Documentation](https://www.explainroo.com/docs/)``` |
| 231 | + |
| 232 | +<!-- verified: 2026-10-03 --> |
0 commit comments