# Narrate

> Turns any text, Markdown, or book into spoken audio entirely on your own machine using the local Kokoro text-to-speech model (voice "Heart" / af_heart) — no cloud, no API keys, nothing uploaded, free. Three modes: speak a short string, narrate a .txt/.md file to an mp3, or compile a folder of chapters into a chaptered .m4b audiobook ready for Apple Books. Trigger this skill whenever someone says "narrate this", "read this aloud", "make an audiobook", "turn this into an mp3", "turn this into audio", "make a voiceover", "say this with Heart", "read me this file", or otherwise wants text spoken locally. Handles Markdown-to-spoken cleanup (strips syntax, linearizes tables and lists, expands symbols Kokoro mispronounces) and chunked, resumable synthesis.

- Skill: `vybzai/narrate` (Agent Skill)
- Install (CLI): `npx skillmds@latest add vybzai/narrate`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vybzai/narrate/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: vybzai (https://skillmd.com/u/vybzai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vybzai/narrate

---


# narrate

`narrate` is the narration half of **spitfire-storybook** — a privacy-first,
local-only, open-source tool. It doesn't just help write your book, it reads it
to you. Every byte of synthesis happens on your own hardware via a bundled local
Kokoro server. There is no hosted version and nothing is ever uploaded.

The script lives at `cli/narrate.py` (relative to the repo root). Run it with
your system Python:

```bash
python3 cli/narrate.py <mode> [args] [flags]
```

## Prerequisites

- **A local Kokoro server.** The CLI talks to it over HTTP. Start one from
  `cli/kokoro/` (`./start.sh`, or `python3 cli/kokoro/server.py`). First run
  downloads the ~330 MB Kokoro-82M model and caches it locally. See
  `cli/kokoro/README.md`.
- **python3 + ffmpeg** on your PATH. That's it — synthesis is dependency-light;
  all audio decode/concat/encode goes through ffmpeg (no numpy/soundfile). `PIL`
  is optional and only used to auto-generate a book cover.
- The CLI **auto-detects** the server at `http://localhost:8081` (and
  `http://127.0.0.1:8081`). Override the endpoint with the `KOKORO_URL`
  environment variable or the `--server` flag. `KOKORO_VOICE` overrides the
  default voice. If no server is reachable, the CLI exits with a message telling
  you to start one.

## Three modes

### 1. `say` — speak a string

```bash
python3 cli/narrate.py say "Welcome to the first chapter." -o intro.mp3 --play
```

Speaks the given text. Defaults to `narration.mp3` when `-o` is omitted.

### 2. `file` — narrate a .txt/.md file

```bash
python3 cli/narrate.py file notes.md -o notes.mp3
```

Reads the file, applies Markdown cleaning, and synthesizes it. The default
output name is the input filename with an `.mp3` extension.

### 3. `book` — build a chaptered .m4b audiobook

```bash
python3 cli/narrate.py book ./my-book \
  --title "The Fictional Field Guide" \
  --subtitle "An Imaginary Manual" \
  --author "A. N. Author" \
  --year 2026
```

The input is either:

- **a directory** of `.md`/`.txt` files (sorted recursively). Each file becomes a
  chapter; its title is the first `# H1` heading, or the filename if there's none.
- **a manifest** `manifest.json`: a list of `[{title, text|src|text_inline, ...}]`
  entries, giving explicit chapter order, titles, and content.

It synthesizes each chapter, then muxes them into a single `.m4b` with embedded
chapter markers, metadata, and (by default) an auto-generated cover. Output
defaults to a slugified title with the `.m4b` extension.

**Book mode is resumable.** Chapter WAVs are cached in a `.narrate_build/`
directory next to the output; if a chapter file already exists, it's skipped on
the next run. Delete that directory (or a single chapter WAV) to force a
re-synthesis.

## Flags

Common to all modes:

| Flag | Meaning |
| --- | --- |
| `-o`, `--out PATH` | Output file (`.mp3` / `.wav` / `.m4a` / `.m4b`). Sensible default per mode. |
| `--voice NAME` | Kokoro voice (default `af_heart` = "Heart"). |
| `--gap SECONDS` | Pause inserted between chunks (default `0.3`). |
| `--play` | Play the result when done (macOS `afplay`). |
| `--server URL` | Override the Kokoro endpoint (else auto-detect / `KOKORO_URL`). |

`file` and `book` also accept:

| Flag | Meaning |
| --- | --- |
| `--clean md\|plain\|none` | Markdown cleaning mode (default `md`). |

Book-only:

| Flag | Meaning |
| --- | --- |
| `--title` | Book / album title. |
| `--subtitle` | Subtitle (stored as comment). |
| `--author` | Author. |
| `--narrator` | Narrator credit (default `Heart · Kokoro TTS`). |
| `--year` | Publication year. |
| `--cover auto\|none\|PATH` | `auto` generates a cover (needs PIL); default `auto`. |
| `--bitrate` | AAC bitrate for the `.m4b` (default `96k`). |

## Markdown → spoken cleaning

With `--clean md` (the default), the text is converted from Markdown into
something that reads naturally aloud before synthesis:

- strips YAML frontmatter, code fences, inline code, HTML, images, and bare URLs
- turns links into their visible text
- linearizes tables into spoken rows and flattens list markers / blockquotes into
  sentences
- converts headings into spoken lines with a terminal period
- removes emphasis markers (`**`, `*`, `_`)
- **expands symbols Kokoro otherwise mispronounces**: `%` → "percent",
  `$1,500` → "1500 dollars", `$5k` → "5 thousand dollars", `3x` → "3 times",
  `&` → "and", `→`/`->` → "leads to", `24/7` → "twenty-four seven", plus common
  abbreviations (`e.g.`, `i.e.`, `etc.`, `vs.`, `approx.`, `w/`).

Use `--clean plain` to just collapse whitespace (keeps the raw text), or
`--clean none` to feed the text through untouched. In `book` mode, `.txt` chapters
are always treated as `plain` regardless of `--clean`.

Long text is automatically split into ~1500-character chunks at paragraph and
sentence boundaries, synthesized chunk-by-chunk, and stitched together with the
configured gap.

## Importing the .m4b into Apple Books

1. Open the **Books** app on macOS.
2. Drag the generated `.m4b` onto the Books window, or use **File → Add to
   Library…** and select it.
3. It appears under **Audiobooks** with chapter navigation, the embedded cover,
   and resume-where-you-left-off playback. It syncs to your iPhone/iPad via the
   same Apple ID.

(`.m4b` is a standard AAC audiobook container, so the same file also imports into
Podcasts and most third-party audiobook players.)

