59 lines
2.2 KiB
Python
59 lines
2.2 KiB
Python
"""Backend Qwen3-TTS (qualite + clonage par audio de reference) — rendu final.
|
|
|
|
Deux modes :
|
|
- voix preglee : `voice` (ex "Chelsie") + `language` ("French").
|
|
- clonage : `ref_audio` (+ `ref_text` transcription du clip) pour imiter une
|
|
voix de la voicebank, attribuee a un personnage.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
|
|
from ..settings import get_settings
|
|
from .base import TTSBackend, VoiceSpec, to_mono_float32
|
|
from .chunk import chunk_text
|
|
|
|
# Qwen3 tolere des sequences plus longues que Kokoro, mais on borne quand meme.
|
|
_QWEN_MAX_CHARS = 500
|
|
|
|
|
|
class Qwen3Backend(TTSBackend):
|
|
name = "qwen3"
|
|
|
|
def __init__(self, model_id: str | None = None, language: str | None = None):
|
|
settings = get_settings()
|
|
self.model_id = model_id or settings.qwen3_model
|
|
self.language = language or settings.language
|
|
self._model = None
|
|
self._sample_rate = 24000
|
|
|
|
def _ensure_loaded(self) -> None:
|
|
if self._model is None:
|
|
from mlx_audio.tts.utils import load_model
|
|
self._model = load_model(self.model_id)
|
|
|
|
def default_voice(self) -> VoiceSpec:
|
|
return VoiceSpec(preset=get_settings().qwen3_default_voice)
|
|
|
|
def _gen_kwargs(self, voice: VoiceSpec) -> dict:
|
|
kwargs: dict = {"language": self.language, "speed": voice.speed}
|
|
if voice.ref_audio: # mode clonage
|
|
kwargs["ref_audio"] = voice.ref_audio
|
|
if voice.ref_text:
|
|
kwargs["ref_text"] = voice.ref_text
|
|
else: # mode voix preglee
|
|
kwargs["voice"] = voice.preset or get_settings().qwen3_default_voice
|
|
return kwargs
|
|
|
|
def synthesize(self, text: str, voice: VoiceSpec) -> tuple[np.ndarray, int]:
|
|
self._ensure_loaded()
|
|
kwargs = self._gen_kwargs(voice)
|
|
pieces: list[np.ndarray] = []
|
|
for chunk in chunk_text(text, max_chars=_QWEN_MAX_CHARS):
|
|
for result in self._model.generate(text=chunk, **kwargs):
|
|
self._sample_rate = getattr(result, "sample_rate", self._sample_rate)
|
|
pieces.append(to_mono_float32(result.audio))
|
|
if not pieces:
|
|
return np.zeros(0, dtype=np.float32), self._sample_rate
|
|
return np.concatenate(pieces), self._sample_rate
|