diff --git a/abogen/tts_backend_registry.py b/abogen/tts_backend_registry.py index 4066537..6278624 100644 --- a/abogen/tts_backend_registry.py +++ b/abogen/tts_backend_registry.py @@ -66,6 +66,19 @@ def register_backend( _registry.register(metadata, factory) +def get_metadata(backend_id: str) -> TTSBackendMetadata: + """Get metadata for a specific backend by id. + + Ensures all backends are registered by importing the tts_backends + package on first access. + + Raises: + KeyError: If backend with given id is not registered. + """ + import abogen.tts_backends # noqa: F401 — triggers backend registration + return _registry.get_metadata(backend_id) + + def create_backend(backend_id: str, **kwargs: Any) -> TTSBackend: """Create a TTS backend instance by provider id.""" return _registry.create_backend(backend_id, **kwargs) diff --git a/abogen/voice_cache.py b/abogen/voice_cache.py index fd8e4df..7d93542 100644 --- a/abogen/voice_cache.py +++ b/abogen/voice_cache.py @@ -17,7 +17,7 @@ if LocalEntryNotFoundError is None: # pragma: no cover - fallback for tests pass -from abogen.constants import VOICES_INTERNAL +from abogen.tts_backend_registry import get_metadata _CACHE_LOCK = threading.Lock() _CACHED_VOICES: Set[str] = set() @@ -26,8 +26,9 @@ _BOOTSTRAPPED = False def _normalize_targets(voices: Optional[Iterable[str]]) -> Set[str]: + kokoro_voices = get_metadata("kokoro").voices if not voices: - return set(VOICES_INTERNAL) + return set(kokoro_voices) normalized: Set[str] = set() for voice in voices: if not voice: @@ -35,7 +36,7 @@ def _normalize_targets(voices: Optional[Iterable[str]]) -> Set[str]: voice_id = str(voice).strip() if not voice_id: continue - if voice_id in VOICES_INTERNAL: + if voice_id in kokoro_voices: normalized.add(voice_id) return normalized diff --git a/abogen/voice_formulas.py b/abogen/voice_formulas.py index b91cffe..6078d9b 100644 --- a/abogen/voice_formulas.py +++ b/abogen/voice_formulas.py @@ -1,7 +1,7 @@ import re from typing import List, Tuple -from abogen.constants import VOICES_INTERNAL +from abogen.tts_backend_registry import get_metadata # Calls parsing and loads the voice to gpu or cpu @@ -22,6 +22,7 @@ def parse_formula_terms(formula: str) -> List[Tuple[str, float]]: raise ValueError("Empty voice formula") terms: List[Tuple[str, float]] = [] + kokoro_voices = get_metadata("kokoro").voices for segment in formula.split("+"): part = segment.strip() if not part: @@ -30,7 +31,7 @@ def parse_formula_terms(formula: str) -> List[Tuple[str, float]]: raise ValueError("Each component must be in the form voice*weight") voice_name, raw_weight = part.split("*", 1) voice_name = voice_name.strip() - if voice_name not in VOICES_INTERNAL: + if voice_name not in kokoro_voices: raise ValueError(f"Unknown voice: {voice_name}") try: weight = float(raw_weight.strip()) diff --git a/abogen/voice_profiles.py b/abogen/voice_profiles.py index 784b252..9823e5f 100644 --- a/abogen/voice_profiles.py +++ b/abogen/voice_profiles.py @@ -2,8 +2,7 @@ import json import os from typing import Any, Dict, Iterable, List, Tuple -from abogen.constants import VOICES_INTERNAL -from abogen.tts_backends.supertonic import DEFAULT_SUPERTONIC_VOICES +from abogen.tts_backend_registry import get_metadata from abogen.utils import get_user_config_path @@ -70,7 +69,8 @@ def serialize_profiles() -> Dict[str, Dict[str, Iterable[Tuple[str, float]]]]: def _normalize_supertonic_voice(value: Any) -> str: raw = str(value or "").strip().upper() - return raw if raw in DEFAULT_SUPERTONIC_VOICES else "M1" + supertonic_voices = get_metadata("supertonic").voices + return raw if raw in supertonic_voices else "M1" def _coerce_supertonic_steps(value: Any) -> int: @@ -135,6 +135,7 @@ def normalize_profile_entry(entry: Any) -> Dict[str, Any]: def _normalize_voice_entries(entries: Iterable) -> List[Tuple[str, float]]: normalized: List[Tuple[str, float]] = [] + kokoro_voices = get_metadata("kokoro").voices for item in entries or []: if isinstance(item, dict): voice = item.get("id") or item.get("voice") @@ -143,7 +144,7 @@ def _normalize_voice_entries(entries: Iterable) -> List[Tuple[str, float]]: voice, weight = item[0], item[1] else: continue - if voice not in VOICES_INTERNAL: + if voice not in kokoro_voices: continue if weight is None: continue