diff options
| author | historia <historiavg@proton.me> | 2026-08-26 21:22:40 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-26 21:22:40 -0400 |
| commit | 477ac3e827e3bdc9f14583fc3aa8db1fa2d27c52 (patch) | |
| tree | 1e21fa5af1d7a95ffc62fb03eed153056c38ae9e /app/converter/converter.py | |
| parent | 65c6f737f1545ef225768af897acd20f163a4fb4 (diff) | |
| download | tts-audiobook-generator-477ac3e827e3bdc9f14583fc3aa8db1fa2d27c52.tar.gz | |
feat: design model support for qwen-tts backend. remove unnecessary port split for qwen models
Diffstat (limited to 'app/converter/converter.py')
| -rw-r--r-- | app/converter/converter.py | 35 |
1 files changed, 28 insertions, 7 deletions
diff --git a/app/converter/converter.py b/app/converter/converter.py index 48d4987..b356448 100644 --- a/app/converter/converter.py +++ b/app/converter/converter.py @@ -24,6 +24,7 @@ from .clients import ( MODEL_SIZE, VOICE_MODE_CLONE, VOICE_MODE_CUSTOM, + VOICE_MODE_DESIGN, VOICE_MODES, AudioCppTTSClient, FasterTTSClient, @@ -100,19 +101,23 @@ def setup_directories() -> None: def voice_mode_for(backend: str, voice: Optional[str] = None, - clone: Optional[str] = None) -> str: + clone: Optional[str] = None, + instructions: Optional[str] = None) -> str: """The voice mode a run with these options would use. Mirrors the choice ``audiobook.convert`` makes from the same inputs (faster always clones; audiocpp clones through a server-side voice; - qwen clones only with a reference .wav), so the hub can run the - pre-flight overwrite checks against exactly the output names the - conversion will produce. + qwen designs with instructions, clones only with a reference .wav, and + uses a built-in speaker otherwise), so the hub can run the pre-flight + overwrite checks against exactly the output names the conversion will + produce. """ if backend == BACKEND_FASTER: return VOICE_MODE_CLONE if backend == BACKEND_AUDIOCPP: return VOICE_MODE_CLONE if voice else VOICE_MODE_CUSTOM + if (instructions or "").strip(): + return VOICE_MODE_DESIGN return VOICE_MODE_CLONE if clone else VOICE_MODE_CUSTOM @@ -245,6 +250,9 @@ class AudiobookConverter: request_options=self.request_options, api_url=api_url, quiet=quiet) else: + # Qwen: the voice mode picks the request shape (built-in + # speaker, clone from a reference .wav, or a designed voice); + # instructions describe the voice in design mode. self.tts = QwenTTSClient( chunks_dir=CHUNKS_FOLDER, voice_mode=voice_mode, @@ -252,6 +260,7 @@ class AudiobookConverter: voice_clone_ref_text=voice_clone_ref_text, skip_transcription=skip_transcription, language=self.language, + instructions=self.instructions, api_url=api_url, quiet=quiet, ) @@ -281,6 +290,12 @@ class AudiobookConverter: f"Unknown voice mode: {self.voice_mode!r} " f"(expected one of {VOICE_MODES})" ) + if self.backend == BACKEND_QWEN and self.voice_mode == VOICE_MODE_DESIGN \ + and not (self.instructions or "").strip(): + raise ValueError( + "Voice Design mode requires a voice description. " + "Use --instructions \"...\" to describe the voice to synthesize with." + ) if self.voice_mode == VOICE_MODE_CLONE and self.backend == BACKEND_QWEN: if not self.voice_clone_ref_audio: raise ValueError( @@ -336,6 +351,10 @@ class AudiobookConverter: narrator = "designed" else: narrator = speaker_display_name() + elif voice_mode == VOICE_MODE_DESIGN: + # Qwen's VoiceDesign model: the voice is described by an + # instruction and has no speaker name. + narrator = "designed" elif voice_mode == VOICE_MODE_CLONE: narrator = Path(voice_clone_ref_audio).stem else: @@ -722,9 +741,7 @@ class AudiobookConverter: else: tts_client = getattr(self, "tts", None) api_url = (getattr(tts_client, "api_url", None) - or (config.CLONE_API_URL - if self.voice_mode == VOICE_MODE_CLONE - else config.QWEN_API_URL)) + or config.QWEN_API_URL) self._say(f"Qwen API endpoint: {api_url}") self._say(f"Voice mode: {self.voice_mode}") self._say(f"Model size: {MODEL_SIZE} (always)") @@ -734,6 +751,10 @@ class AudiobookConverter: elif self.voice_mode == VOICE_MODE_CLONE: self._say(f"Reference audio: {Path(self.voice_clone_ref_audio).name}") self._say(f"Language: {self.language}") + elif self.voice_mode == VOICE_MODE_DESIGN: + self._say("Backend: qwen-tts (voice from --instructions description)") + self._say(f"Instruction: {self.instructions}") + self._say(f"Language: {self.language}") self._say(f"Output format: {self.output_format}") if self.single_file and self.output_format != "m4b": self._say("Chapter mode: single file (--single-file)") |
