Files
InkFlow/backend/inkflow/tts/qwen3.py

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