Convert PDFs and EPUBs into MP3 audiobooks with a clean local web UI, multiple TTS backends, chapter export, and voice cloning.
4
stars
28
commits
Python
primary language
Mar 12, 2026
updated
Turn PDFs and EPUBs into spoken audio from a single local web app. Pick a page range, choose a TTS engine, preview what is available, and export either one MP3 or a ZIP of chapter files.
This repo is optimized for practical use, not as a framework. It gives you one place to:
input/ or direct uploadsMost TTS tools make you choose between flexibility and convenience. This app keeps both:
| Capability | Details |
|---|---|
| Input formats | PDF and EPUB |
| Source options | Upload from your machine or pick files from input/ |
| Output formats | Single MP3 or chapter ZIP |
| Save mode | Download immediately or save into output/ |
| Voice cloning | Upload reference audio and synthesize with XTTS |
| Chapter mode | Works for both PDF and EPUB |
| Progress tracking | Browser polls server-side job progress during conversion |
| Language filtering | UI can filter backends by English / Romanian |
| Backend | Type | Languages | Best For | First Run / Dependency Notes |
|---|---|---|---|---|
| Kokoro | Local | English | Best default local quality-to-speed balance | Requires espeak-ng; downloads model on first use |
| Piper | Local | English | Lightweight local ONNX voices and tunable pacing | Needs a .onnx model plus sidecar JSON in models/ |
| Supertonic | Local | English | ONNX-based local synthesis with simple controls | Downloads about 305MB on first use |
| Hugging Face | Local | English, Romanian | Trying HF TTS models locally | Downloads model weights on first use |
| SpeechT5 | Local | English | Multi-speaker preset voices | Downloads model, vocoder, and speaker embeddings |
| XTTS-v2 | Local | English + multilingual | Voice cloning with a reference sample | Large model download, reference audio required |
| XTTS-v2 Romanian | Local | Romanian | Romanian voice cloning | Large Romanian fine-tune download, reference audio required |
| Amazon Polly | Cloud | English in current UI | Reliable cloud voices and billing visibility | Requires valid AWS credentials |
| HF Inference API | Cloud | English, Romanian | Quick cloud inference experiments | Requires HF_TOKEN; free tier can rate-limit |
The shortest clean setup is two commands:
./scripts/bootstrap.sh
uv run python app.py
Then open http://localhost:1234.
If you want every optional backend as well:
./scripts/bootstrap.sh --all
uv run python app.py
./scripts/bootstrap.sh:
uv if missingffmpeg and espeak-nguvuv sync --frozen --dev.env from .env.example if neededIt currently supports:
apt-getIf you prefer not to use the bootstrap script, the equivalent manual flow is below.
uvmacOS or Ubuntu:
curl -LsSf https://astral.sh/uv/install.sh | sh
If you already use Homebrew on macOS:
brew install uv
This repo is set up for Python 3.12 because several TTS backends are still fragile on newer interpreters.
uv python install 3.12
The repo includes .python-version, so uv will use Python 3.12 automatically.
This project needs a couple of tools that are outside Python dependency management.
| Tool | Required? | Why |
|---|---|---|
ffmpeg | Yes | pydub uses it for MP3 import/export and audio format conversion |
espeak-ng | Required for Kokoro | Kokoro depends on it for phonemization |
piper CLI | Optional | Only needed if Piper falls back from the Python API to the CLI binary |
pydub relies on ffmpeg for MP3 import/export, and Kokoro relies on espeak-ng.
macOS:
brew install ffmpeg espeak-ng
If you want the optional Piper CLI available as a fallback:
brew install piper
Ubuntu / Debian:
sudo apt-get update
sudo apt-get install -y ffmpeg espeak-ng
If you want the optional Piper CLI available as a fallback:
sudo apt-get install -y piper
Quick sanity check:
ffmpeg -version
espeak-ng --version
uvFor the default app experience, including the web app, Kokoro, Polly support, and the fast test suite:
uv sync --frozen --dev
If you want every optional backend available locally:
uv sync --frozen --dev --extra all
| Extra | Adds |
|---|---|
piper | Piper Python backend support |
supertonic | Supertonic local ONNX backend |
huggingface | Local Transformers TTS backend |
xtts | XTTS / XTTS-RO voice cloning backends |
speecht5 | SpeechT5 multi-speaker backend |
ocr | EasyOCR fallback for scanned PDFs |
all | All optional backends above |
Examples:
uv sync --frozen --dev --extra piper --extra supertonic
uv sync --frozen --dev --extra xtts --extra ocr
cp .env.example .env
You do not need to fill everything in up front. For a first successful run, the minimum usually is:
TTS_BACKEND=kokorouv run python app.py
Open:
http://localhost:1234
input/ and pick it from the dropdown.all or choose a range such as 1-10.uv Commands./scripts/bootstrap.sh
./scripts/bootstrap.sh --all
uv sync --frozen --dev
uv sync --frozen --dev --extra all
uv lock
uv sync --frozen --dev
uv run python app.py
uv run pytest tests/test_smoke.py -q
Use uv lock only when you intentionally want to update the pinned dependency set.
The full configuration surface lives in ./.env.example.
Recommended workflow:
cp .env.example .env
Then edit only the values you care about.
| Setting | Why It Matters |
|---|---|
TTS_BACKEND | Sets the default backend shown on page load |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION | Required for Polly readiness and conversion |
PIPER_MODEL | Points to the default Piper voice model |
HF_TOKEN | Required for HF cloud inference |
KOKORO_VOICE / KOKORO_SPEED | Sets the default local voice and pace |
SUPERTONIC_* | Sets default Supertonic voice, language, speed, and silence |
.env.example, the app does not currently rely on it..env.example uses safe placeholders and code defaults only..env only..env automatically through python-dotenv.The main workflow is a three-step wizard:
Source
.pdf or .epubinput/Pages
all, 5, or 1-10Engine & Voice
If chapters are detected, you can enable Split into chapter files.
Important behavior:
Enable Save to output/ folder if you want the results kept in the repo instead of downloaded directly by the browser.
Use the Voice Cloning page to upload short reference audio:
Those files are stored in reference_audio/ and become selectable when using XTTS backends.
output/Generated filenames include:
.
├── app.py
├── pyproject.toml
├── .python-version
├── scripts/
│ └── bootstrap.sh
├── templates/
│ └── index.html
├── input/
├── output/
├── reference_audio/
├── tests/
├── models/
├── .env.example
└── uv.lock
What each piece does:
app.py: the full Flask app, document extraction pipeline, backend dispatch, and routespyproject.toml: dependency definitions, optional backend extras, and pytest config for uv.python-version: pins the interpreter version used by uvscripts/bootstrap.sh: one-command local setup for supported macOS and Ubuntu/Debian environmentstemplates/index.html: the UI, styling, and browser-side JavaScript in one templateinput/: books you want selectable from the dropdownoutput/: saved MP3s and chapter ZIP directoriesreference_audio/: uploaded voice-cloning samplesmodels/: Piper .onnx models and their .json sidecarstests/: fast regression coverage plus focused route and extraction testsuv.lock: fully resolved dependency lockfile generated by uvThe app is intentionally simple:
Flask server
Inline frontend
templates/index.htmlfetch/progress/<job_id>Document processing
PyMuPDF / fitzSpeech synthesis
Local cache management
.cache/| Route | Purpose |
|---|---|
/ | Main app UI |
/convert | Starts a conversion job and returns MP3, ZIP, or saved-result JSON |
/progress/<job_id> | Current extraction / synthesis progress |
/api/backend-status | Readiness notes for local and cloud backends |
/api/pdf-info | Page and chapter metadata for PDFs and EPUBs |
/api/reference-voices | Lists uploaded XTTS reference samples |
/api/upload-reference | Uploads a new reference audio file |
Before synthesis, the app tries to make extracted book text sound better:
[aside]This is a practical cleanup layer, not a perfect document normalizer.
Run the fast regression suite:
uv run pytest tests/test_smoke.py -q
The current tests focus on:
Check:
espeak-ng is installed and available on PATHCheck:
ffmpeg is installedffmpeg -versionCheck:
uv sync --extra piper has been run if you want the Python Piper backendpiper is installed and available on PATHpiper --helpCheck:
AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_REGIONThe app validates Polly readiness with a lightweight AWS call, so auth or network problems show up before conversion starts.
Check:
.onnx file exists.json exists next to itPIPER_BINARY points to a real binary if the Python API falls back to CLI--noise-scale and --noise-wCheck:
That usually means the PDF is scanned pages rather than embedded text. This app does not currently perform OCR.
Check:
HF_TOKEN is setCheck:
reference_audio/Several backends download large assets the first time you use them:
If a backend is slow the first time, that is expected.
Model caches are redirected into a repo-local .cache/ directory so experiments stay self-contained.
For Polly conversions, the app tracks billed character counts and estimated cost from backend pricing constants. This is useful for quick budgeting, but it is not a billing statement.
If you want a practical starting point:
output/reference_audio/Do not commit your real .env.
Keep:
only in local, untracked environment files.
This project is licensed under the GNU Affero General Public License v3.0.
That means if you modify it and make the modified version available to users over a network, you must also make the corresponding source available under the same license.
See LICENSE for the full text.
28 commits
Python
65.6%
HTML
33.2%
Shell
1.2%
Convert PDFs and EPUBs into MP3 audiobooks with a clean local web UI, multiple TTS backends, chapter export, and voice cloning.
4
stars
28
commits
Python
primary language
Mar 12, 2026
updated
Turn PDFs and EPUBs into spoken audio from a single local web app. Pick a page range, choose a TTS engine, preview what is available, and export either one MP3 or a ZIP of chapter files.
This repo is optimized for practical use, not as a framework. It gives you one place to:
input/ or direct uploadsMost TTS tools make you choose between flexibility and convenience. This app keeps both:
| Capability | Details |
|---|---|
| Input formats | PDF and EPUB |
| Source options | Upload from your machine or pick files from input/ |
| Output formats | Single MP3 or chapter ZIP |
| Save mode | Download immediately or save into output/ |
| Voice cloning | Upload reference audio and synthesize with XTTS |
| Chapter mode | Works for both PDF and EPUB |
| Progress tracking | Browser polls server-side job progress during conversion |
| Language filtering | UI can filter backends by English / Romanian |
| Backend | Type | Languages | Best For | First Run / Dependency Notes |
|---|---|---|---|---|
| Kokoro | Local | English | Best default local quality-to-speed balance | Requires espeak-ng; downloads model on first use |
| Piper | Local | English | Lightweight local ONNX voices and tunable pacing | Needs a .onnx model plus sidecar JSON in models/ |
| Supertonic | Local | English | ONNX-based local synthesis with simple controls | Downloads about 305MB on first use |
| Hugging Face | Local | English, Romanian | Trying HF TTS models locally | Downloads model weights on first use |
| SpeechT5 | Local | English | Multi-speaker preset voices | Downloads model, vocoder, and speaker embeddings |
| XTTS-v2 | Local | English + multilingual | Voice cloning with a reference sample | Large model download, reference audio required |
| XTTS-v2 Romanian | Local | Romanian | Romanian voice cloning | Large Romanian fine-tune download, reference audio required |
| Amazon Polly | Cloud | English in current UI | Reliable cloud voices and billing visibility | Requires valid AWS credentials |
| HF Inference API | Cloud | English, Romanian | Quick cloud inference experiments | Requires HF_TOKEN; free tier can rate-limit |
The shortest clean setup is two commands:
./scripts/bootstrap.sh
uv run python app.py
Then open http://localhost:1234.
If you want every optional backend as well:
./scripts/bootstrap.sh --all
uv run python app.py
./scripts/bootstrap.sh:
uv if missingffmpeg and espeak-nguvuv sync --frozen --dev.env from .env.example if neededIt currently supports:
apt-getIf you prefer not to use the bootstrap script, the equivalent manual flow is below.
uvmacOS or Ubuntu:
curl -LsSf https://astral.sh/uv/install.sh | sh
If you already use Homebrew on macOS:
brew install uv
This repo is set up for Python 3.12 because several TTS backends are still fragile on newer interpreters.
uv python install 3.12
The repo includes .python-version, so uv will use Python 3.12 automatically.
This project needs a couple of tools that are outside Python dependency management.
| Tool | Required? | Why |
|---|---|---|
ffmpeg | Yes | pydub uses it for MP3 import/export and audio format conversion |
espeak-ng | Required for Kokoro | Kokoro depends on it for phonemization |
piper CLI | Optional | Only needed if Piper falls back from the Python API to the CLI binary |
pydub relies on ffmpeg for MP3 import/export, and Kokoro relies on espeak-ng.
macOS:
brew install ffmpeg espeak-ng
If you want the optional Piper CLI available as a fallback:
brew install piper
Ubuntu / Debian:
sudo apt-get update
sudo apt-get install -y ffmpeg espeak-ng
If you want the optional Piper CLI available as a fallback:
sudo apt-get install -y piper
Quick sanity check:
ffmpeg -version
espeak-ng --version
uvFor the default app experience, including the web app, Kokoro, Polly support, and the fast test suite:
uv sync --frozen --dev
If you want every optional backend available locally:
uv sync --frozen --dev --extra all
| Extra | Adds |
|---|---|
piper | Piper Python backend support |
supertonic | Supertonic local ONNX backend |
huggingface | Local Transformers TTS backend |
xtts | XTTS / XTTS-RO voice cloning backends |
speecht5 | SpeechT5 multi-speaker backend |
ocr | EasyOCR fallback for scanned PDFs |
all | All optional backends above |
Examples:
uv sync --frozen --dev --extra piper --extra supertonic
uv sync --frozen --dev --extra xtts --extra ocr
cp .env.example .env
You do not need to fill everything in up front. For a first successful run, the minimum usually is:
TTS_BACKEND=kokorouv run python app.py
Open:
http://localhost:1234
input/ and pick it from the dropdown.all or choose a range such as 1-10.uv Commands./scripts/bootstrap.sh
./scripts/bootstrap.sh --all
uv sync --frozen --dev
uv sync --frozen --dev --extra all
uv lock
uv sync --frozen --dev
uv run python app.py
uv run pytest tests/test_smoke.py -q
Use uv lock only when you intentionally want to update the pinned dependency set.
The full configuration surface lives in ./.env.example.
Recommended workflow:
cp .env.example .env
Then edit only the values you care about.
| Setting | Why It Matters |
|---|---|
TTS_BACKEND | Sets the default backend shown on page load |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION | Required for Polly readiness and conversion |
PIPER_MODEL | Points to the default Piper voice model |
HF_TOKEN | Required for HF cloud inference |
KOKORO_VOICE / KOKORO_SPEED | Sets the default local voice and pace |
SUPERTONIC_* | Sets default Supertonic voice, language, speed, and silence |
.env.example, the app does not currently rely on it..env.example uses safe placeholders and code defaults only..env only..env automatically through python-dotenv.The main workflow is a three-step wizard:
Source
.pdf or .epubinput/Pages
all, 5, or 1-10Engine & Voice
If chapters are detected, you can enable Split into chapter files.
Important behavior:
Enable Save to output/ folder if you want the results kept in the repo instead of downloaded directly by the browser.
Use the Voice Cloning page to upload short reference audio:
Those files are stored in reference_audio/ and become selectable when using XTTS backends.
output/Generated filenames include:
.
├── app.py
├── pyproject.toml
├── .python-version
├── scripts/
│ └── bootstrap.sh
├── templates/
│ └── index.html
├── input/
├── output/
├── reference_audio/
├── tests/
├── models/
├── .env.example
└── uv.lock
What each piece does:
app.py: the full Flask app, document extraction pipeline, backend dispatch, and routespyproject.toml: dependency definitions, optional backend extras, and pytest config for uv.python-version: pins the interpreter version used by uvscripts/bootstrap.sh: one-command local setup for supported macOS and Ubuntu/Debian environmentstemplates/index.html: the UI, styling, and browser-side JavaScript in one templateinput/: books you want selectable from the dropdownoutput/: saved MP3s and chapter ZIP directoriesreference_audio/: uploaded voice-cloning samplesmodels/: Piper .onnx models and their .json sidecarstests/: fast regression coverage plus focused route and extraction testsuv.lock: fully resolved dependency lockfile generated by uvThe app is intentionally simple:
Flask server
Inline frontend
templates/index.htmlfetch/progress/<job_id>Document processing
PyMuPDF / fitzSpeech synthesis
Local cache management
.cache/| Route | Purpose |
|---|---|
/ | Main app UI |
/convert | Starts a conversion job and returns MP3, ZIP, or saved-result JSON |
/progress/<job_id> | Current extraction / synthesis progress |
/api/backend-status | Readiness notes for local and cloud backends |
/api/pdf-info | Page and chapter metadata for PDFs and EPUBs |
/api/reference-voices | Lists uploaded XTTS reference samples |
/api/upload-reference | Uploads a new reference audio file |
Before synthesis, the app tries to make extracted book text sound better:
[aside]This is a practical cleanup layer, not a perfect document normalizer.
Run the fast regression suite:
uv run pytest tests/test_smoke.py -q
The current tests focus on:
Check:
espeak-ng is installed and available on PATHCheck:
ffmpeg is installedffmpeg -versionCheck:
uv sync --extra piper has been run if you want the Python Piper backendpiper is installed and available on PATHpiper --helpCheck:
AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_REGIONThe app validates Polly readiness with a lightweight AWS call, so auth or network problems show up before conversion starts.
Check:
.onnx file exists.json exists next to itPIPER_BINARY points to a real binary if the Python API falls back to CLI--noise-scale and --noise-wCheck:
That usually means the PDF is scanned pages rather than embedded text. This app does not currently perform OCR.
Check:
HF_TOKEN is setCheck:
reference_audio/Several backends download large assets the first time you use them:
If a backend is slow the first time, that is expected.
Model caches are redirected into a repo-local .cache/ directory so experiments stay self-contained.
For Polly conversions, the app tracks billed character counts and estimated cost from backend pricing constants. This is useful for quick budgeting, but it is not a billing statement.
If you want a practical starting point:
output/reference_audio/Do not commit your real .env.
Keep:
only in local, untracked environment files.
This project is licensed under the GNU Affero General Public License v3.0.
That means if you modify it and make the modified version available to users over a network, you must also make the corresponding source available under the same license.
See LICENSE for the full text.
28 commits
Python
65.6%
HTML
33.2%
Shell
1.2%