a build log, with audio
79 EPUBs, 66 Russian and 13 English, 53 million characters — about 983 hours of narration if you read all of it aloud. Everything runs locally, so the marginal cost per book is electricity. The interesting part was not building the pipeline. It was discovering, repeatedly, that the automated checks were passing while the audio was wrong.
Six stages, each resumable. Chapters come from the EPUB's own table of contents; text is normalised into something a speech model will not mispronounce; chunks are synthesised, then verified by transcribing them back and comparing; audio is mastered to the broadcast levels audiobook stores expect and packaged as a single M4B with chapter markers.
Each pair is the same text through the same model. The left-hand clip is what the pipeline produced before the fix.
Russian stress changes meaning: за́мок is a castle, замо́к is a lock. Both clips were fed identical text with the stress marked explicitly. Chatterbox accepts the notation and stresses whatever it likes; ESpeech honours it.
Character error rate 58.5% against 0.0%. Chatterbox vocalises the plus signs: «И как эрба асф, это в это воро емя…». The combining acute U+0301 passes clean — but passing clean is not the same as being obeyed, which took a separate test to establish.
«Иоган Кеплер» is the name of a spacecraft, sitting mid-sentence. The model had learned that guillemets mark speech, so it paused as though a character had started talking.
Longest internal gap 0.83 s against 0.37 s. The fix could not be a blanket strip, because quotes around real speech should pause — so the library was measured: 15,747 quoted spans across 25 Russian books, 63% short name/title spans and 37% actual speech. Marks are now dropped only around the former.
The same sentence — deliberately built as a homograph trap: «Я из готов» (of the Goths) followed by «я уже готов» (I am ready), plus «замки» which is either locks or castles depending on stress.
This was the failure that exposed a hole in the whole verification approach: an ASR round trip transcribes за́мок and замо́к as the same string, so a 0.0% error rate proved the right words were spoken and said nothing about the stress. Wrong stress is only findable by ear.
Continuous passages from the actual books, mastered exactly as the delivered file would be — real pauses between sentences and paragraphs, broadcast levels, 48 kbps AAC.
F5/DiT architecture, with every stress mark supplied by RUAccent rather than guessed by the model. Character error rate 1.6%, no dropped numerals, and no internal pause over half a second. About 14 hours for a 13-hour book.
Lower error rate (1.0%) at slightly higher cost. Worth comparing: the two checkpoints sound close, but this one drops fewer words, which means fewer retries.
Flatter delivery, but the stress is fully correct and it runs 125× faster than real time — 51 minutes for a 13-hour book against 14 hours.
0.0% error rate across every test case. 90 minutes for a 9-hour book.
Error rate is measured by transcribing the generated audio and comparing it to the input text, so proper nouns inflate every row equally. RTF is the real-time factor: 0.5 means twice as fast as playback.
| model | language | error rate | RTF | 13-hour book |
|---|---|---|---|---|
| Silero v5 + RUAccent | Russian | 1.7% | 0.008 | 51 min |
| ESpeech-SFT | Russian | 1.6% | 0.77 | 11 h |
| ESpeech-RLV2 | Russian | 1.0% | 1.23 | 17 h |
| Chatterbox Multilingual | Russian | 3.3% | 2.98 | 41 h |
| Kokoro-82M | English | 0.0% | 0.055 | 90 min |
| Chatterbox (English) | English | 0.0% | 2.74 | 38 h |
| a commercial API | either | — | — | $75 |
The verification stage transcribes every generated chunk and compares it to the source. It is genuinely good at catching dropped and hallucinated words, which these models do silently and often. But it is blind in two directions that took a human ear to find.
It cannot hear stress. Speech recognition returns word identity, not prosody, so a perfect score says nothing about whether после was read as по́сле or после́. Four separate stress errors were reported by ear, none visible to the checks.
Averages hide short errors. One mangled word inside a 149-character chunk scores 5.4% — comfortably under any sensible threshold. The word in question was a number: “sixteen passengers died” became “sew passengers died”. The fix was to add checks that are not length-normalised, so a missing numeral fails on its own regardless of how long the surrounding text is.