aboutsummaryrefslogtreecommitdiff
path: root/converter/tts.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-18 17:58:04 -0400
committerhistoria <historiavg@proton.me>2026-08-18 17:58:04 -0400
commite56754498f1c6b2a9dabb62529f783e73fae8e6b (patch)
tree20ea86a16d6c51c4bfd793cea56fd76ae9985671 /converter/tts.py
parent50f1825f05972e3685c55beb10c288899959b2e5 (diff)
downloadtts-audiobook-generator-e56754498f1c6b2a9dabb62529f783e73fae8e6b.tar.gz
feat: language parameter for potential accent tuning
Diffstat (limited to 'converter/tts.py')
-rw-r--r--converter/tts.py44
1 files changed, 38 insertions, 6 deletions
diff --git a/converter/tts.py b/converter/tts.py
index 83c7330..db5de9b 100644
--- a/converter/tts.py
+++ b/converter/tts.py
@@ -15,11 +15,38 @@ from . import config
logger = logging.getLogger(__name__)
+def normalize_language(value: Optional[str]) -> str:
+ """Normalize a user-provided language name to a Qwen3-TTS display name.
+
+ Accepts the display names in config.TTS_LANGUAGES case-insensitively as
+ well as the short aliases in config.TTS_LANGUAGE_ALIASES (ISO 639-1 codes
+ and common shorthands). Raises ValueError for anything else, since the
+ Qwen3-TTS demo silently falls back to "Auto" for unrecognized languages.
+ """
+ if value is None:
+ raise ValueError("Language must not be None")
+ candidate = value.strip()
+ if not candidate:
+ raise ValueError("Language must not be empty")
+ for name in config.TTS_LANGUAGES:
+ if candidate.lower() == name.lower():
+ return name
+ alias = config.TTS_LANGUAGE_ALIASES.get(candidate.lower())
+ if alias:
+ return alias
+ raise ValueError(
+ f"Unknown language: {value!r}. Expected one of "
+ f"{', '.join(config.TTS_LANGUAGES)} (or an alias: "
+ f"{', '.join(sorted(config.TTS_LANGUAGE_ALIASES))})."
+ )
+
+
class QwenTTSClient:
"""Generates audio chunks through a Qwen3-TTS Gradio server."""
def __init__(self, voice_mode: str = "custom_voice", voice_clone_ref_audio: Optional[str] = None,
- voice_clone_ref_text: Optional[str] = None, skip_transcription: bool = False):
+ voice_clone_ref_text: Optional[str] = None, skip_transcription: bool = False,
+ language: Optional[str] = None):
if voice_mode not in config.VOICE_MODES:
raise ValueError(
f"Unknown voice mode: {voice_mode!r} (expected one of {config.VOICE_MODES})"
@@ -28,6 +55,11 @@ class QwenTTSClient:
self.voice_clone_ref_audio = voice_clone_ref_audio
self.voice_clone_ref_text = (voice_clone_ref_text or "").strip()
self.skip_transcription = skip_transcription
+ if language is None:
+ language = (config.VOICE_CLONE_LANGUAGE if voice_mode == config.VOICE_MODE_CLONE
+ else config.CUSTOM_VOICE_LANGUAGE)
+ # Validate before connecting so bad values fail fast without a server.
+ self.language = normalize_language(language)
self.client = None
self.api_info: Dict[str, Any] = {}
self.clone_client = None
@@ -70,7 +102,7 @@ class QwenTTSClient:
self.voice_clone_ref_text = self.transcribe_audio(self.voice_clone_ref_audio) or ""
if not self.voice_clone_ref_text:
print("[WARNING] No reference text available; using x-vector-only clone mode (lower quality).")
- print(' Pass --voice-sample-text "..." for higher-quality in-context cloning.')
+ print(' Pass --transcription "..." for higher-quality in-context cloning.')
else:
print(f"[OK] Reference text: {self.voice_clone_ref_text[:100]}...")
@@ -258,7 +290,7 @@ class QwenTTSClient:
if custom_api == "/run_instruct":
payload = dict(
text=text,
- lang_disp=config.CUSTOM_VOICE_LANGUAGE,
+ lang_disp=self.language,
spk_disp=config.SPEAKER_DISPLAY_NAMES.get(
config.CUSTOM_VOICE_SPEAKER.lower(), config.CUSTOM_VOICE_SPEAKER),
instruct=config.CUSTOM_VOICE_INSTRUCT,
@@ -266,7 +298,7 @@ class QwenTTSClient:
else:
payload = dict(
text=text,
- language=config.CUSTOM_VOICE_LANGUAGE,
+ language=self.language,
speaker=config.CUSTOM_VOICE_SPEAKER,
instruct=config.CUSTOM_VOICE_INSTRUCT,
)
@@ -305,14 +337,14 @@ class QwenTTSClient:
ref_txt=self.voice_clone_ref_text,
use_xvec=use_xvector,
text=text,
- lang_disp=config.VOICE_CLONE_LANGUAGE,
+ lang_disp=self.language,
)
else:
payload = dict(
ref_audio=self._ref_audio_payload(),
ref_text=self.voice_clone_ref_text,
target_text=text,
- language=config.VOICE_CLONE_LANGUAGE,
+ language=self.language,
use_xvector_only=use_xvector,
)
optional_params = {