diff options
| author | historia <historiavg@proton.me> | 2026-08-28 14:57:48 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-28 14:57:48 -0400 |
| commit | db38085d07ce75f8961eecdc1919e98748254c53 (patch) | |
| tree | beaea2ba84de05bfc2fa90e9b92a24568d3b4cb9 /audiobook.py | |
| parent | afb2c2d5b297c5aa28bcced0e3f90e207d799c2a (diff) | |
| download | tts-audiobook-generator-db38085d07ce75f8961eecdc1919e98748254c53.tar.gz | |
refactor: overhaul config.py, remove cli default options
Diffstat (limited to 'audiobook.py')
| -rwxr-xr-x | audiobook.py | 93 |
1 files changed, 55 insertions, 38 deletions
diff --git a/audiobook.py b/audiobook.py index 811ffdf..5da78ae 100755 --- a/audiobook.py +++ b/audiobook.py @@ -7,7 +7,8 @@ Run with no arguments in a terminal for the full TUI (set up backends, process the input directory); pass flags to script a conversion directly. --input-file/--output-file convert one individual book file instead of a directory (the two flag pairs are mutually exclusive). Edit -app/converter/config.py to change voice and processing settings. +app/converter/config.py for server URLs and processing settings; the TTS +backend, model and voice are chosen per run with CLI flags. """ import argparse @@ -48,6 +49,7 @@ from converter.clients import ( VOICE_MODE_CLONE, VOICE_MODE_CUSTOM, VOICE_MODE_DESIGN, + is_builtin_speaker, normalize_language, ) from converter.converter import ( @@ -72,7 +74,7 @@ def resolve_book_path(path: Path) -> Path: return resolved if resolved.is_absolute() else _BASE_DIR / resolved -def convert(backend: str = None, voice: str = None, clone: str = None, +def convert(backend: str, voice: str = None, clone: str = None, transcription: str = None, no_transcription: bool = False, language: str = None, speed: float = None, single_file: bool = False, output_format: str = None, debug: bool = None, @@ -85,14 +87,14 @@ def convert(backend: str = None, voice: str = None, clone: str = None, """Run one conversion pass with explicit options (used by the CLI and hub). Returns the process exit code (0 on success, 1 on failure, 130 on - Ctrl-C). BACKEND defaults to config.BACKEND, OUTPUT_FORMAT to - config.AUDIO_FORMAT. LANGUAGE is already-normalized where required. - INPUT_DIR/OUTPUT_DIR override the configured INPUT_DIR/OUTPUT_DIR - folders when given (relative paths resolve against the project - root); SPEED/DEBUG default to the config.SPEED/config.DEBUG - settings. API_URL, when given, overrides the configured server URL - for the selected backend (used by the hub's "[remote]" entries and - --api-url). + Ctrl-C). BACKEND is required (the CLI flag or the hub's Generate form + supplies it); OUTPUT_FORMAT defaults to config.AUDIO_FORMAT. LANGUAGE + is already-normalized where required. INPUT_DIR/OUTPUT_DIR override the + configured INPUT_DIR/OUTPUT_DIR folders when given (relative paths + resolve against the project root); SPEED/DEBUG default to the + config.SPEED/config.DEBUG settings. API_URL, when given, overrides the + configured server URL for the selected backend (used by the hub's + "[remote]" entries and --api-url). INPUT_FILE converts a single book file instead of scanning the input folder (the CLI validates it and resolves relative paths @@ -109,7 +111,8 @@ def convert(backend: str = None, voice: str = None, clone: str = None, the overwrite prompts are not asked again) wire the conversion into the TUI run view; without them everything behaves like the CLI. """ - backend = backend or config.BACKEND + if backend is None: + raise ValueError("backend is required (pass --backend)") output_format = output_format or config.AUDIO_FORMAT speed = config.SPEED if speed is None else speed debug = config.DEBUG if debug is None else debug @@ -221,8 +224,13 @@ def main() -> None: if interactive: from ui import hub sys.exit(hub.run()) - # Non-interactive with no args: a default conversion run (cron/etc). - sys.exit(convert()) + # Non-interactive with no args: there is no default backend/model/ + # voice anymore, so a scripted run must say what it wants. + print("[ERROR] No --backend given. Scripted (non-interactive) runs " + "must pass --backend plus the backend's voice/model flags " + "(see --help); run with no arguments in a terminal for the " + "TUI.") + sys.exit(2) parser = argparse.ArgumentParser( description="Convert books to audiobooks using a local TTS server", @@ -242,8 +250,8 @@ Examples: python audiobook.py --backend audiocpp --model Qwen3-TTS-12Hz-1.7B-VoiceDesign-GGUF \\ --instructions "A warm adult female narrator with a British accent" - # Use the qwen-tts demo server with a custom voice - python audiobook.py --backend qwen + # Use the qwen-tts demo server with a built-in speaker + python audiobook.py --backend qwen --voice Vivian # Use the qwen-tts demo server with voice cloning from reference audio python audiobook.py --backend qwen --clone path/to/reference.wav @@ -334,26 +342,23 @@ Examples: ) parser.add_argument( "--backend", choices=[BACKEND_AUDIOCPP, BACKEND_QWEN, BACKEND_FASTER], - default=config.BACKEND, + required=True, help=("TTS server to talk to: the qwen-tts demo server (qwen), the " "faster-qwen3-tts OpenAI-compatible server (faster), or an " "audio.cpp audiocpp_server (audiocpp) hosting any of its TTS " "model families — Qwen3-TTS, Higgs Audio, VoxCPM2, IndexTTS2, " - "and more. Defaults to the BACKEND setting in " - "app/converter/config.py (audiocpp).") + "and more.") ) parser.add_argument( "--voice", type=str, default=None, metavar="NAME", - help=("Voice to request. faster: a key in the server's voices.json " - "('default' when it was started with --ref-audio). audiocpp: " - "the name of the voice for the selected model entry — for the " - "Qwen3-TTS CustomVoice entry a built-in speaker (e.g. Vivian, " - "Ryan, Uncle Fu), for every other family a voice_preset or " - "voice_dir entry (cloning). When AUDIOCPP_CLONE_MODEL_ID " - "names a second server entry (typically the Qwen Base model), " - "a non-speaker --voice reroutes to it. Not used by the qwen " - "backend (use app/converter/config.py SPEAKER or --clone " - "there).") + help=("Voice to request. faster: (required) a key in the server's " + "voices.json. audiocpp: the name of the voice for the " + "selected model entry — for the Qwen3-TTS CustomVoice entry " + "a built-in speaker (e.g. Vivian, Ryan, Uncle Fu; required " + "there), for every other family a voice_preset or voice_dir " + "entry (cloning). qwen: a built-in CustomVoice speaker name " + "(required for built-in-speaker runs; use --clone or " + "--instructions for the other voice modes).") ) parser.add_argument( "--debug", action="store_true", @@ -365,12 +370,11 @@ Examples: parser.add_argument( "--model", type=str, default=None, metavar="ID", help=("audio.cpp server model entry id to use for this run " - "(--backend audiocpp only). Overrides AUDIOCPP_MODEL_ID in " - "app/converter/config.py, which is useful for a server hosting " + "(--backend audiocpp only). Required when the server hosts " "several lazily-loaded models: generate one server.json with " "backends.audiocpp, then pick the model per run with --model. " - "Leave unset to use the config id, or to auto-select when the " - "server hosts exactly one entry.") + "Leave unset to auto-select when the server hosts exactly one " + "entry.") ) parser.add_argument( "--instructions", type=str, default=None, metavar="TEXT", @@ -380,8 +384,7 @@ Examples: "e.g. Qwen3-TTS VoiceDesign) and optional style/delivery " "control elsewhere; with qwen it selects the VoiceDesign " "model and describes the voice to synthesize with, e.g. " - "'A warm adult female narrator with a British accent' " - "(defaulting to INSTRUCT in app/converter/config.py).") + "'A warm adult female narrator with a British accent'.") ) parser.add_argument( "--option", action="append", type=str, default=None, metavar="KEY=VALUE", @@ -485,6 +488,9 @@ Examples: print("[WARNING] --language is ignored with --backend faster: language " "is configured on the server (see README)") args.language = None + if not args.voice: + parser.error("--backend faster requires --voice: a key in the " + "server's voices.json (see README)") elif args.backend == BACKEND_AUDIOCPP: if args.clone: print("[WARNING] --clone is ignored with --backend audiocpp: cloning " @@ -503,10 +509,15 @@ Examples: except ValueError as exc: parser.error(str(exc)) else: - if args.voice is not None: - parser.error("--voice requires --backend faster or audiocpp; the " - "qwen backend uses built-in speakers " - "(app/converter/config.py SPEAKER) or --clone") + # qwen: --voice names a built-in CustomVoice speaker (the only + # voice mode that needs one; --clone and --instructions pick the + # Base/VoiceDesign models instead). + if args.voice is not None and not is_builtin_speaker(args.voice): + parser.error(f"--backend qwen: --voice {args.voice!r} is not a " + "built-in speaker (want one of: Vivian, Serena, " + "Uncle_Fu, Dylan, Eric, Ryan, Aiden, Ono_Anna, " + "Sohee); use --clone or --instructions for the " + "other voice modes") if args.language is not None: try: args.language = normalize_language(args.language) @@ -516,6 +527,12 @@ Examples: if not args.clone and (args.transcription or args.no_transcription): print("[WARNING] --transcription/--no-transcription " "are ignored without --clone") + if not args.clone and not (args.instructions or "").strip() \ + and not args.voice: + parser.error("--backend qwen needs a voice: pass --voice SPEAKER " + "for a built-in speaker, --clone PATH to clone a " + "reference .wav, or --instructions \"...\" to " + "design a voice") if args.model is not None and args.backend != BACKEND_AUDIOCPP: parser.error("--model requires --backend audiocpp; it selects an " |
