aboutsummaryrefslogtreecommitdiff
path: root/app/ui/hub.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/ui/hub.py')
-rw-r--r--app/ui/hub.py99
1 files changed, 47 insertions, 52 deletions
diff --git a/app/ui/hub.py b/app/ui/hub.py
index afb8a30..e883996 100644
--- a/app/ui/hub.py
+++ b/app/ui/hub.py
@@ -988,10 +988,9 @@ def _help_lines() -> list:
(None = body). Numbered steps start at the margin; every other
line is indented three spaces — Frame's two-space indent unit
plus a leading space in the row's first segment — so it lines up
- with the step text after the "N. " prefixes. The
- input/output folders are read from the converter module at call
- time, so a Settings change this session is reflected without a
- restart.
+ with the step text after the "N. " prefixes. The input/output and
+ clone .wav folders are read at call time, so a Settings change this
+ session is reflected without a restart.
"""
return [
([("1. ", "title"),
@@ -1000,7 +999,7 @@ def _help_lines() -> list:
"",
([("2. ", "title"),
("Put any .wavs of voices to clone here:", None)], 0),
- ([(" " + str(common.VOICES_DIR), "input")], 1),
+ ([(" " + str(_clone_wav_dir()), "input")], 1),
"",
([("3. ", "title"),
("If no backend is installed, go to ", None),
@@ -1948,8 +1947,9 @@ def _qwen_fields(remote_modes: Optional[list] = None,
Returns ``(fields, mapper)`` where FIELDS are the qwen options — which
model the demo server hosts (Base (voice cloning) / CustomVoice (built-in
voices) / VoiceDesign (design)), plus the per-model controls: Speaker on
- CustomVoice, a Clone .wav directory browser (default ./voices) + Voice-
- to-clone .wav picker on Base, Instructions on VoiceDesign — and
+ CustomVoice, a Voice-to-clone .wav picker on Base (listing the reference
+ .wavs in the configured Clone .wav directory — a Settings entry),
+ Instructions on VoiceDesign — and
MAPPER turns a submitted form values dict into the qwen converter
kwargs. None (form omitted) when a filtered remote model list comes
back empty — no known mode matched what the remote demo reported.
@@ -1990,29 +1990,18 @@ def _qwen_fields(remote_modes: Optional[list] = None,
speakers = list(qwen_backend.QWEN_SPEAKERS)
default_speaker = speakers[0]
- # Voice cloning references: the directory the .wavs live in — browsed
- # with the directory widget, defaulting to the project's ./voices (the
- # folder the Help screen points at) — plus a picker of the .wav files
- # found there (the same directory + voice picker the audio.cpp form
- # uses; the demo uploads exactly one reference file).
+ # Voice cloning reference: the .wav picker lists the files in the
+ # configured Clone .wav directory (a Settings entry, defaulting to the
+ # project's ./voices — the folder the Help screen points at); the demo
+ # uploads exactly one reference file.
def clone_wav_choices(fs) -> list:
"""(file name, full path) pairs for the clone directory's .wavs."""
- return [(p.name, str(p)) for p in _list_wavs(
- _field_value(fs, prefix + "clone_dir"))]
-
- def reset_clone_wav(fs) -> None:
- """Re-point the .wav picker at the newly chosen directory."""
- wav_field = next(f for f in fields
- if f.get("key") == prefix + "clone")
- wav_field["value"] = next(
- (path for _name, path in clone_wav_choices(fs)), "")
+ return [(p.name, str(p)) for p in _list_wavs(_clone_wav_dir())]
def no_wavs_hint(_fs=None) -> str:
"""Why the .wav picker is empty (validate echoes it on Generate!)."""
- directory = next((f.get("value") for f in fields
- if f.get("key") == prefix + "clone_dir"), None)
- return (f"No .wav files in {directory} — put a reference .wav "
- "there or pick another directory.")
+ return (f"No .wav files in {_clone_wav_dir()}. "
+ "Check directory in Settings.")
def clone_wav_validate(value) -> Optional[str]:
"""Refuse Generate! when no reference .wav is available to clone."""
@@ -2020,7 +2009,7 @@ def _qwen_fields(remote_modes: Optional[list] = None,
return None
return no_wavs_hint()
- initial_wavs = _list_wavs(common.VOICES_DIR)
+ initial_wavs = _list_wavs(_clone_wav_dir())
initial_clone = str(initial_wavs[0]) if initial_wavs else ""
fields = [
{"key": prefix + "mode", "label": "Model", "kind": "choice",
@@ -2030,11 +2019,6 @@ def _qwen_fields(remote_modes: Optional[list] = None,
{"key": prefix + "speaker", "label": "Speaker", "kind": "choice",
"value": default_speaker, "choices": speakers,
"visible": lambda fs: _field_value(fs, prefix + "mode") == "custom"},
- {"key": prefix + "clone_dir", "label": "Clone .wav directory",
- "kind": "dir", "value": common.VOICES_DIR,
- "info": common.wav_dir_info, "preview": common.wav_dir_preview,
- "on_change": reset_clone_wav,
- "visible": lambda fs: _field_value(fs, prefix + "mode") == "clone"},
{"key": prefix + "clone", "label": "Voice to clone",
"kind": "choice", "value": initial_clone,
"choices": clone_wav_choices, "on_empty_choices": no_wavs_hint,
@@ -2141,11 +2125,11 @@ def _sglomni_fields(stdscr, api_url: Optional[str] = None,
(preset voices on speaker models; on clone models the model's
built-in default voice on top when it can narrate without a
reference, then the server's uploaded named voices, then the
- reference .wavs from the Clone .wav directory browser — picking a
- .wav clones it, and on models that cannot narrate without a
- reference a .wav pick is required), Instructions on the VoiceDesign
- model — and MAPPER turns a submitted form values dict into the
- sglomni converter kwargs. Returns None when
+ reference .wavs in the configured Clone .wav directory — a Settings
+ entry — picking a .wav clones it, and on models that cannot narrate
+ without a reference a .wav pick is required), Instructions on the
+ VoiceDesign model — and MAPPER turns a submitted form values dict
+ into the sglomni converter kwargs. Returns None when
the entry's options cannot be gathered (a flash explains why), so the
caller drops SGLang-Omni from the Backend choices. PREFIX namespaces
the field keys ("" for the managed entry) so two entries of this
@@ -2188,11 +2172,11 @@ def _sglomni_fields(stdscr, api_url: Optional[str] = None,
def model_capability(fs) -> str:
return model_entry(fs).capability
- # Voice-clone references: the qwen form's directory + .wav picker.
+ # Voice-clone references: the qwen form's .wav picker, fed by the
+ # configured Clone .wav directory (a Settings entry).
def clone_wav_choices(fs) -> list:
- """The reference .wavs offered by the clone-directory field."""
- return [(p.name, str(p)) for p in _list_wavs(
- _field_value(fs, prefix + "clone_dir"))]
+ """The reference .wavs offered by the clone directory."""
+ return [(p.name, str(p)) for p in _list_wavs(_clone_wav_dir())]
def voice_choices(fs):
"""One Voice menu per capability.
@@ -2218,10 +2202,8 @@ def _sglomni_fields(stdscr, api_url: Optional[str] = None,
return choices
def no_wavs_hint(_fs=None) -> str:
- directory = next((f.get("value") for f in fields
- if f.get("key") == prefix + "clone_dir"), None)
- return (f"No .wav files in {directory} — put a reference .wav "
- "there or pick another directory.")
+ return (f"No .wav files in {_clone_wav_dir()}. "
+ "Check directory in Settings.")
def voice_validate(value) -> Optional[str]:
"""Blank is the built-in default voice — unless the model needs
@@ -2313,7 +2295,7 @@ def _sglomni_fields(stdscr, api_url: Optional[str] = None,
initial_voice = voices[0] if voices else ""
elif (default_entry.capability == "clone"
and default_entry.requires_reference):
- initial_wavs = _list_wavs(common.VOICES_DIR)
+ initial_wavs = _list_wavs(_clone_wav_dir())
initial_voice = str(initial_wavs[0]) if initial_wavs else ""
fields = [
@@ -2328,11 +2310,6 @@ def _sglomni_fields(stdscr, api_url: Optional[str] = None,
"validate": voice_validate,
"visible": lambda fs: model_capability(fs) in ("speaker",
"clone")},
- {"key": prefix + "clone_dir", "label": "Clone .wav directory",
- "kind": "dir", "value": common.VOICES_DIR,
- "info": common.wav_dir_info, "preview": common.wav_dir_preview,
- "on_change": reset_voice_fields,
- "visible": lambda fs: model_capability(fs) == "clone"},
{"key": prefix + "instructions", "label": "Instructions",
"kind": "text", "value": "",
"help": ["Describe the voice to design, e.g.",
@@ -2351,8 +2328,7 @@ def _sglomni_fields(stdscr, api_url: Optional[str] = None,
elif entry.capability == "clone":
# A .wav pick clones it; a named (uploaded) voice reuses a
# server-side voice; blank uses the model's built-in default.
- wavs = {str(p) for p in _list_wavs(
- result.get(prefix + "clone_dir"))}
+ wavs = {str(p) for p in _list_wavs(_clone_wav_dir())}
if pick in wavs:
kwargs["clone"] = pick
elif pick:
@@ -2370,6 +2346,17 @@ def _sglomni_fields(stdscr, api_url: Optional[str] = None,
# Settings menu (global output options -> app/converter/config.py)
# ---------------------------------------------------------------------------
+def _clone_wav_dir() -> Path:
+ """The configured Clone .wav directory (a Settings entry).
+
+ The directory the qwen-tts Base / SGLang-Omni voice pickers list
+ their reference .wavs from. Resolved like the input/output folders:
+ relative paths resolve against the project root, blank falls back
+ to the project's ./voices.
+ """
+ return converter_mod.resolve_dir(config.CLONE_WAV_DIR, "voices")
+
+
def _settings_changed(fields: list, original: dict) -> bool:
"""True when any field's current value differs from its ORIGINAL.
@@ -2409,6 +2396,10 @@ def _settings_fields() -> list:
{"key": "output_dir", "label": "Output Directory", "kind": "dir",
"value": converter_mod.resolve_dir(config.OUTPUT_DIR, "output"),
"validate": _validate_dir},
+ {"key": "clone_wav_dir", "label": "Clone .wav directory",
+ "kind": "dir", "value": _clone_wav_dir(),
+ "info": common.wav_dir_info, "preview": common.wav_dir_preview,
+ "validate": _validate_dir},
{"key": "speed", "label": "Speed", "kind": "text",
"value": str(config.SPEED), "validate": _validate_speed},
{"key": "debug", "label": "Debug", "kind": "bool",
@@ -2550,10 +2541,13 @@ def _apply_settings(values: dict) -> None:
raise ValueError("Speed must be a positive number")
input_dir = str(values["input_dir"]).strip()
output_dir = str(values["output_dir"]).strip()
+ clone_dir = str(values["clone_wav_dir"]).strip()
if not input_dir:
raise ValueError("Input Directory must not be empty")
if not output_dir:
raise ValueError("Output Directory must not be empty")
+ if not clone_dir:
+ raise ValueError("Clone .wav directory must not be empty")
ports = {
"qwen_port": _read_port(values, "qwen_port"),
@@ -2578,6 +2572,7 @@ def _apply_settings(values: dict) -> None:
"CHUNK_SIZE": chunk_size,
"INPUT_DIR": input_dir,
"OUTPUT_DIR": output_dir,
+ "CLONE_WAV_DIR": clone_dir,
"SPEED": speed,
"DEBUG": bool(values["debug"]),
"STOP_SERVER_AND_EXIT": bool(values["stop_and_exit"]),