Read-along audiobooks from your own books

Echo

personal infra · in daily use · echo.clay-i.com — live site ↗

What
A pipeline that turns any epub/txt/pdf into a read-along audiobook — narration generated locally on my own GPU, sentence-level highlighting synced to the audio, streamed to any device through a small authenticated web reader.
Why it was hard
TTS models churn every few months, word-level sync normally demands a forced aligner, and real-time synthesis on a phone can't run the models worth listening to.
What I decided
Make generation offline and batch (render once, cache forever), put the TTS model behind a one-interface seam so models are swappable, and synthesize one clip per sentence — so each clip's measured duration simply is that sentence's timing. No aligner.
What happened
It runs my audiobook library. The reader is live behind auth, with a public-domain demo chapter public.

Shape of the system

Nothing here is real-time, and that is the first design decision: because a book is rendered once, on my own CUDA box, the model can be as heavy and as slow as quality demands. The current narrator is ZipVoice zero-shot cloning — one six-second reference clip drives an entire book — with Kokoro and the built-in system voice as alternates.

The seam

The TTS model is the part guaranteed to change, so it is the part behind an interface: TTSBackend, one class with a synthesize(text, voice, out_wav) method. Adding a model is a new backend file plus one registry line. The reader never knows — it only ever consumes manifest.json and audio.wav, so a book narrated by ZipVoice and a book narrated by Kokoro are indistinguishable to every layer above the seam. This is the same instinct as the OMS’s injected clock and the arbitrage monitor’s venue adapters: isolate the thing that varies behind one interface, and the system above it cannot tell the difference.

Highlighting for free

Read-along highlighting usually means a forced aligner — a second model whose job is to guess where each word lands in the audio. Echo sidesteps the problem structurally: text is chunked into sentences and synthesized one clip per sentence, so each clip’s measured duration is that sentence’s timing. Concatenate the clips, accumulate the durations into the manifest, and sentence-level sync is exact by construction. Word-level remains an optional future add; sentence-level turned out to be what reading along actually needs.

The reader is deliberately dumb

A single-file PWA player plus a small static server: Basic Auth (over TLS only), HTTP Range streaming so seeking works on large audio files, and no state beyond the packages on disk. The public demo follows one rule worth stating: a public page is distribution, so it only ever carries public-domain text and a voice I own.