diff options
Diffstat (limited to 'audiobook.py')
| -rwxr-xr-x | audiobook.py | 84 |
1 files changed, 54 insertions, 30 deletions
diff --git a/audiobook.py b/audiobook.py index 302bb17..19f5290 100755 --- a/audiobook.py +++ b/audiobook.py @@ -19,6 +19,7 @@ externally-run server and never touches server state. import argparse import logging +import math import sys import traceback from pathlib import Path @@ -394,9 +395,22 @@ def convert(backend: str, voice: str = None, clone: str = None, print("[INFO] Conversion cancelled; nothing was converted") return 0 else: - # qwen: instructions design the voice (VoiceDesign model), a - # reference .wav clones one (Base), otherwise a built-in speaker. - if (instructions or "").strip(): + # qwen: a built-in speaker (CustomVoice — with the instructions + # text, if given, sent as that model's delivery/style control), a + # reference .wav clone (Base), or instructions alone designing the + # voice (VoiceDesign). Instructions never *switch* the mode: + # speaker+instructions is a directed CustomVoice run, and + # clone+instructions is refused — the Base demo has no + # voice-direction route, so silently redesigning the voice would + # drop the chosen reference. + wants_instructions = bool((instructions or "").strip()) + if clone and wants_instructions: + raise ValueError( + "The qwen-tts Base (voice cloning) demo cannot take style " + "instructions. Drop --instructions, or pick a CustomVoice " + "speaker with --voice (which sends them as delivery " + "control) or the VoiceDesign model (--instructions only).") + if wants_instructions and not (voice or "").strip(): voice_mode = VOICE_MODE_DESIGN else: voice_mode = VOICE_MODE_CLONE if clone else VOICE_MODE_CUSTOM @@ -713,14 +727,15 @@ Examples: args = parser.parse_args() # An explicit --speed overrides the config SPEED setting; either way - # the value must be a positive number. + # the value must be a finite positive number (a bare positivity check + # lets --speed inf / 1e309 through, which would loop forever building + # ffmpeg atempo filters during assembly). speed = args.speed if args.speed is not None else config.SPEED - try: - bad_speed = not isinstance(speed, (int, float)) or speed <= 0 - except TypeError: - bad_speed = True + bad_speed = (not isinstance(speed, (int, float)) + or not math.isfinite(speed) + or speed <= 0) if bad_speed: - parser.error(f"--speed must be a positive number (got {speed!r})") + parser.error(f"--speed must be a finite positive number (got {speed!r})") # The directory flags and the single-book flags are two different # ways to choose what to convert and where it goes; mixing a pair @@ -828,27 +843,36 @@ Examples: if not args.clone and (args.transcription or args.no_transcription): print("[WARNING] --transcription/--no-transcription " "are ignored without --clone") - try: - from backends.sglomni import models as sg_models - installed = sg_models.installed_keys() - except Exception: - installed = None - if installed is not None: - if not installed: - parser.error("--backend sglomni: no models are downloaded " - "— install one via the TUI's Configure " - "Backends → SGLang-Omni first") - if args.model is None: - if len(installed) > 1: - parser.error( - "--backend sglomni requires --model when several " - "models are installed (installed: " - f"{', '.join(installed)})") - args.model = installed[0] - elif sg_models.entry_by_key(args.model) is None: - parser.error(f"--model {args.model!r} is not an " - "SGLang-Omni catalog key (installed: " - f"{', '.join(installed)})") + if args.api_url is not None: + # A remote server brings its own models: local installs are + # irrelevant here, but the catalog key to request must be + # named (the converter has no default for remote runs). + if not args.model: + parser.error("--backend sglomni with --api-url requires " + "--model (a catalog key, e.g. " + "higgs_audio_v3_tts; see the backend docs)") + else: + try: + from backends.sglomni import models as sg_models + installed = sg_models.installed_keys() + except Exception: + installed = None + if installed is not None: + if not installed: + parser.error("--backend sglomni: no models are downloaded " + "— install one via the TUI's Configure " + "Backends → SGLang-Omni first") + if args.model is None: + if len(installed) > 1: + parser.error( + "--backend sglomni requires --model when several " + "models are installed (installed: " + f"{', '.join(installed)})") + args.model = installed[0] + elif sg_models.entry_by_key(args.model) is None: + parser.error(f"--model {args.model!r} is not an " + "SGLang-Omni catalog key (installed: " + f"{', '.join(installed)})") if args.clone and not Path(args.clone).is_file(): parser.error(f"--clone: no such reference audio file: {args.clone}") else: |
