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
|
#!/usr/bin/env python3
"""
Qwen-Based Audiobook Converter
Converts TXT, PDF and EPUB files into audiobooks using a local Qwen3-TTS server.
Edit converter/config.py to change voice and processing settings.
License: MIT
"""
import argparse
import sys
import traceback
# Fix Windows console encoding for unicode output
if sys.platform == "win32":
try:
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
except AttributeError:
# Python < 3.7
import codecs
sys.stdout = codecs.getwriter("utf-8")(sys.stdout.buffer, "strict")
sys.stderr = codecs.getwriter("utf-8")(sys.stderr.buffer, "strict")
from converter.converter import AudiobookConverter, setup_directories, setup_logging
def main() -> None:
"""Entry point with argparse."""
parser = argparse.ArgumentParser(
description="Convert books to audiobooks using the Qwen3-TTS voice model",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Use custom voice (default - Vivian speaker)
python audiobook_converter.py
# Use voice cloning with reference audio
python audiobook_converter.py --voice-clone --voice-sample path/to/reference.wav
"""
)
parser.add_argument(
"--voice-clone",
action="store_true",
help="Use voice cloning mode instead of custom voice (requires --voice-sample)"
)
parser.add_argument(
"--voice-sample",
type=str,
help="Path to reference audio file for voice cloning (WAV format)."
)
parser.add_argument(
"--voice-sample-text",
type=str,
default=None,
help=("Transcript of the reference audio for in-context cloning (recommended for "
"highest quality). If omitted, a local Whisper backend is used if installed; "
"otherwise the converter falls back to x-vector-only mode.")
)
parser.add_argument(
"--no-transcription",
action="store_true",
help=("Skip automatic transcription of the reference audio (use x-vector-only "
"cloning). Ignored when --voice-sample-text is provided.")
)
parser.add_argument(
"--speed",
type=float,
default=1.0,
help="Playback speed factor for the final audiobook (1.0 = normal). Pitch-preserving."
)
args = parser.parse_args()
if args.speed <= 0:
parser.error(f"--speed must be a positive number (got {args.speed:g})")
if args.voice_clone:
if not args.voice_sample:
print("[ERROR] --voice-clone requires --voice-sample")
print('Usage: python audiobook_converter.py --voice-clone --voice-sample <path> [--voice-sample-text "..."]')
sys.exit(1)
elif args.voice_sample or args.voice_sample_text or args.no_transcription:
print("[WARNING] --voice-sample/--voice-sample-text/--no-transcription "
"are ignored without --voice-clone")
setup_logging()
setup_directories()
try:
converter = AudiobookConverter(
voice_mode="voice_clone" if args.voice_clone else "custom_voice",
voice_clone_ref_audio=args.voice_sample if args.voice_clone else None,
voice_clone_ref_text=args.voice_sample_text if args.voice_clone else None,
skip_transcription=args.no_transcription,
speed=args.speed,
)
converter.run()
except KeyboardInterrupt:
print("\n[WARNING] Shutdown requested by user")
except Exception as exc:
print(f"[FATAL] Fatal error: {exc}")
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()
|