Turn any text-based PDF into natural-sounding audio — locally, for free, with no cloud API.
VoiceForStory is a desktop application that converts PDF documents into audio files using Kokoro, an offline neural TTS engine. All processing runs on your own machine. No subscription, no usage limits, no data leaves your device.
Built for long-form reading: novels, study materials, research papers, and children's books.
| VoiceForStory | Cloud TTS (ElevenLabs, Google, etc.) | |
|---|---|---|
| Cost | Free | Pay per character |
| Privacy | Fully local | Text sent to servers |
| Voice quality | Neural (Kokoro) | Varies |
| Offline use | ✅ | ❌ |
| Open source | ✅ | ❌ |
Input
- PDF: extracts text using PyMuPDF with sorted layout reconstruction
- TXT: plain text files, chunked into fixed-size pseudo-pages so they flow through the same pipeline as PDFs
- Strips page numbers, blank/filler pages, and reading noise automatically
- Conservative chapter detection: requires an explicit structural marker (e.g. "Chapter 3", "Part II", a numbered/roman heading) before splitting — short or title-case lines alone won't trigger a split, to avoid over-splitting into false chapters
- Two-pass dehyphenation: PyMuPDF built-in flag + regex, with optional FlairScorer language-model pass
Voice and synthesis
- 7 built-in Kokoro voices: American and British, male and female
- Adjustable speaking speed (0.5× – 2.0×)
- Text humanisation: micro-pauses, breath markers, and light stutter at configurable intensity
- CPU and CUDA modes; auto-selects GPU when available
Output
- WAV or MP3 export
- Four split modes: per page, per chapter (heading detection), per N pages, or whole book as one file
- Optional loudness normalisation for consistent volume across files
- Optional warmth EQ (low-pass filter, configurable cutoff 3–20 kHz) to reduce TTS harshness
- MP3 bitrate selection: 128k / 192k / 256k / 320k
- Resumes interrupted jobs — existing files are skipped unless overwrite is enabled
- Opens output folder automatically on completion
Interface
- Dark-themed Tkinter desktop GUI, no browser required
- Each step is a collapsible section — click a header to expand or collapse it
- Live conversion log with per-chunk status
- Cancellable mid-conversion with clean temp-file removal
- Settings persist between sessions (
~/.voiceforstory_settings.json) - Dependency status shown at startup, based on the packages actually required to run; optional packages (torch, dehyphen) being absent is never reported as an error
Python: 3.10 (recommended via Miniforge — see Installation below). Tkinter must be available; it is included in standard Python installers and in Miniforge environments.
Core packages (required for all functionality):
pymupdf
pydub
kokoro
soundfile
numpy
Optional packages:
| Package | License | Purpose |
|---|---|---|
torch (CUDA build) |
BSD-style | GPU acceleration for faster synthesis |
dehyphen |
GPL-3.0 | Language-model-aware dehyphenation (downloads ~200 MB Flair model on first use) |
External binaries:
| Tool | Required for |
|---|---|
| FFmpeg + FFprobe | MP3 export only — not needed for WAV |
WAV export works without FFmpeg. When using the Miniforge installation path, FFmpeg is installed into the Conda environment and is available automatically. If FFmpeg is missing or a broken build is detected, the UI lets you browse to the binary manually (section 5).
The recommended path on Windows is Miniforge, a lightweight Conda distribution maintained by the conda-forge community. It gives you clean environment isolation and makes compiled dependencies like numpy, soundfile, and ffmpeg straightforward to install on Windows.
- Download the latest Windows installer from the Miniforge releases page.
- Run the installer and keep the default settings unless you have a specific reason to change them.
- Open Miniforge Prompt from the Start menu after installation.
- Optionally harden the default configuration:
conda config --set auto_activate_base false
conda config --set channel_priority strict
conda update -n base -y condaUsing strict channel priority reduces package-mixing issues when combining conda-forge and PyPI packages.
Create a dedicated environment rather than installing into base:
conda create -n storyvoice python=3.10 -y
conda activate storyvoiceInstall compiled dependencies through Conda first, then the rest through pip:
conda install -c conda-forge numpy pysoundfile ffmpeg -y
pip install -r requirements.txtInstalling numpy and pysoundfile via Conda before pip is often smoother on Windows because Conda handles the compiled binaries. If requirements.txt also lists them, that is fine — pip will see them already satisfied.
Installing FFmpeg through Conda in the same environment means ffmpeg and ffprobe are automatically on the path for MP3 export, with no manual binary configuration needed.
conda activate storyvoice
python voice_for_story.pyKokoro model weights download automatically from Hugging Face on first synthesis run (~300–500 MB depending on voice selected).
GPU inference can substantially improve throughput on long documents — roughly 300 pages within an hour in a well-configured local setup, depending on document complexity, voice settings, and export options.
CUDA setup on Windows involves the dependency chain of your GPU driver, the selected PyTorch build, and optionally the CUDA Toolkit. Treat it as optional optimisation work rather than a guaranteed one-command install.
For a carefully ordered install that keeps Torch, Kokoro, and CUDA dependencies consistent:
conda create -n storyvoice python=3.10 -y
conda activate storyvoice
conda install -c conda-forge numpy soundfile ffmpeg pip -y
conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia -y
pip install -r requirements.txtpython -c "import torch; print(torch.__version__); print(torch.cuda.is_available()); print(torch.version.cuda)"
python -c "import soundfile, numpy; print('core packages ok')"If torch.cuda.is_available() returns False, check nvidia-smi first to confirm your driver sees the GPU, then verify the installed Torch build matches your driver version before changing other packages.
In some Windows setups the CUDA Toolkit may still need a separate manual installation from NVIDIA, particularly if developer tools beyond the runtime bundled with PyTorch are required.
Note: The application runs on CPU without any CUDA configuration. WAV export on CPU is fully functional; GPU mode reduces synthesis time on long documents.
If you prefer not to use Conda, a standard virtual environment works for CPU-only use and WAV export:
git clone https://github.com/your-username/voiceforstory.git
cd voiceforstory
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
pip install -r requirements.txt
python voice_for_story.py(This runs the same entrypoint as the Conda path above — voice_for_story.py at the repo root.)
FFmpeg must be installed separately and either added to your PATH or configured manually in section 5 of the UI.
- Select source — browse to a text-based PDF or a plain
.txtfile. Page count (PDF) or word count (TXT) and file size appear below the path. - Output settings — choose a folder and format (WAV or MP3; MP3 requires FFmpeg).
- Split mode — pick how the source is divided into files:
- One file per page — one audio file per PDF page. Disabled for
.txtsources, since text files have no real page boundaries; the app falls back to another mode automatically if this was selected. - One file per chapter — heading detection groups pages into chapters (works for both PDF and TXT; deliberately conservative — it requires an explicit marker like "Chapter 3" and prefers merging over false splits)
- One file per N pages — fixed-size batches (configurable). For
.txtsources, "pages" are fixed-size text chunks rather than PDF pages. - Single file (whole book) — one audio file for the entire source
- One file per page — one audio file per PDF page. Disabled for
- Voice — select a Kokoro voice and speaking speed. Device mode defaults to Auto (uses GPU if available).
- FFmpeg (MP3 only) — validate FFmpeg is found, or browse to the executable manually.
- Dehyphenation — enable FlairScorer for books with heavy hyphenated line-breaks (academic texts, older typesetting). Choose language (
en,de,fr,es,it,nl,pl,cs, ormulti-v0). - Humanisation — adds natural-sounding micro-pauses and breath markers. Adjust intensity (0.0 = off, 1.0 = maximum).
- Audio post-processing — enable loudness normalisation and/or warmth EQ. EQ cutoff: 8000 Hz = warm, 12000 Hz = neutral, 20000 Hz = bypassed.
- Advanced — skip very short chunks (<50 chars), control overwrite behaviour.
- Convert — press ▶ Convert to Audio. Progress and per-chunk status appear in the log. Cancel at any time; in-progress temp files are cleaned up.
There are plenty more, but I found those likable.
| Voice ID | Accent | Gender | Character |
|---|---|---|---|
af_nicole |
American | Female | Warm |
af_heart |
American | Female | Bright |
af_bella |
American | Female | Smooth |
bf_emma |
British | Female | Clear |
bf_isabella |
British | Female | Rich |
am_adam |
American | Male | — |
bm_george |
British | Male | — |
VoiceForStory accepts text-based PDFs — documents where the text layer is embedded and selectable — and plain .txt files. PDF coverage includes most ebooks, reports, academic papers, and PDFs exported from word processors. TXT files are read as UTF-8 and chunked into fixed-size pseudo-pages so they run through the same cleanup and splitting pipeline as PDFs.
Not currently supported:
- Scanned PDFs without a text layer (image-only pages produce no output)
- Complex multi-column layouts may need manual review
- PDFs with heavy mathematical notation or tables may produce awkward audio
- Rich text formats other than plain
.txt(e.g..docx,.rtf,.epub) are not read directly — convert to PDF or plain text first
| Package | License | Purpose |
|---|---|---|
| Kokoro | Apache 2.0 | Neural TTS engine |
| misaki | Apache 2.0 | G2P engine used internally by Kokoro |
| PyMuPDF | AGPL 3.0 | PDF text extraction |
| pydub | MIT | Audio processing and export |
| soundfile | BSD | WAV file writing |
| numpy | BSD | Audio array handling |
| dehyphen | GPL 3.0 | Optional deep dehyphenation |
| eSpeak NG | GPL 3.0 | Bundled transitively via Kokoro → misaki → espeakng-loader; fallback phonemizer for out-of-dictionary words |
| FFmpeg | LGPL / GPL | External — MP3 encoding |
License note: three dependencies carry copyleft obligations: PyMuPDF (AGPL-3.0), dehyphen (GPL-3.0), and eSpeak NG (GPL-3.0, pulled in transitively by Kokoro). GPLv3 and AGPLv3 are mutually compatible by design (each license's §13 grants permission to combine with the other), so none of these conflict with each other. This is why the project is licensed AGPL-3.0 — see
LICENSE,docs/LICENSE-AND-DEPENDENCY-REVIEW.md, anddocs/THIRD-PARTY-NOTES.mdfor the full reasoning before packaging or distributing.
voiceforstory/
├── voice_for_story.py # Entrypoint — run this
├── storyvoice/ # Application package
│ ├── __init__.py # version/app-name constants
│ ├── constants.py # voice list, output options, regexes
│ ├── settings.py # settings load/save (~/.voiceforstory_settings.json)
│ ├── deps.py # optional-dependency detection, FFmpeg/GPU probing
│ ├── text_cleaning.py # filler/hyphenation cleanup, chapter detection, chunking
│ ├── extraction.py # PDF and TXT text extraction
│ ├── audio.py # Kokoro pipeline, post-processing/export
│ └── gui.py # Tkinter App class, collapsible sections
├── requirements.txt # Core dependencies
├── requirements-optional.txt # GPU and dehyphen extras
├── .gitignore
├── README.md
├── LICENSE-PENDING.md
└── docs/
├── RELEASE-CHECKLIST.md
├── THIRD-PARTY-NOTES.md
└── LICENSE-AND-DEPENDENCY-REVIEW.md
The settings file keeps its original .voiceforstory_settings.json name from before the module split, so upgrading from an earlier build doesn't lose saved settings.
This is a pre-release codebase. The application is functional; the following work remains before a tagged release:
- Split the single-file script into the
storyvoice/package (see Repository layout) - Collapsible GUI sections
- Plain
.txtinput support - Screenshots and sample audio output
-
setup.bat/setup.shone-step installer - GitHub Actions smoke test
- Final outbound license decision — AGPL-3.0 (see
LICENSEandLICENSE-PENDING.md) - Packaged binary builds (PyInstaller / Inno Setup)
- Expanded test coverage
Do not treat this as a stable API. File names, settings schema, and CLI behaviour may change before v0.1.0.
VoiceForStory is licensed under the GNU Affero General Public License
v3.0 (AGPL-3.0). See the LICENSE file at the repository root for the
full text.
This decision was driven by the project's dependency chain: PyMuPDF
(AGPL-3.0), dehyphen (GPL-3.0), and eSpeak NG (GPL-3.0, via Kokoro's
dependency chain) all carry copyleft terms that are compatible with each
other and with an AGPL-3.0 outbound license, but not with a permissive
(MIT/Apache/BSD) license without removing or replacing all three. See
docs/LICENSE-AND-DEPENDENCY-REVIEW.md and docs/THIRD-PARTY-NOTES.md for
the full reasoning, and LICENSE-PENDING.md for how this decision was
reached.
Practical note if you plan to package or distribute this app: AGPL-3.0 requires that anyone who receives the software — including over a network, if a hosted version is ever offered — can obtain the complete corresponding source code. This is not legal advice; consult a real license review before your first public tag if you're unsure how this applies to your distribution plans.
Bug reports, packaging feedback, and dependency review input are especially useful right now. Until v0.1.0 is tagged, treat all interfaces as unstable.
