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
|
# 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 a faster backend, metadata, generated cover art, transcription/speed/language options, better text cleanup, and clearer instructions. It also expects the qwen-tts server to be on different ports per model, so two server processes can run at once.
## 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`
- Output a single file or one per chapter
- Automatic metadata (title/artist/album tags, chapter track numbers) and a generated cover
- Two voice modes:
- Custom voice: pre-built speakers
- Voice clone: clone a voice from a `.wav` reference audio file
## Prerequisites
- Python 3.12
- ffmpeg
- Enough VRAM to run the 1.7B model (~6GB)
## Installation
Install ffmpeg and your python environment of choice, e.g. conda.
```bash
sudo pacman -S conda ffmpeg #Arch Linux
sudo apt-get install ffmpeg #Debian, conda must be installed separately
```
### Install Qwen3-TTS (Server)
```bash
conda create -n qwen3-tts python=3.12 -y
conda activate qwen3-tts
pip install -U qwen-tts
```
### Install the conversion script
```bash
git clone https://git.historia.vg/git/qwen3-audiobook-converter
cd qwen3-audiobook-converter
pip install -r requirements.txt
```
## Running the Qwen Gradio server and audiobook script
The audiobook script talks to a Qwen3-TTS Gradio server that is run using `qwen-tts-demo`. Add `--no-flash-attn` if FlashAttention isn't installed (see below). The script expects the custom voice model and base model to be on different ports depending on which you're using. The Qwen model(s) will automatically download.
Put your book files (epub, etc.) in the `input/` folder. Then run the script. The output goes to `output/`.
### Voice clone
```bash
conda activate qwen3-tts
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`.
## Optional: Using the `--faster` backend
Instead of the Qwen Gradio demos, `--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**.
**The `--faster` 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 transcripts must come from you — either by hand, or with the `tools/make_voices.py` helper (see below).
### Install
Install into the **same `qwen3-tts` conda environment** used for the Gradio server.
```bash
conda activate qwen3-tts
pip install "faster-qwen3-tts[demo]"
```
### Run the server
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
```
Single voice (the voice is named `default`):
```bash
python examples/openai_server.py \
--model Qwen/Qwen3-TTS-12Hz-1.7B-Base \
--ref-audio /absolute/path/to/reference.wav \
--ref-text "Transcript of the reference audio." \
--language English --port 8000
```
Multiple voices — create a `voices.json` mapping names to reference configurations:
```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"}
}
```
```bash
python examples/openai_server.py --voices voices.json --port 8000
```
### Generating voices.json (optional)
The `tools/make_voices.py` helper builds a `voices.json` for the server: it transcribes every `.wav` in a directory with using whisper (which is in the qwen3-tts environment). By default it puts voices.json into the input directory. Check the help with `-h` for more options.
```bash
python tools/make_voices.py path/to/wavs
```
### Run the converter
```bash
python audiobook.py --faster [--faster-voice NAME]
```
## 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. |
| `--faster` | Use a faster-qwen3-tts OpenAI-compatible server (up to 5x faster in certain cases). |
| `--faster-voice <name>` | Chooses a voice from voices.json when using `--faster` with multiple voices. |
| `--debug` | Troubleshooting: dump each chunk's raw audio and sent text to `debug/` and log every request. |
Other options and defaults are configured in `converter/config.py`
## Optional: FlashAttention for qwen-tts-demo server
This is **not** used with the `--faster` backend. The Gradio server tries to use FlashAttention 2 by default, but requires `--no-flash-attn` without it. On supported GPUs FlashAttention can give a modest speedup.
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. Or pip install a prebuilt wheel matching your torch / CUDA / Python / CXX11-ABI combination:
```bash
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/ (hosted at https://github.com/mjun0812/flash-attention-prebuild-wheels).
## Tips
Transcription affects the output a lot. Whisper is okay, but does not give perfect transcription. A manual transcription passed via `--transcription` is better.
Keep `CHUNK_SIZE_WORDS` small (default 40). Every API call is a single model generation: long generations lose prosody, can degrade into garbled audio, and text past the model's token limit is never spoken. If parts of a book sound flat, monotone, or garbled, the chunk size is the first thing to check.
`MIN_DELAY_BETWEEN_CHUNKS` only matters for hosted demos (rate limits); a local server needs no delay (default 0).
Manual transcription, imperfect whisper transcription, and `--no-transcription` each provide different results. Usually the most accurate transcription is the best, but sometimes `--no-transcription` can produce a flat tone that might be preferable for certain voices.
Even tiny amounts of pause between phrases in the sample audio can have a big impact. Try increasing or decreasing them.
Setting `--language` to the "wrong" language for English text can produce an accent. It is not as strong as cloning a voice with the desired accent.
The built-in "custom" voices are mediocre. I get *much* better results cloning anything.
## License
MIT
## Credits
- [Qwen3-Audiobook-Converter](https://github.com/WhiskeyCoder/Qwen3-Audiobook-Converter) by WhiskeyCoder.
- [Qwen3-TTS](https://github.com/QwenLM/Qwen3-TTS) voice model.
|