mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
feat: Improve SuperTonic voice resolution and add tests for voice formula handling
This commit is contained in:
@@ -56,16 +56,24 @@ SAMPLE_RATE = 24000
|
|||||||
|
|
||||||
def _supertonic_voice_from_spec(spec: Any, fallback: str) -> str:
|
def _supertonic_voice_from_spec(spec: Any, fallback: str) -> str:
|
||||||
raw = str(spec or "").strip()
|
raw = str(spec or "").strip()
|
||||||
if not raw:
|
fallback_raw = str(fallback or "").strip()
|
||||||
raw = str(fallback or "").strip()
|
|
||||||
if not raw:
|
# SuperTonic voices are discrete IDs (M1/F3/...). If we see a Kokoro mix
|
||||||
return "M1"
|
# formula (contains '*' or '+'), ignore it and fall back to a safe voice.
|
||||||
if "*" in raw or "+" in raw:
|
if not raw or "*" in raw or "+" in raw:
|
||||||
raw = str(fallback or "").strip() or "M1"
|
raw = fallback_raw
|
||||||
|
if not raw or "*" in raw or "+" in raw:
|
||||||
|
raw = "M1"
|
||||||
|
|
||||||
upper = raw.upper()
|
upper = raw.upper()
|
||||||
if upper in DEFAULT_SUPERTONIC_VOICES:
|
if upper in DEFAULT_SUPERTONIC_VOICES:
|
||||||
return upper
|
return upper
|
||||||
return str(fallback or "").strip() or "M1"
|
|
||||||
|
fallback_upper = fallback_raw.upper() if fallback_raw else ""
|
||||||
|
if fallback_upper in DEFAULT_SUPERTONIC_VOICES:
|
||||||
|
return fallback_upper
|
||||||
|
|
||||||
|
return "M1"
|
||||||
|
|
||||||
|
|
||||||
def _split_speaker_reference(value: Any) -> tuple[Optional[str], str]:
|
def _split_speaker_reference(value: Any) -> tuple[Optional[str], str]:
|
||||||
@@ -2439,6 +2447,12 @@ def _build_ffmpeg_command(path: Path, fmt: str, metadata: Optional[Dict[str, str
|
|||||||
|
|
||||||
def _resolve_voice(pipeline, voice_spec: str, use_gpu: bool):
|
def _resolve_voice(pipeline, voice_spec: str, use_gpu: bool):
|
||||||
if "*" in voice_spec:
|
if "*" in voice_spec:
|
||||||
|
# Voice formulas are a Kokoro-only feature (they require a pipeline that can
|
||||||
|
# load individual Kokoro voices). When running with SuperTonic (or when the
|
||||||
|
# pipeline is otherwise unavailable), treat the spec as a plain string and
|
||||||
|
# allow downstream provider-specific resolution to choose a safe fallback.
|
||||||
|
if pipeline is None or not hasattr(pipeline, "load_single_voice"):
|
||||||
|
return voice_spec
|
||||||
return get_new_voice(pipeline, voice_spec, use_gpu)
|
return get_new_voice(pipeline, voice_spec, use_gpu)
|
||||||
return voice_spec
|
return voice_spec
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from abogen.web.conversion_runner import _resolve_voice, _supertonic_voice_from_spec
|
||||||
|
from abogen.tts_supertonic import DEFAULT_SUPERTONIC_VOICES
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_voice_formula_without_pipeline_does_not_crash() -> None:
|
||||||
|
# This can happen when a previously-saved Kokoro mix formula is present
|
||||||
|
# but the active provider is SuperTonic (no Kokoro pipeline object).
|
||||||
|
formula = "af_heart*0.5+af_sky*0.5"
|
||||||
|
resolved = _resolve_voice(None, formula, use_gpu=False)
|
||||||
|
assert resolved == formula
|
||||||
|
|
||||||
|
|
||||||
|
def test_supertonic_voice_from_formula_falls_back_to_valid_voice() -> None:
|
||||||
|
# When a stale Kokoro mix formula is present, SuperTonic should not receive it.
|
||||||
|
chosen = _supertonic_voice_from_spec("af_heart*0.5+af_sky*0.5", "af_heart*1.0")
|
||||||
|
assert chosen in DEFAULT_SUPERTONIC_VOICES
|
||||||
Reference in New Issue
Block a user