aboutsummaryrefslogtreecommitdiff
path: root/audiobook.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-18 17:58:04 -0400
committerhistoria <historiavg@proton.me>2026-08-18 17:58:04 -0400
commite56754498f1c6b2a9dabb62529f783e73fae8e6b (patch)
tree20ea86a16d6c51c4bfd793cea56fd76ae9985671 /audiobook.py
parent50f1825f05972e3685c55beb10c288899959b2e5 (diff)
downloadtts-audiobook-generator-e56754498f1c6b2a9dabb62529f783e73fae8e6b.tar.gz
feat: language parameter for potential accent tuning
Diffstat (limited to 'audiobook.py')
-rwxr-xr-xaudiobook.py141
1 files changed, 141 insertions, 0 deletions
diff --git a/audiobook.py b/audiobook.py
new file mode 100755
index 0000000..34806f2
--- /dev/null
+++ b/audiobook.py
@@ -0,0 +1,141 @@
+#!/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:
+ pass
+
+from converter import config
+from converter.converter import AudiobookConverter, setup_directories, setup_logging
+from converter.tts import normalize_language
+
+
+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.py
+
+ # Use voice cloning with reference audio
+ python audiobook.py --clone path/to/reference.wav
+ """
+ )
+
+ parser.add_argument(
+ "--clone",
+ type=str,
+ metavar="PATH",
+ help=("Path to reference audio file for voice cloning (WAV format). "
+ "Passing this flag switches the converter to voice clone mode.")
+ )
+
+ parser.add_argument(
+ "--transcription",
+ 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 --transcription is provided.")
+ )
+
+ parser.add_argument(
+ "--language",
+ type=str,
+ default=None,
+ metavar="LANG",
+ help=("Output language for the synthesized speech, e.g. English, Japanese, "
+ "or Auto (language names and short codes like en/ja are accepted). "
+ "Defaults to the mode's setting in converter/config.py (English).")
+ )
+
+ parser.add_argument(
+ "--speed",
+ type=float,
+ default=1.0,
+ help="Playback speed factor for the final audiobook (1.0 = normal). Pitch-preserving."
+ )
+
+ parser.add_argument(
+ "--format",
+ choices=list(config.AUDIO_FORMATS),
+ default=config.AUDIO_FORMAT,
+ help=f"Output container format (default: {config.AUDIO_FORMAT}). m4b uses AAC audio."
+ )
+
+ parser.add_argument(
+ "--single-file",
+ action="store_true",
+ help=("Combine all chapters into a single audio file. By default books with "
+ "chapters (e.g. EPUB) are converted to one file per chapter. "
+ "Ignored for m4b, which is always a single file.")
+ )
+
+ args = parser.parse_args()
+
+ if args.speed <= 0:
+ parser.error(f"--speed must be a positive number (got {args.speed:g})")
+
+ if args.language is not None:
+ try:
+ args.language = normalize_language(args.language)
+ except ValueError as exc:
+ parser.error(str(exc))
+
+ if not args.clone and (args.transcription or args.no_transcription):
+ print("[WARNING] --transcription/--no-transcription "
+ "are ignored without --clone")
+
+ setup_logging()
+ setup_directories()
+
+ try:
+ converter = AudiobookConverter(
+ voice_mode=config.VOICE_MODE_CLONE if args.clone else config.VOICE_MODE_CUSTOM,
+ voice_clone_ref_audio=args.clone,
+ voice_clone_ref_text=args.transcription,
+ skip_transcription=args.no_transcription,
+ speed=args.speed,
+ single_file=args.single_file,
+ output_format=args.format,
+ language=args.language,
+ )
+ ok = converter.run()
+ except KeyboardInterrupt:
+ print("\n[WARNING] Shutdown requested by user")
+ sys.exit(130)
+ except Exception as exc:
+ print(f"[FATAL] Fatal error: {exc}")
+ traceback.print_exc()
+ sys.exit(1)
+
+ sys.exit(0 if ok else 1)
+
+
+if __name__ == "__main__":
+ main()