From e56754498f1c6b2a9dabb62529f783e73fae8e6b Mon Sep 17 00:00:00 2001 From: historia Date: Tue, 18 Aug 2026 17:58:04 -0400 Subject: feat: language parameter for potential accent tuning --- converter/tts.py | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) (limited to 'converter/tts.py') 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 = { -- cgit v1.2.3