diff options
Diffstat (limited to 'app/converter/clients/audiocpp.py')
| -rw-r--r-- | app/converter/clients/audiocpp.py | 81 |
1 files changed, 61 insertions, 20 deletions
diff --git a/app/converter/clients/audiocpp.py b/app/converter/clients/audiocpp.py index c441047..a1888bc 100644 --- a/app/converter/clients/audiocpp.py +++ b/app/converter/clients/audiocpp.py @@ -102,24 +102,23 @@ AUDIOCPP_VOICE_REQUIRED = "required" # clone-only: a reference voice is mandato AUDIOCPP_VOICE_OPTIONAL = "optional" # tts + clone: blank voice means plain TTS AUDIOCPP_VOICE_NONE = "none" # pure TTS: no cloning, no voice at all -# Spec-task cache for audiocpp_family_voice_policy (family -> tasks or -# None for unknown). The form consults the policy on every menu render, -# so each family's spec is read at most once per process. -_FAMILY_SPEC_TASKS: Dict[str, Optional[Set[str]]] = {} +# Spec cache (family -> parsed spec dict, or None for unknown). The form +# consults the policy and capability tags on every menu render, so each +# family's spec is read at most once per process. +_FAMILY_SPECS: Dict[str, Optional[dict]] = {} -def audiocpp_family_spec_tasks(family: str) -> Optional[Set[str]]: - """FAMILY's task set from the local audio.cpp checkout's model_specs. +def _family_spec(family: str) -> Optional[dict]: + """FAMILY's parsed model spec from the local audio.cpp checkout. Reads ``<checkout>/model_specs/<family>.json`` (the checkout the setup - wizard manages, which also ships the specs for remote servers) and - returns its "tasks" list as a set, or None when the checkout is - missing, the family is not described, or the spec is unparsable. - Results are cached per process. + wizard manages, which also ships the specs for remote servers), or + None when the checkout is missing, the family is not described, or the + spec is unparsable. Results are cached per process. """ - if family in _FAMILY_SPEC_TASKS: - return _FAMILY_SPEC_TASKS[family] - tasks: Optional[Set[str]] = None + if family in _FAMILY_SPECS: + return _FAMILY_SPECS[family] + spec: Optional[dict] = None try: # Imported lazily: backends.audiocpp imports this package (its # voices module), so a module-level import would cycle. @@ -129,14 +128,56 @@ def audiocpp_family_spec_tasks(family: str) -> Optional[Set[str]]: checkout = None if checkout is not None: try: - spec = json.loads((checkout / "model_specs" / f"{family}.json") - .read_text(encoding="utf-8")) + parsed = json.loads((checkout / "model_specs" / f"{family}.json") + .read_text(encoding="utf-8")) except (OSError, ValueError): - spec = None - if isinstance(spec, dict) and isinstance(spec.get("tasks"), list): - tasks = {str(task) for task in spec["tasks"]} - _FAMILY_SPEC_TASKS[family] = tasks - return tasks + parsed = None + if isinstance(parsed, dict): + spec = parsed + _FAMILY_SPECS[family] = spec + return spec + + +def audiocpp_family_spec_tasks(family: str) -> Optional[Set[str]]: + """FAMILY's task set from the local audio.cpp checkout's model_specs. + + Returns the spec's "tasks" list as a set, or None when the family is + not described (see _family_spec). + """ + spec = _family_spec(family) + if spec is None or not isinstance(spec.get("tasks"), list): + return None + return {str(task) for task in spec["tasks"]} + + +def audiocpp_entry_supports_design(family: str, task: str, + model_id: str) -> bool: + """Whether a server model entry can design a voice from a description. + + True for task-"vdes" entries (the model *is* a voice-design model) and + for entries of families whose spec advertises a design task — those + families design on the regular entry from the request's instructions + text (e.g. OmniVoice, VoxCPM2). Qwen3-TTS is the exception: its + design support lives only in a separate VoiceDesign model entry (also + task "vdes"), while its Base/CustomVoice entries cannot design. Model + IDs play no role today but stay in the signature for parity with + audiocpp_entry_voice_capability. Unknown families (no local specs) + conservatively report no design support. + """ + if task == AUDIOCPP_TASK_VDES: + return True + if family == AUDIOCPP_FAMILY_QWEN3_TTS: + return False + spec = _family_spec(family) + if spec is None: + return False + design_markers = {"design", AUDIOCPP_TASK_VDES} + tasks = audiocpp_family_spec_tasks(family) + if tasks and tasks & design_markers: + return True + capabilities = spec.get("capabilities") + return isinstance(capabilities, dict) \ + and bool(set(map(str, capabilities)) & design_markers) def audiocpp_family_voice_policy(family: str) -> str: |
