diff options
| author | historia <historiavg@proton.me> | 2026-08-26 19:12:11 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-26 19:12:11 -0400 |
| commit | 5af37bbd575eec89c6ac2fecf2d8c2eda4c1728d (patch) | |
| tree | 4f8fe9b79d339695ca83968e03301b853eb2e2ef /app/ui/hub.py | |
| parent | df95e7034df683c38fde67890430ab4c2abfa4ba (diff) | |
| download | tts-audiobook-generator-5af37bbd575eec89c6ac2fecf2d8c2eda4c1728d.tar.gz | |
fix: crash when menu of clone voices is empty
Diffstat (limited to 'app/ui/hub.py')
| -rw-r--r-- | app/ui/hub.py | 31 |
1 files changed, 28 insertions, 3 deletions
diff --git a/app/ui/hub.py b/app/ui/hub.py index 6a82c83..80fe843 100644 --- a/app/ui/hub.py +++ b/app/ui/hub.py @@ -927,6 +927,13 @@ def _audiocpp_fields(stdscr, api_url: Optional[str] = None, the field keys ("" for the managed entry) so two entries of this backend can share one form without overwriting each other. + The Voice field tracks the selected entry's capability — built-in + speakers on CustomVoice, the server's clone voices on every other + entry. A clone-capable entry whose server lists no voices cannot be + picked from (empty menu) and refuses Generate! with a hint pointing + at the voice-clone .wav directory instead of crashing or producing a + run that fails at model-load time. + With API_URL None (the managed entry) the model list is fed from the local checkout's server.json — the config of the server this tool manages. With API_URL set (the "[remote]" entry) the models and voices @@ -1045,6 +1052,25 @@ def _audiocpp_fields(stdscr, api_url: Optional[str] = None, fields, prefix + "model_id"))] return [] # design: the field is hidden + def no_voices_hint() -> str: + """Why a clone-capable entry has no selectable voices.""" + if local: + return ("No .wav files available to clone — run Configure " + "backends → audio.cpp and add voices to its " + "voice-clone .wav directory.") + return ("No .wav files available to clone — the audio.cpp server " + f"at {url} hosts none. Configure its voice-clone .wav " + "directory on that machine.") + + def voice_validate(value): + """Refuse Generate! when this entry's clone voice is unavailable.""" + if model_capability(fields) != AUDIOCPP_VOICE_CLONE: + return None + if not voices_for(_field_value(fields, prefix + "model_id")): + return no_voices_hint() + return None if value \ + else "This model needs a voice — pick one or switch models" + model_ids = [m.get("id") for m in models] default_model = config.AUDIOCPP_MODEL_ID \ if config.AUDIOCPP_MODEL_ID in model_ids else model_ids[0] @@ -1080,9 +1106,8 @@ def _audiocpp_fields(stdscr, api_url: Optional[str] = None, "value": initial_voice, "choices": lambda fs: voice_choices(fs), "visible": lambda fs: model_capability(fs) != AUDIOCPP_VOICE_DESIGN, - "validate": lambda value: None - if (model_capability(fields) != AUDIOCPP_VOICE_CLONE or value) - else "This model needs a voice — pick one or switch models"}, + "on_empty_choices": no_voices_hint, + "validate": voice_validate}, {"key": prefix + "instructions", "label": "Instructions", "kind": "text", "value": config.AUDIOCPP_INSTRUCTIONS, "visible": lambda fs: model_capability(fs) in (AUDIOCPP_VOICE_DESIGN, |
