extract resolve_fallback_voice_spec to domain/voice_resolution.py; fix missing get_default_voice import and __custom_mix reset bug

This commit is contained in:
Artem Akymenko
2026-07-15 15:01:17 +00:00
parent 7bd3177241
commit 56cfd0810d
3 changed files with 171 additions and 20 deletions
+25 -1
View File
@@ -8,7 +8,7 @@ from __future__ import annotations
from typing import Any, Dict, Optional, Set
from abogen.tts_plugin.utils import get_voices
from abogen.tts_plugin.utils import get_voices, get_default_voice
from abogen.voice_formulas import extract_voice_ids
from abogen.voice_cache import ensure_voice_assets
@@ -164,3 +164,27 @@ def chunk_voice_spec(job: Any, chunk: Dict[str, Any], fallback: str) -> str:
if fallback:
return fallback
return job_voice_fallback(job)
def resolve_fallback_voice_spec(
base_spec: str,
job_voice: str,
voice_cache_keys: list[str],
provider: str = "kokoro",
) -> str:
"""Resolve the voice spec for intro/outro with a priority fallback chain.
Priority: base_spec → job_voice → first voice_cache key → default voice.
``"__custom_mix"`` is treated as empty (it is not a usable voice spec).
"""
spec = base_spec or job_voice
if spec == "__custom_mix":
spec = job_voice or ""
if not spec:
for key in voice_cache_keys:
if key and key != "__custom_mix":
spec = key.split(":", 1)[-1]
break
if not spec:
spec = get_default_voice(provider)
return spec
+7 -19
View File
@@ -84,6 +84,7 @@ from abogen.domain.voice_resolution import (
initialize_voice_cache as _initialize_voice_cache,
chapter_voice_spec as _chapter_voice_spec,
chunk_voice_spec as _chunk_voice_spec,
resolve_fallback_voice_spec as _resolve_fallback_voice_spec,
)
from abogen.domain.chapter_overrides import apply_chapter_overrides as _apply_chapter_overrides
from abogen.domain.metadata_merge import merge_metadata as _merge_metadata
@@ -535,15 +536,9 @@ def run_conversion_job(job: Job) -> None:
preview = book_intro_text if len(book_intro_text) <= 120 else f"{book_intro_text[:117]}"
job.add_log(f"Title intro enabled: {preview}", level="debug")
intro_voice_spec = base_voice_spec or job.voice
if intro_voice_spec == "__custom_mix":
intro_voice_spec = base_voice_spec or ""
if not intro_voice_spec:
fallback_key = next(iter(voice_cache.keys()), "")
if fallback_key and fallback_key != "__custom_mix":
intro_voice_spec = fallback_key.split(":", 1)[-1]
if not intro_voice_spec:
intro_voice_spec = get_default_voice("kokoro")
intro_voice_spec = _resolve_fallback_voice_spec(
base_voice_spec, job.voice, list(voice_cache.keys())
)
if intro_voice_spec:
intro_provider, _, intro_voice_choice, intro_speed, intro_steps = resolve_voice_choice(
@@ -960,16 +955,9 @@ def run_conversion_job(job: Job) -> None:
if getattr(job, "read_closing_outro", True):
outro_text = _build_outro_text(job.metadata_tags, job.original_filename)
outro_voice_spec = base_voice_spec or job.voice
if outro_voice_spec == "__custom_mix":
outro_voice_spec = base_voice_spec or ""
if not outro_voice_spec:
fallback_key = next(iter(voice_cache.keys()), "")
if fallback_key and fallback_key != "__custom_mix":
# `voice_cache` keys are internal and include provider prefixes.
outro_voice_spec = fallback_key.split(":", 1)[-1]
if not outro_voice_spec:
outro_voice_spec = get_default_voice("kokoro")
outro_voice_spec = _resolve_fallback_voice_spec(
base_voice_spec, job.voice, list(voice_cache.keys())
)
if outro_text and outro_voice_spec:
outro_start_time = current_time