Convert text EPUBs into EPUB 3 with Media Overlays via local Kokoro TTS
Project description
spantrack
Convert a text EPUB into a self-contained EPUB 3 that narrates itself and highlights each sentence as it reads — an open, fully offline take on Kindle's Immersion Reading.
What it does
Synchronized read-along narration, text that highlights in time with spoken audio, is effectively locked to Amazon's ecosystem and its paired audiobook purchases. spantrack builds the same thing from any DRM-free text EPUB, on your own machine: it voices the book with a local neural TTS model, wires the audio into standard EPUB 3 Media Overlays, and hands back a single *.narrated.epub that plays with correct highlighting in any Media-Overlay reader — Thorium on desktop, BookFusion on mobile. No account, no cloud, no network after a one-time model download.
Demo
https://github.com/user-attachments/assets/a1d52881-4d21-45b1-997e-f201dc9d56f8
Watch the demo — a spantrack read-along playing in Thorium.
How it works
The hard part of a read-along is keeping the highlight glued to the audio. Most tools synthesize or record a whole chapter and then run a forced aligner to guess where each sentence begins — which drifts (seconds of lag in comparable prior art). spantrack never aligns anything.
[!NOTE] Sync is exact by construction. Each sentence is synthesized on its own, the chapter audio is those sentences concatenated in order, and every SMIL clip offset is the running sum of real clip durations. The timestamp is the audio position, so there is nothing to drift.
The pipeline is one stage per module:
- Ingest — unzip a working copy, parse the OPF, and upgrade EPUB 2 to EPUB 3 (synthesizing
nav.xhtmlfrom the NCX) when needed. - Select — walk the spine, skip cover / nav / front-matter by
epub:typelandmarks, and print the choice;--include/--exclude/--pickoverride it. - Segment & tag — walk block elements, split each into sentences with pysbd, and wrap every sentence in
<span id="sNNNNN">, preserving all inline markup. - Normalize (TTS input only — the DOM is never touched) — expand numbers, dates, currency, roman numerals, footnote markers, and URLs; apply an optional per-book pronunciation lexicon.
- Synthesize — one sentence at a time through Kokoro-82M at a fixed voice, so the narrator stays consistent across the whole book. Output is content-addressed by
sha256(model · voice · speed · text), so a killed run resumes without re-synthesizing and identical text is never voiced twice. - Assemble — concatenate sentences with natural gaps and short edge fades, then encode one mono MP3 (or Opus) per chapter with ffmpeg.
- Overlay — emit one gapless SMIL per chapter, patch the OPF (media-overlay wiring,
media:duration/narrator), and inject highlight CSS. - Repack & validate — zip
mimetype-first, then optionally gate the build on epubcheck.
Publisher CSS and markup stay byte-for-byte identical outside the injected spans, so turning narration off leaves the book looking exactly like the source.
Install
pip install spantrack
spantrack's grapheme-to-phoneme step also needs spaCy's en_core_web_sm, which isn't published to PyPI — install the pinned wheel explicitly. Pinning keeps G2P offline and deterministic; otherwise misaki downloads a model at runtime.
pip install "en_core_web_sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl"
You also need:
- ffmpeg on your
PATH, to encode chapter audio. Not required for--dry-runor--oov-report. - A JRE, only for
--validate, which runs epubcheck on the output.
The first real conversion downloads the Kokoro weights once (a few hundred MB); after that it runs fully offline. CUDA is used automatically when present, otherwise synthesis falls back to CPU.
Usage
spantrack book.epub
This writes book.narrated.epub beside the input and a book.epub.spantrack/ work directory holding the sentence cache and per-chapter checkpoints. Re-running resumes from there; --clean discards it.
Handy flags:
--dry-run— print the narration set and a duration/size estimate, synthesizing nothing.--oov-report— list words the voice may mispronounce, as a lexicon skeleton to fill in.--voice af_heart/--speed 1.1/--lang en-us|en-gb— narrator, pace, and accent.--lexicon words.toml— per-book pronunciation overrides in misaki IPA.--include/--exclude/--pick— steer which documents get narrated.--audio-format opus/--bitrate 64k— codec and size.--granularity word— experimental word-by-word highlighting (timings drift; A/B a single chapter first).--validate— run epubcheck and fail the build on errors.--progress— a per-chapter progress bar (needspip install 'spantrack[progress]').
Run spantrack --help for the complete list.
Examples
The examples/ directory ships four hand-written sample books, plus short, commented Python scripts that drive the API. Convert one end to end:
spantrack examples/the-lighthouse-keeper.epub --validate
or just inspect the plan without synthesizing:
spantrack examples/the-pocket-anthology.epub --dry-run
examples/README.md walks through each book and script with captured output.
Status
[!WARNING] Beta. English only, one fixed-voice narrator, and word-mode highlighting is experimental. The output validates with epubcheck and plays correctly in Thorium; the mobile (BookFusion) target is not yet validated first-hand. Interfaces may change without notice.
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file spantrack-1.0.0.tar.gz.
File metadata
- Download URL: spantrack-1.0.0.tar.gz
- Upload date:
- Size: 10.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
abe0719a5711e90fc96bfd72f8877e1ddecfc6e7a5dc577950be7df3e48c2a8a
|
|
| MD5 |
f856e8ad83e5af30e54264b9b5a6659c
|
|
| BLAKE2b-256 |
748c0f872052df19c7839ffa76f3d181d761aaad6a23a5a62bf893fa3901529b
|
Provenance
The following attestation bundles were made for spantrack-1.0.0.tar.gz:
Publisher:
release.yml on ozefe/spantrack
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spantrack-1.0.0.tar.gz -
Subject digest:
abe0719a5711e90fc96bfd72f8877e1ddecfc6e7a5dc577950be7df3e48c2a8a - Sigstore transparency entry: 2101797282
- Sigstore integration time:
-
Permalink:
ozefe/spantrack@ec70096529a5591bb988ea891984dada0d83c47b -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/ozefe
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ec70096529a5591bb988ea891984dada0d83c47b -
Trigger Event:
push
-
Statement type:
File details
Details for the file spantrack-1.0.0-py3-none-any.whl.
File metadata
- Download URL: spantrack-1.0.0-py3-none-any.whl
- Upload date:
- Size: 81.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e4a16887f2a5dcb3dba37dd590e50151dad345838a98233325751cb0ba7829ce
|
|
| MD5 |
49e9b0831e34257d9cd377bb42e91227
|
|
| BLAKE2b-256 |
cf6ef62ef865e1bf63dee5c1e81fb48291e1b6f5af5f7e2b40724f2db6802777
|
Provenance
The following attestation bundles were made for spantrack-1.0.0-py3-none-any.whl:
Publisher:
release.yml on ozefe/spantrack
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spantrack-1.0.0-py3-none-any.whl -
Subject digest:
e4a16887f2a5dcb3dba37dd590e50151dad345838a98233325751cb0ba7829ce - Sigstore transparency entry: 2101797401
- Sigstore integration time:
-
Permalink:
ozefe/spantrack@ec70096529a5591bb988ea891984dada0d83c47b -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/ozefe
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ec70096529a5591bb988ea891984dada0d83c47b -
Trigger Event:
push
-
Statement type: