1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
|
# Qwen3 Audiobook Converter
Convert TXT, PDF, and EPUB files into audiobooks using the Qwen3-TTS voice model.
This builds upon [WhiskeyCoder/Qwen3-Audiobook-Converter](https://github.com/WhiskeyCoder/Qwen3-Audiobook-Converter) adding more output files, support for faster backends, metadata, generated cover art, transcription/speed/language options, better text cleanup, and clearer instructions. The converter supports three TTS backends, selected with `--backend`: the original Qwen3-TTS Gradio demos (`gradio`, default), [faster-qwen3-tts](https://github.com/andimarafioti/faster-qwen3-tts) (`faster`), and [audio.cpp](https://github.com/0xShug0/audio.cpp) (`audiocpp`) — the last of which can serve either the same Qwen3-TTS 1.7B model (Option 3) or any of audio.cpp's larger non-Qwen TTS families like Higgs Audio v3 4B, VoxCPM2, and IndexTTS-2/2.5 (Option 4).
## Overview
The converter sends text extracted from your books to a locally running Qwen3-TTS server and assembles the returned audio into a single audiobook file.
- Input: `.txt`, `.pdf`, or `.epub`
- Output: `.m4b`, `.mp3`, `.ogg`, or `.flac`
- Supports [qwen-tts](https://pypi.org/project/qwen-tts/), [faster-qwen3-tts](https://github.com/andimarafioti/faster-qwen3-tts), and [audio.cpp](https://github.com/0xShug0/audio.cpp) backend servers (audio.cpp can host any of its TTS model families, not just Qwen3-TTS)
- Output a single file or one per chapter
- Automatic metadata (title/artist/album tags, chapter track numbers) and a generated cover
- Clone voices from .wav reference files or use the built-in speaker in the CustomVoice model.
## Prerequisites
- Python 3.12
- ffmpeg
## Installation
Create a python 3.12 environment, clone the repo, and install the requirements.
```bash
conda create -n qwen3-tts python=3.12 -y
conda activate qwen3-tts
git clone https://git.historia.vg/git/qwen3-audiobook-converter
cd qwen3-audiobook-converter
pip install -r requirements.txt
```
Put your book files (epub, etc.) in the `input/` directory. The output goes to `output/`.
You need to install one of the following backends (see below for installation/usage)
| Backend | Description |
| -------------------------------------------------------------------- | ------------------------------------------------------ |
| [Qwen-TTS](https://pypi.org/project/qwen-tts/) | Gradio server released by Qwen |
| [Faster-Qwen-TTS](https://github.com/andimarafioti/faster-qwen3-tts) | Qwen server with 2-8x faster inference for NVidia GPUs |
| [audio.cpp](https://github.com/0xShug0/audio.cpp) | Newer C++ TTS backend that supports many recent models |
## Options
| Flag | Description |
| ----------------------------- | ------------------------------------------------------------------------------------------------ |
| `--format {mp3,m4b,ogg,flac}` | Output format (default `m4b`). `m4b` uses AAC audio and has built-in chapters. |
| `--clone <path>` | Reference audio (`wav`) for voice cloning. |
| `--transcription "..."` | Override whisper auto-transcription with manual audio transcript. |
| `--no-transcription` | Skip auto-transcription of the reference audio. |
| `--speed <n>` | Playback speed, pitch-preserving (`1.0` = normal). A normal-speed copy is also output. |
| `--single-file` | Merge all chapters into a single file (default: one file per chapter). `m4b` is always one file. |
| `--language <lang>` | Output language for the synthesized speech. Can add an accent even if the text is English. |
| `--backend {gradio,faster,audiocpp}` | TTS server to use (default `gradio`). `faster` and `audiocpp` require their server running first — see the backend sections above. With `audiocpp` the server may host any audio.cpp TTS model family (see Option 4). |
| `--voice <name>` | Voice to request from a server-side voice configuration (`--backend faster` or `audiocpp` only). Required for audio.cpp families without built-in speakers (everything except Qwen3-TTS). |
| `--chunk` | Force client-side chunking into `CHUNK_SIZE`-word requests. Only matters for `--backend audiocpp`, which otherwise sends each chapter as one request and lets the server chunk long text itself (may double-chunk); the `gradio` and `faster` backends always chunk. |
| `--model <id>` | `--backend audiocpp` only: audio.cpp server model entry id to use for this run. Overrides `AUDIOCPP_MODEL_ID` in `converter/config.py`, so a server hosting several lazily-loaded models (one `server.json`, see Option 4) can be used without editing config — pick the model per run. Leave unset to use the config id, or to auto-select when the server hosts exactly one entry. |
| `--debug` | Troubleshooting: dump each chunk's raw audio and sent text to `debug/` and log every request. |
Other options including backend server URLs/ports are configured in `converter/config.py`
## Backend Option 1: Qwen3-TTS
Install qwen-tts with pip:
```bash
conda activate qwen3-tts
pip install -U qwen-tts
```
Run the backend with `qwen-tts-demo`. Add `--no-flash-attn` if FlashAttention isn't installed (see below). Note that the Base model and CustomVoice model run on different ports.
### Voice clone
```bash
qwen-tts-demo Qwen/Qwen3-TTS-12Hz-1.7B-Base --ip 127.0.0.1 --port 7861 [--no-flash-attn]
```
Then in another terminal:
```bash
python audiobook.py --clone reference.wav
```
The reference `.wav` should be ~10-15 seconds (3 second minimum, 60 second maximum; ~15 seconds is ideal). Longer is **not** better.
Whisper (`faster_whisper` or `whisper`) is used automatically to transcribe the reference audio. Without a Whisper backend it falls back to x-vector-only cloning. Override with `--transcription "What the .wav says"` or skip transcription with `--no-transcription`.
### Custom voice (i.e. built-in voice)
```bash
conda activate qwen3-tts
qwen-tts-demo Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice --ip 127.0.0.1 --port 7860 [--no-flash-attn]
```
```bash
python audiobook.py
```
Change the voice settings in `converter/config.py`.
## Backend Option 2: faster-qwen-tts
`--backend faster` talks to the OpenAI-compatible server from [faster-qwen3-tts](https://github.com/andimarafioti/faster-qwen3-tts), which uses CUDA graph capture for roughly 5-10x faster inference with the same models. **It requires an NVIDIA GPU**.
Install into the **same `qwen3-tts` conda environment** used for qwen-tts.
```bash
conda activate qwen3-tts
pip install -U qwen-tts
pip install "faster-qwen3-tts[demo]"
```
### Voice Clone
**This backend always uses voice cloning**. The reference voice and language are configured on the **server**, not through the converter. The server does not transcribe reference audio itself, so do it manually or use the `tools/make_faster_voices_json.py` helper (see below).
The pip package does not include the server script, so clone the repository:
```bash
git clone https://github.com/andimarafioti/faster-qwen3-tts
cd faster-qwen3-tts
```
Create a `voices.json` mapping names to reference configurations (.wav to clone, transcript, language). Optionally run `python ./tools/make_faster_voices_json.py path/to/clone/wavs` to automatically create a `voices.json` using whisper to automatically transcribe the test audio.
```json
{
"default": {"ref_audio": "voice1.wav", "ref_text": "Transcript of voice 1.", "language": "English"},
"obama": {"ref_audio": "voice2.wav", "ref_text": "Transcript of voice 2.", "language": "English"}
}
```
Run the server
```bash
python examples/openai_server.py --voices voices.json --port 8000
```
Then from another terminal, run audiobook.py with `--backend faster`
```bash
python audiobook.py --backend faster [--voice NAME]
```
## Backend Option 3: audio.cpp with Qwen3-TTS
Build `audiocpp_server` for your platform and backend `(cuda, vulkan, hip, cpu)`. Check [audio.cpp's readme](https://github.com/0xShug0/audio.cpp) for details. I'm using one of the helper scripts:
```bash
git clone https://github.com/0xShug0/audio.cpp
cd audio.cpp
scripts/build_linux.sh --backend cuda --target audiocpp_server
```
Download the Qwen3-TTS GGUF packages (Base for cloning, CustomVoice for built-in speakers) with the python model manager script. This will download these to `./models`
```bash
python3 tools/model_manager_v2.py install qwen3_tts_1_7b_base_q8_0
python3 tools/model_manager_v2.py install qwen3_tts_1_7b_customvoice_q8_0
```
Create a `server.json` file. One server can host multiple models. Note that the `id:` field(s) must match `AUDIOCPP_MODEL_ID` and `AUDIOCPP_CLONE_MODEL_ID` in qwen3_ebook_converter's .`converter/config.py`. Optionally run `tools/make_audiocpp_server_json.py path/to/clone/wavs --audiocpp-dir /path/to/audio.cpp` to make `server.json` for you with automatic whisper transcription (see [Generating server.json](#generating-serverjson-with-make_audiocpp_server_json)).
Cloning voices can be configured per model entry (`voice_presets`) or once at the server level (`voice_dir` + a `prompt_text` file), which every hosted model can clone from. The generator uses the server-level form:
```json
{
"host": "127.0.0.1",
"port": 8080,
"backend": "cuda",
"lazy_load": false,
"voice_dir": "/path/to/clone/wavs",
"models": [
{
"id": "qwen",
"family": "qwen3_tts",
"path": "models/Qwen3-TTS-12Hz-1.7B-CustomVoice-GGUF",
"task": "tts",
"mode": "offline"
},
{
"id": "qwen3-clone",
"family": "qwen3_tts",
"path": "models/Qwen3-TTS-12Hz-1.7B-Base-GGUF",
"task": "tts",
"mode": "offline"
}
]
}
```
`voice_dir` points at a directory of `.wav` reference files plus a `prompt_text` file with one `<basename>|<transcript>` line per voice:
```
narrator|Transcript of the reference audio.
obama|Transcript of reference audio 2.
```
A request with `"voice": "narrator"` then clones `voice_dir/narrator.wav` using that transcript. (Per-entry `voice_presets` work too — see audio.cpp's server readme.)
Run the server. The `audiocpp_server` path will be slightly different depending on your platform and build options:
```bash
./build/linux-cuda-release/bin/audiocpp_server --config server.json
```
Then in a different terminal, run `audiobook.py`
```bash
# Built-in speaker
python audiobook.py --backend audiocpp
# Voice cloning
python audiobook.py --backend audiocpp --voice narrator
```
## Backend Option 4: audio.cpp with non-Qwen models
The same `audiocpp_server` can host most of audio.cpp's other TTS model families, including models that are larger or higher quality than Qwen3-TTS 1.7B. The converter detects the model family from the server at startup and adapts its requests automatically (language codes, style instructions, etc.), so no other converter settings change: point `AUDIOCPP_MODEL_ID` at the entry you want, start the server, and convert with `--backend audiocpp --voice <name>`.
One difference from Qwen3-TTS: **all of these families are clone-only** — they have no built-in speakers, so a reference voice must be configured on the server and selected with `--voice`. Running without `--voice` fails fast with a hint instead of synthesizing a random voice.
Supported families (see [audio.cpp's model list](https://github.com/0xShug0/audio.cpp#supported-models) for the full catalog):
| Family | Model | Languages | Notes |
| ------------------------------------------- | --------------------- | ------------------------ | ------------------------------------------------ |
| `higgs_audio_tts` | Higgs Audio v3 TTS 4B | 100+ | Largest TTS in audio.cpp; expressive, inline emotion/style control |
| `voxcpm2` | VoxCPM2-2B | 29 listed | 48 kHz output (others are 24 kHz); cloning + "ultimate clone" (audio + transcript) |
| `index_tts2` | IndexTTS-2 | zh, en | Top-tier cloning quality |
| `index_tts2` (package `index_tts2_5_*`) | IndexTTS-2.5 | zh, en, ja, es, ar | Multilingual IndexTTS variant |
The converter also works with families not in this table (Fish Audio, Chatterbox, DotTTS, OmniVoice, ...) through its generic profile: clone-only, voice from `--voice`, language detected by the model itself. Anything you can host in `audiocpp_server` with `"task": "tts"` should work, and `make_audiocpp_server_json.py` reads the full catalog from your audio.cpp checkout, so every TTS family audio.cpp supports is offered — not just the ones listed above.
### Install and run
Build `audiocpp_server` exactly as in Option 3 (same binary), then download a model package with audio.cpp's model manager from the audio.cpp checkout:
```bash
python3 tools/model_manager_v2.py install higgs_audio_tts_4b_q8_0
# or: python3 tools/model_manager_v2.py install voxcpm2_q8_0
# or: python3 tools/model_manager_v2.py install index_tts2_q8_0
# or: python3 tools/model_manager_v2.py install index_tts2_5_q8_0
```
Create a `server.json` hosting the model. Cloning voices go in a server-level `voice_dir` (a directory of `.wav` files plus a `prompt_text` file — see Option 3) so every hosted model can use them. Note that `id` must match `AUDIOCPP_MODEL_ID` in `converter/config.py` (set `AUDIOCPP_CLONE_MODEL_ID` to the same id — single-model servers use one entry for both):
```json
{
"host": "127.0.0.1",
"port": 8080,
"backend": "cuda",
"lazy_load": false,
"voice_dir": "/path/to/clone/wavs",
"models": [
{
"id": "higgs",
"family": "higgs_audio_tts",
"path": "models/Higgs-Audio-v3-TTS-4B-GGUF",
"task": "tts",
"mode": "offline"
}
]
}
```
One `server.json` can host several families at once (add more entries to `models` and set `"lazy_load": true` so each loads only on first use). Then pick the entry per run with `--model <id>` (see below).
Start the server and convert:
```bash
./build/linux-cuda-release/bin/audiocpp_server --config server.json
# In another terminal
python audiobook.py --backend audiocpp --voice narrator
# or, on a multi-model server:
python audiobook.py --backend audiocpp --model higgs --voice narrator
```
VRAM note: the 4B Higgs Audio Q8_0 package needs roughly 2.5x the memory of the 1.7B Qwen3-TTS packages; VoxCPM2-2B and IndexTTS-2 sit in between. BF16/F16 packages roughly double the footprint again.
### Generating server.json with make_audiocpp_server_json
`tools/make_audiocpp_server_json.py` reads the model catalog (`model_specs/*.json`) from a local audio.cpp checkout and offers every TTS family it supports as a multi-select checklist, so one `server.json` can host several lazily-loaded models. It transcribes your reference wavs with whisper and writes a server-level `voice_dir` + `prompt_text` file automatically.
```bash
# Interactive: point at your audio.cpp checkout and pick families from a checklist
python tools/make_audiocpp_server_json.py path/to/clone/wavs --audiocpp-dir /path/to/audio.cpp
# Non-interactive: host Higgs Audio + VoxCPM2 in one lazily-loaded server
python tools/make_audiocpp_server_json.py path/to/clone/wavs \
--audiocpp-dir /path/to/audio.cpp \
--families higgs_audio_tts,voxcpm2 \
--backend cuda --output server.json --force
```
The checkout can also be auto-detected (an `audio.cpp` directory next to/above your working directory, or the `AUDIOCPP_DIR` environment variable), so `--audiocpp-dir` is optional when you run from there. Pressing Enter at the checklist selects the default Qwen3-TTS flow (built-in speakers + cloning); otherwise enter comma-separated numbers for any combination of families. With more than one family the tool defaults to `"lazy_load": true` (models load on first use and stay in memory until the server exits — restart the server, or `POST /v1/tasks/unload_models`, before switching to a large model to free VRAM).
For a single hosted entry the tool offers to rewrite `AUDIOCPP_MODEL_ID`/`AUDIOCPP_CLONE_MODEL_ID` in `converter/config.py` to the new id so `audiobook.py` talks to it without manual editing. With several entries it instead prints the available ids — pick one per run with `--model` (or set `AUDIOCPP_MODEL_ID`). Transcripts matter a lot for cloning quality — fill in any empty lines in `prompt_text` by hand before starting the server.
### Language handling
`--language` works with these families too, adapted per family: IndexTTS sends a language code (`English` → `en`), while Higgs Audio and VoxCPM2 detect the language from the text themselves and omit the field. `--language Auto` never sends a language.
## Optional: FlashAttention for qwen-tts-demo server
FlashAttention provides a small speed boost on the `qwen-tts-demo` backend. It is **not** relevant with the `faster` or `audiocpp` backends, and switching to either of those will provide a bigger speed boost.
`qwen-tts-demo` server tries to use FlashAttention 2 by default and requires `--no-flash-attn` without it. You have two options to install FlashAttention in your python environment:
1. Build from source (takes absolutely forever). If you run out of memory, lower MAX_JOBS until you don't.
```bash
conda activate qwen3-tts
pip install ninja packaging psutil
MAX_JOBS=4 pip install --no-build-isolation flash-attn
```
2. pip install a prebuilt wheel matching your torch / CUDA / Python / CXX11-ABI combination:
```bash
conda activate qwen3-tts
python -c "import torch; print(torch.__version__, torch.version.cuda, torch._C._GLIBCXX_USE_CXX11_ABI)"
```
- [Official wheels](https://github.com/Dao-AILab/flash-attention/releases) - Pick `cp312` + matching `cuX` + `torchX.Y` + `cxx11abiTRUE/FALSE`
- [Third-party wheels](https://mjunya.com/flash-attention-prebuild-wheels/)
## Tips
Transcription affects the output a lot. Whisper does not always give perfect transcription. Manual transcription is better.
If you're cloning one language and outputting another language, `--no-transcription` will remove the accent. Alternatively, setting the "wrong" output `--language` can add an accent.
Even tiny amounts of pause between phrases in the sample audio can have a big impact. Try increasing or decreasing them or find a sample with different cadence.
## License
MIT
## Credits
- [Qwen3-Audiobook-Converter](https://github.com/WhiskeyCoder/Qwen3-Audiobook-Converter) by WhiskeyCoder. This project was originally built upon this.
- [Qwen3-TTS](https://github.com/QwenLM/Qwen3-TTS) voice model.
- [audio.cpp](https://github.com/0xShug0/audio.cpp) inference engine for the `audiocpp` backend.
|