mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
feat: store supported voices in TTSBackendMetadata
Add voices field to TTSBackendMetadata so each backend's supported voice list is part of its metadata rather than external constants. - Add voices: tuple[str, ...] = () to TTSBackendMetadata - Create _KOKORO_METADATA / _SUPERTONIC_METADATA as single source of truth for both metadata property and registry registration - Update KokoroBackend.get_available_voices() to use self.metadata.voices - Update SupertonicBackend.get_available_voices() to use self.metadata.voices - Add tests for voices field, metadata voice content, and unified instance identity
This commit is contained in:
@@ -18,11 +18,13 @@ class TTSBackendMetadata:
|
||||
id: Unique backend identifier (e.g. ``"kokoro"``, ``"supertonic"``).
|
||||
name: Human-readable display name.
|
||||
description: Short description of the backend.
|
||||
voices: Tuple of supported voice identifiers.
|
||||
"""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
voices: tuple[str, ...] = ()
|
||||
|
||||
|
||||
class TTSBackend(Protocol):
|
||||
|
||||
@@ -10,6 +10,16 @@ from typing import Any, Dict, Iterator, List, Optional
|
||||
|
||||
import numpy as np
|
||||
|
||||
from abogen.constants import VOICES_INTERNAL
|
||||
from abogen.tts_backend import TTSBackendMetadata
|
||||
|
||||
_KOKORO_METADATA = TTSBackendMetadata(
|
||||
id="kokoro",
|
||||
name="Kokoro",
|
||||
description="Kokoro TTS engine",
|
||||
voices=tuple(VOICES_INTERNAL),
|
||||
)
|
||||
|
||||
|
||||
def _load_kpipeline():
|
||||
"""Lazy-load Kokoro dependencies."""
|
||||
@@ -39,14 +49,8 @@ class KokoroBackend:
|
||||
self._lang_code = lang_code
|
||||
|
||||
@property
|
||||
def metadata(self):
|
||||
from abogen.tts_backend import TTSBackendMetadata
|
||||
|
||||
return TTSBackendMetadata(
|
||||
id="kokoro",
|
||||
name="Kokoro",
|
||||
description="Kokoro TTS engine",
|
||||
)
|
||||
def metadata(self) -> TTSBackendMetadata:
|
||||
return _KOKORO_METADATA
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
@@ -89,9 +93,7 @@ class KokoroBackend:
|
||||
|
||||
def get_available_voices(self) -> List[str]:
|
||||
"""Return known Kokoro voice identifiers."""
|
||||
from abogen.constants import VOICES_INTERNAL
|
||||
|
||||
return list(VOICES_INTERNAL)
|
||||
return list(self.metadata.voices)
|
||||
|
||||
def get_supported_formats(self) -> List[str]:
|
||||
"""Kokoro outputs raw PCM float32 audio."""
|
||||
@@ -111,14 +113,9 @@ def create_kokoro_backend(**kwargs: Any) -> KokoroBackend:
|
||||
|
||||
|
||||
# --- Registration ---
|
||||
from abogen.tts_backend import TTSBackendMetadata # noqa: E402
|
||||
from abogen.tts_backend_registry import register_backend # noqa: E402
|
||||
|
||||
register_backend(
|
||||
metadata=TTSBackendMetadata(
|
||||
id="kokoro",
|
||||
name="Kokoro",
|
||||
description="Kokoro TTS engine",
|
||||
),
|
||||
metadata=_KOKORO_METADATA,
|
||||
factory=create_kokoro_backend,
|
||||
)
|
||||
|
||||
@@ -15,6 +15,15 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_SUPERTONIC_VOICES = ("M1", "M2", "M3", "M4", "M5", "F1", "F2", "F3", "F4", "F5")
|
||||
|
||||
from abogen.tts_backend import TTSBackendMetadata
|
||||
|
||||
_SUPERTONIC_METADATA = TTSBackendMetadata(
|
||||
id="supertonic",
|
||||
name="SuperTonic",
|
||||
description="SuperTonic TTS engine",
|
||||
voices=DEFAULT_SUPERTONIC_VOICES,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SupertonicSegment:
|
||||
@@ -282,12 +291,8 @@ class SupertonicBackend:
|
||||
"""
|
||||
|
||||
@property
|
||||
def metadata(self) -> "TTSBackendMetadata":
|
||||
return TTSBackendMetadata(
|
||||
id="supertonic",
|
||||
name="SuperTonic",
|
||||
description="SuperTonic TTS engine",
|
||||
)
|
||||
def metadata(self) -> TTSBackendMetadata:
|
||||
return _SUPERTONIC_METADATA
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
self._pipeline = SupertonicPipeline(
|
||||
@@ -333,7 +338,7 @@ class SupertonicBackend:
|
||||
|
||||
def get_available_voices(self) -> List[str]:
|
||||
"""Return the list of built-in SuperTonic voice identifiers."""
|
||||
return list(DEFAULT_SUPERTONIC_VOICES)
|
||||
return list(self.metadata.voices)
|
||||
|
||||
def get_supported_formats(self) -> List[str]:
|
||||
return ["wav"]
|
||||
@@ -379,14 +384,9 @@ def create_supertonic_backend(**kwargs: Any) -> SupertonicBackend:
|
||||
return SupertonicBackend(**kwargs)
|
||||
|
||||
|
||||
from abogen.tts_backend import TTSBackendMetadata
|
||||
from abogen.tts_backend_registry import register_backend
|
||||
from abogen.tts_backend_registry import register_backend # noqa: E402
|
||||
|
||||
register_backend(
|
||||
metadata=TTSBackendMetadata(
|
||||
id="supertonic",
|
||||
name="SuperTonic",
|
||||
description="SuperTonic TTS engine",
|
||||
),
|
||||
metadata=_SUPERTONIC_METADATA,
|
||||
factory=create_supertonic_backend,
|
||||
)
|
||||
|
||||
@@ -18,6 +18,23 @@ class TestTTSBackendMetadata:
|
||||
assert meta.name == "Test Backend"
|
||||
assert meta.description == "A test backend"
|
||||
|
||||
def test_voices_field_default_empty(self):
|
||||
meta = TTSBackendMetadata(
|
||||
id="test",
|
||||
name="Test",
|
||||
description="Test backend",
|
||||
)
|
||||
assert meta.voices == ()
|
||||
|
||||
def test_voices_field_stored(self):
|
||||
meta = TTSBackendMetadata(
|
||||
id="test",
|
||||
name="Test",
|
||||
description="Test backend",
|
||||
voices=("v1", "v2"),
|
||||
)
|
||||
assert meta.voices == ("v1", "v2")
|
||||
|
||||
def test_is_immutable(self):
|
||||
import pytest
|
||||
|
||||
@@ -132,6 +149,26 @@ class TestBackendRegistration:
|
||||
assert meta.name == "SuperTonic"
|
||||
assert "SuperTonic" in meta.description
|
||||
|
||||
def test_kokoro_metadata_has_voices(self):
|
||||
import abogen.tts_backends # noqa: F401
|
||||
|
||||
from abogen.tts_backend_registry import _registry
|
||||
|
||||
meta = _registry.get_metadata("kokoro")
|
||||
assert isinstance(meta.voices, tuple)
|
||||
assert len(meta.voices) > 0
|
||||
assert all(isinstance(v, str) for v in meta.voices)
|
||||
|
||||
def test_supertonic_metadata_has_voices(self):
|
||||
import abogen.tts_backends # noqa: F401
|
||||
|
||||
from abogen.tts_backend_registry import _registry
|
||||
|
||||
meta = _registry.get_metadata("supertonic")
|
||||
assert isinstance(meta.voices, tuple)
|
||||
assert len(meta.voices) == 10
|
||||
assert meta.voices == ("M1", "M2", "M3", "M4", "M5", "F1", "F2", "F3", "F4", "F5")
|
||||
|
||||
def test_kokoro_factory_callable(self):
|
||||
import abogen.tts_backends # noqa: F401
|
||||
|
||||
@@ -147,3 +184,21 @@ class TestBackendRegistration:
|
||||
|
||||
factory = _registry._factories["supertonic"]
|
||||
assert callable(factory)
|
||||
|
||||
def test_kokoro_metadata_voices_match_registry(self):
|
||||
"""Ensure the metadata property on the instance shares voices with registry."""
|
||||
from abogen.tts_backends.kokoro import _KOKORO_METADATA
|
||||
from abogen.tts_backend_registry import _registry
|
||||
|
||||
registry_meta = _registry.get_metadata("kokoro")
|
||||
assert _KOKORO_METADATA is registry_meta
|
||||
assert _KOKORO_METADATA.voices == registry_meta.voices
|
||||
|
||||
def test_supertonic_metadata_voices_match_registry(self):
|
||||
"""Ensure the metadata property on the instance shares voices with registry."""
|
||||
from abogen.tts_backends.supertonic import _SUPERTONIC_METADATA
|
||||
from abogen.tts_backend_registry import _registry
|
||||
|
||||
registry_meta = _registry.get_metadata("supertonic")
|
||||
assert _SUPERTONIC_METADATA is registry_meta
|
||||
assert _SUPERTONIC_METADATA.voices == registry_meta.voices
|
||||
|
||||
Reference in New Issue
Block a user