#!/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 [--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()