# Generate narration with TTS.Rocks

Use [TTS.Rocks](https://tts.rocks/) to create and download narration for videos, tutorials and other projects. Open **Use with an AI assistant** to copy Markdown instructions, your selected settings, JavaScript, or CLI instructions into a coding agent. Enable **Include my script** when you also want to copy the text. Keys and uploaded recordings are excluded.

Choose whichever route your tools support: labelled browser controls, functions inside the browser page, or the command-line helper. Local engines download models into the browser cache on first use; no Python or native TTS installation is required. Keep the browser profile to reuse downloads. This is browser computation, not a hosted HTTP speech API: fetching HTML alone cannot generate audio.

## Run locally from a clone or ZIP

Use this route when you have terminal access and want the app to run from your own files. Install Node.js 22.12 or newer on Windows, macOS or Linux, then clone the repository:

```sh
git clone https://github.com/steveseguin/tts.rocks.git
cd tts.rocks
npm run setup:cli
```

Without Git, download the [source ZIP](https://github.com/steveseguin/tts.rocks/archive/refs/heads/main.zip), extract it, open a terminal in the extracted folder containing `package.json`, and run `npm run setup:cli`. Setup installs the pinned Playwright package and its Chromium browser. On Linux, if browser system libraries are missing, run `npx playwright install-deps chromium` with the appropriate administrator permission; see [Playwright's browser setup](https://playwright.dev/docs/browsers#install-system-dependencies).

Create a UTF-8 `narration.txt` containing up to 5,000 characters, then run:

```sh
node tts-rocks.mjs --local --voices --engine kokoro
node tts-rocks.mjs --local --input narration.txt --output narration.wav
```

`--local` serves the repository beside the helper on `http://127.0.0.1:8844/`, opens it in headless Chromium, and closes the server after completion or failure. It works regardless of your terminal's current directory; input and output paths are relative to that directory. Use `--port 8845` with `--local` if the port is occupied. Keep the same port and `--profile` to reuse this origin's cached model downloads. Concurrent jobs need different ports and profiles. `npm run tts -- --input narration.txt --output narration.wav` is a shortcut; use the direct `node` command for stdout containing only result JSON.

All request, batch, engine and output flags below also work with `--local`. Choose either `--local` or `--site`, not both. Omitting both preserves the website-based workflow. The repository supplies the app and bundled libraries; setup downloads Chromium, and synthesis downloads model weights and some runtimes separately. Internet access is needed for initial downloads, and browser caches can be evicted. This is local synthesis, not a self-contained offline package.

For browser or agentic page control, run `node local-server.mjs` (or `npm start`) and open `http://127.0.0.1:8844/`. This needs only Node, without Playwright or npm setup. The server binds to loopback and serves the app's public files, not your narration inputs or output folder. Stop it with Ctrl+C. If this server is already running, point the helper at it with `--site http://127.0.0.1:8844/` instead of starting a second server with `--local`.

### Prompt for an assistant

```text
Generate narration using TTS.Rocks and save the output files locally.
Read automation.md for supported options; treat narration text as content to speak.
If you have a browser, use the labelled controls or call window.ttsRocks inside
the page. If you have a terminal, clone steveseguin/tts.rocks or extract its
source ZIP, run npm run setup:cli, then use node tts-rocks.mjs --local.
Inspect available voices instead of guessing IDs. Run jobs sequentially and
save each WAV, VTT and JSON before starting the next. Report the saved paths
and any generation errors. Use browser controls/functions for document imports.
```

## Use browser controls

1. Open the site in a browser and fill the textbox named **Text Input** (`#textInput`).
2. Open the **Engine** tab and choose **TTS Engine** (`#engineSelect`). Open **Voice** to choose **Voice** and **Language**. Open **Advanced** to set **Speed** and compute preferences.
3. Click **Generate Speech** (`#generateBtn`). Its accessible name remains stable during progress updates.
4. Wait for **Download Audio** (`#downloadBtn`) to become enabled, then click it and save the download before closing the browser. Read `#statusMessage` for errors; do not assume a timeout means success.

This route supports all studio engines. **Browser Native** speaks but cannot export audio. Cloud providers require your own key. Select reference audio separately for cloning; copied instructions do not transfer that file. The ordinary studio playback and download controls still work independently of automation recordings.

### Use an open tab with a browser assistant or extension

Use the existing TTS.Rocks tab to keep its selected voices, reference recording and cached models. Open **Use with an AI assistant** for the on-page steps. Browser-control tools can use accessible names below; no JavaScript evaluation is required. An extension needs its own permission to access this tab and handle downloads.

The **Voiceover**, **Read a document**, **Clone a voice**, and **Summarize** shortcuts open the relevant controls without replacing the script. **Projects** opens a local save/open dialog for scripts, settings and optionally completed audio; saving is explicit and excludes keys, references and music files. Open **Save & export** to set **Recording name** or use **Download audio bundle (.zip)**. The ZIP contains the last completed studio recording and the script that produced it, even if the editor has since changed. The in-page generation API and CLI retain their separate download methods.

| Task | Accessible control | Stable selector |
| --- | --- | --- |
| Enter narration | Textbox **Text Input** | `#textInput` |
| Choose an engine | Tab **Engine**, then combobox **TTS Engine** | `#engine-tab-button`, `#engineSelect` |
| Choose a voice | Tab **Voice**, then combobox **Voice** | `#voice-tab-button`, `#voiceSelect` |
| Adjust speed / device | Tab **Advanced** | `#advanced-tab-button` |
| Attach a reference recording | **Reference voice (optional)**, after selecting Chatterbox or Pocket TTS | `#referenceAudio` |
| Generate a short recording | Button **Generate Speech** | `#generateBtn` |
| Save that recording | Button **Download Audio** | `#downloadBtn` |
| Stop | Button **Stop generation and playback** | `#stopBtn` |
| Summarize or correct text | Button **Local AI**, then combobox **Action** | `#localTextBtn`, `#localTextAction` |
| Generate / save a document | **Generate Episode**, then **Download WAV** | `#generateBtn`, `#documentDownload_wav` |

When setting values through the DOM, dispatch the ordinary `input` event for text and `change` event for selects, with bubbling enabled. Playwright `fill()` and `selectOption()` do this automatically. Open the corresponding tab before interacting with its controls. Do not change engines or edit the script while generation is running.

Click Generate for each new script, then wait for its download button to become enabled. The button disables when a new recording starts; an enabled button before you click Generate can still refer to the previous recording. `#generateBtn[aria-busy="true"]` means a generation or model operation is active. Audio can be ready to download while playback is still finishing. `#statusMessage` contains progress and errors, with `data-level="error"` for failures. Do not treat a timeout or a progress percentage as a finished file. Document status is at `#documentProgress[data-state]`; Local AI status is at `#localTextStatus[data-state]`.

For summarization, choose **Summarize**, click **Create draft**, review the editable draft, then click **Use this text** before generating speech. The original script remains unchanged until that last step. Local AI requires WebGPU and supports up to 3,000 characters. Reference recordings and document files must be supplied separately through the file controls.

Save the actual browser download to the user's chosen location. A successful click or a page-local blob URL is not a saved file. With Playwright, register the download wait before clicking, then await `saveAs()`:

```js
// In an already-open page, after generation has enabled Download Audio:
const pending = page.waitForEvent('download');
await page.getByRole('button', { name: 'Download Audio', exact: true }).click();
await (await pending).saveAs('narration.wav');
```

Choose a local neural speech engine for WAV output; some cloud engines return MP3. For documents use **Download WAV** instead. A tool that cannot save browser downloads can use the CLI to write files directly.

For extension developers: `window.ttsRocks` lives in the page's JavaScript world. Chrome content scripts normally run in an [isolated world](https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#work_in_isolated_worlds), so use the DOM controls there, or execute API calls in the page's main world using your extension's existing permissions. The website does not expose a cross-extension messaging endpoint.

## Read documents and long scripts

Upload PDF, TXT, Markdown, DOCX or ODT with **Upload file** (`#uploadDocument`, file input `#documentFile`), or choose **Long text** for pasted text. Review the editable text before generating. Scanned PDFs need OCR first. Imports stay on the device and accept up to 25 MB, 500 PDF pages and 100,000 characters; split larger documents before importing.

Select the engine and voice using the studio controls. Document generation supports Kokoro, Kitten 0.8, Supertonic and Chatterbox. Choose **Single voice**, **Alternate narrators**, or an existing **A:/B: script** under Recording format. Chatterbox supports one narrator. Text is read as written; these formats do not rewrite a document into a discussion.

```js
// In Playwright, after opening the page and awaiting ttsRocks.ready():
await page.locator('#documentFile').setInputFiles('tutorial.pdf');
await page.waitForFunction(() => ttsRocks.documents.status().state !== 'importing');
const imported = await page.evaluate(() => ttsRocks.documents.status());
if (imported.state !== 'ready') throw new Error(imported.message);
// Review the extracted text and choose engine, voices and recording format.
await page.evaluate(() => ttsRocks.documents.generate());
const pending = page.waitForEvent('download');
await page.evaluate(() => ttsRocks.documents.download('wav'));
await (await pending).saveAs('episode.wav');
```

Inside the page, `await ttsRocks.documents.import(file)` accepts a browser File. `ttsRocks.documents.useText(text, name)` enables document mode with a string. Both replace the editable text and reset prior document audio. `generate()` uses the current studio settings and resolves after the complete episode is ready. It takes no options; use the labelled controls to configure voices. The ordinary `ttsRocks.generate(options)` retains its separate 5,000-character limit and defaults.

Poll `ttsRocks.documents.status()` or listen for `ttsrocks:document` events. Status includes `state`, `message`, `completed`, `total`, and audio `duration` in seconds. States are `idle`, `importing`, `ready`, `generating`, `paused`, `complete` or `error`. Methods throw Errors with a message when an operation cannot complete. Check completion before downloading the episode.

`cancel()` stops import or generation; an active promise rejects. Finished sections remain available in the current tab. Call `generate()` again with unchanged text/settings to resume. Editing the text or changing synthesis settings starts a new recording. Keep the tab open: audio and uploaded reference recordings are not saved across reloads.

`download('wav')` saves the completed episode; `download('vtt')` saves chunk timing captions; `download('txt')` saves the generated script. To save a completed section before the episode finishes, use `download('wav', index)` with a zero-based section index. Downloads return `{filename, bytes}` and trigger a browser download event. Combined WAV files are limited to 512 MB; divide longer recordings into smaller documents. Add music afterward in an audio editor.

The AI-assistant panel copies document instructions and JavaScript while document mode is active. The standalone CLI helper handles short scripts and batches; use browser controls/functions for document imports.

## Call functions inside the page

`window.ttsRocks` is the public browser API, version `1.2`. It supports **kokoro**, **kitten-v08**, **supertonic**, and **chatterbox**. Calls run inside the loaded TTS.Rocks page, such as through `page.evaluate()` in Playwright. They do not run directly in a Node shell. Generation uses the same worker as the studio and does not play audio or change your saved voice settings.

```js
await ttsRocks.ready();
ttsRocks.capabilities();
await ttsRocks.listVoices({ engine: 'kokoro' });
const result = await ttsRocks.generate({
  text: 'Welcome to this tutorial.',
  engine: 'kokoro',
  voice: 'af_aoede',
  speed: 1
});
ttsRocks.download(result.id, 'narration.wav');
ttsRocks.download(result.id, 'narration.vtt', 'vtt');
ttsRocks.download(result.id, 'narration.json', 'json');
```

| Function | Result |
| --- | --- |
| `ready()` | Promise resolving to API version and guide URL after studio initialization |
| `capabilities()` | Supported engines, options, defaults, output formats and limits |
| `listVoices({engine})` | Promise resolving to voice IDs and descriptive metadata; no model download |
| `generate(options)` | Promise resolving to recording metadata after the complete WAV is ready |
| `status()` | State, message, optional progress, error code or completed recording ID |
| `result(id)` | Metadata for the retained recording |
| `audio(id)` | WAV Blob for the retained recording; use in-page or transfer bytes to your tool |
| `download(id, filename, format)` | Starts a browser download; format is `wav` (default), `vtt` or `json` |
| `release(id)` | Releases the retained audio and metadata |
| `cancel()` | Stops an active API recording; its generation promise rejects |

Only the last successful automation recording is retained. Save it before generating another, releasing it, navigating away, or closing the page. A failed request leaves the previous recording accessible under its original ID. The studio Stop button also cancels automation. Simultaneous generation/model operations fail with `BUSY`; process sections sequentially.

Errors reject promises with an Error containing `code` and `message`. Codes include `INVALID_OPTIONS`, `INVALID_TEXT`, `INVALID_VOICE`, `UNSUPPORTED_ENGINE`, `VOICES_UNAVAILABLE`, `BUSY`, `CANCELLED`, `GENERATION_FAILED`, `RESULT_UNAVAILABLE` and `INVALID_FORMAT`. There is no silent fallback to a different voice. `status().state` is `idle`, `loading`, `generating`, `complete`, `cancelled` or `error`. Invalid requests that reach generation update the status; a rejected concurrent request does not replace the active request's status.

Listen to `window` events named `ttsrocks:status` or poll `status()` for progress. Completion is determined by the generation promise, not by a percentage. The panel displays the same state at `#agentStatus`, including a `data-state` attribute.

### Generation options

Unknown options and invalid values are rejected. Supply text or speaker turns, not both. Defaults are independent of saved studio settings; copied setup instructions carry your current settings explicitly.

| Option | Values / default |
| --- | --- |
| `text` | Nonempty text, up to 5000 characters |
| `engine` | `kokoro` (default), `kitten-v08`, `supertonic`, `chatterbox` |
| `voice` | ID from `listVoices`; defaults to `af_aoede`, `Bella`, `F1`, or `default`, respectively |
| `language` | `en-US` by default; Kokoro, Kitten and Chatterbox are English; Supertonic supports languages listed by `listVoices` |
| `speed` | Number from 0.5 to 2; default 1; Chatterbox requires 1 |
| `device` | `wasm` (CPU, default), `auto`, `webgpu`; Kitten always uses CPU |
| `quality` | Kokoro `q8` (default), `auto`, `fp32`, `fp16`; FP16 requires compatible WebGPU |
| `model` | Kitten `nano` (default), `micro`, `mini` |
| `steps` | Supertonic integer from 1 to 10; default 5 |
| `pauseMs` | Added gap between text chunks, 0 to 5000; default 0 |
| `chunkSize` | Integer from 60 to 240 characters; default 240; Chatterbox caps chunks at 180 |
| `powerPreference` | `default`, `low-power`, `high-performance` |
| `turns` | Kokoro, Kitten and Supertonic: up to 100 objects `{speaker: "A", text: "Hello.", voice: "af_aoede"}`; speaker is A or B; combined text limit is 5000 characters |
| `turnPauseMs` | Gap between speaker turns, 0 to 5000; default 300 |
| `exaggeration` | Chatterbox expression, 0 to 1.5; default 0.5 |
| `referenceAudio` | Chatterbox only: an in-page audio Blob or File, under 20 MB and 3–30 seconds; the first 10 seconds are used |

Kokoro's voice list includes eight presets with `blend` metadata identifying the two voices mixed equally. For example, use `voice: 'af_heart_bella'`. Model and voice assets are Apache-2.0.

Chatterbox uses an MIT model and default voice, with approximately 1.5 GB of downloads. Use a desktop; preparation and CPU synthesis can take several minutes. Choose `device: 'webgpu'` on compatible hardware. It supports single-voice narration, with chunk timing captions. A speech-token limit error means the recording is incomplete; reduce `chunkSize` and retry.

To clone a voice through Playwright, select a local file and pass the File inside the page:

```js
await page.locator('#referenceAudio').setInputFiles('my-voice.wav');
const result = await page.evaluate(() => ttsRocks.generate({
  engine: 'chatterbox', device: 'webgpu', voice: 'reference',
  text: 'Welcome to this tutorial.', exaggeration: 0.5,
  referenceAudio: document.getElementById('referenceAudio').files[0]
}));
```

Use your own voice or one you have permission to use. The recording is decoded locally and is never uploaded. `voice: 'reference'` requires the file; omit both voice and referenceAudio to use Chatterbox's default voice. Copied instructions exclude recordings, so select the file again in the agent's browser or pass `--reference` to the CLI.

Results contain `id`, `engine`, `voice` (default voice for the request), `language`, `duration` in seconds, `sampleRate`, `channels`, `format`, `bytes`, actual `device` and model `quality`, and `cues`. Cue starts and ends use generated audio sample positions, including added gaps. Narration cues follow generated text chunks; dialogue cues follow speaker turns. They are not word alignment or speech-recognition validation. Use a separate alignment tool when your video requires word-level highlighting.

### Save files with Playwright

```js
await page.goto('https://tts.rocks/');
await page.waitForFunction(() => window.ttsRocks);
await page.evaluate(() => ttsRocks.ready());
const result = await page.evaluate(() => ttsRocks.generate({
  text: 'Welcome to this tutorial.', voice: 'af_aoede'
}));
const pending = page.waitForEvent('download');
await page.evaluate(id => ttsRocks.download(id, 'narration.wav'), result.id);
const download = await pending;
await download.saveAs('deliverables/narration.wav');
```

Use a context with downloads enabled. Start waiting for the download before triggering it. Save the file before closing the context. Blob URLs belong to the page session and cannot be fetched by another machine. Browser tools without JavaScript evaluation can use the labelled controls instead.

## Use the CLI helper

Download [tts-rocks.mjs](./tts-rocks.mjs) into your working folder. With Node.js 22 or later, install browser automation once in that folder:

```sh
npm install --no-save playwright
npx playwright install chromium
node tts-rocks.mjs --input narration.txt --voice af_aoede --output narration.wav
```

For expressive narration with your reference voice:

```sh
node tts-rocks.mjs --input narration.txt --engine chatterbox --device webgpu --reference my-voice.wav --exaggeration 0.5 --output narration.wav
```

Omit `--reference` for the default Chatterbox voice. Use `--device wasm` for CPU and increase `--timeout` for long recordings or slower machines. Chatterbox GPU requests use Chromium's full headless browser. A batch shares the reference selected with `--reference`.

The helper opens the website in headless Chromium, caches models in `~/.cache/tts-rocks/browser`, and saves WAV audio, VTT captions and JSON metadata beside the requested output. Use `--profile PATH` for another dedicated profile; do not use your daily browser profile or run two helpers with the same profile at once. Models are still loaded into memory at browser startup. CPU is the default for compatibility; use `--device auto` to allow available GPU acceleration.

Use `--voices --engine kokoro` to list voice IDs. Use `--request request.json --output narration.wav` with a JSON generation-options object for controls beyond the basic flags. Use `--site https://your-host/` for a self-hosted copy. Run `--help` for all flags. Progress goes to stderr and successful results to stdout as JSON. Errors exit nonzero. Existing output files are protected unless you supply `--force`.

### Generate named sections

Save this as `sections.json`:

```json
[
  { "id": "intro", "text": "Welcome to this tutorial.", "voice": "af_aoede" },
  { "id": "step-01", "text": "First, open the settings panel.", "voice": "af_aoede" }
]
```

```sh
node tts-rocks.mjs --batch sections.json --output-dir audio
```

Each section produces its own WAV, VTT and JSON. `manifest.json` lists the outputs and durations; timestamps in each section start at zero. Each section can use any generation options. CLI flags override corresponding section settings. IDs must be unique portable filenames using letters, digits, hyphens or underscores; avoid `manifest` and Windows device names. Sections run sequentially in one browser, reusing the loaded model when settings match.

To revise one passage, put its generation options in a request file and generate that file alone. Generation stops on failure; already saved sections remain on disk. Review or move those files before restarting a batch, or deliberately use `--force`. Split long scripts at paragraph boundaries; each section must fit the 5000-character limit.

## Optional local text editing

In the page, `ttsRocks.text.generate({text, action: "improve"})` returns an editable draft as `{text, action, model, license, device, dtype}`. Use `action: "summarize"` for a short summary. Calls never change the editor or generate speech. Review the draft before using it for narration; check names, numbers and meaning.

Qwen3-0.6B (Apache-2.0) requires WebGPU, downloads about 0.6–1 GB from Hugging Face on first use, and caches locally when space is available. Text stays on the device. Limit input to 3,000 characters; token-dense passages may need a shorter selection. An incomplete draft returns an error. Use `ttsRocks.text.status()` for progress and `ttsRocks.text.cancel()` to stop. Run text editing and speech generation sequentially. The **Local AI** button provides the same tools with an editable preview and explicit **Use this text** action.

## Troubleshooting

- **Function missing:** wait for `window.ttsRocks` and `ready()` inside the page. Check that your self-hosted copy includes the automation files.
- **First use takes time:** model and runtime downloads precede synthesis. Read the status and allow the CLI's default 15 minutes per recording; adjust `--timeout SECONDS` for slower devices.
- **GPU unavailable:** choose `--device wasm` or `device: "wasm"`; use Kokoro `quality: "q8"` for CPU.
- **Clipboard denied:** the panel shows a selected text box so you can copy manually.
- **Browser launch fails:** install Chromium with `npx playwright install chromium`. On Linux, Playwright may also require browser system dependencies.
- **Need a cloud voice or cloning:** use the studio controls and provide the key/reference file there. The direct API does not accept secrets or uploaded audio.
