import base64 from mistralai.client import Mistral from . import config _client = Mistral(api_key=config.MISTRAL_API_KEY) _default_voice_id: str | None = None def _get_default_voice_id() -> str: """ Récupère et met en cache l'ID d'une voix preset. Priorise les voix supportant la langue configurée (VOICE_LANGUAGE). """ global _default_voice_id if _default_voice_id is not None: return _default_voice_id voices = _client.audio.voices.list(type_="preset", limit=50) if not voices.items: raise RuntimeError( "Aucune voix disponible. Configurez VOICE_ID dans .env ou créez une voix " "avec scripts/register_voice.py" ) # Cherche une voix supportant la langue configurée preferred_lang = config.VOICE_LANGUAGE matching = [ v for v in voices.items if v.languages and preferred_lang in v.languages ] chosen = matching[0] if matching else voices.items[0] _default_voice_id = chosen.id print(f"[TTS] Voix sélectionnée : {chosen.name} (langues: {chosen.languages}) — id: {_default_voice_id}") return _default_voice_id def text_to_speech(text: str, voice_id: str | None = None) -> bytes: """ Convertit du texte en audio WAV via Voxtral TTS. WAV est lu nativement par aplay (Linux) et afplay (macOS) sans conversion. """ effective_voice_id = voice_id or config.VOICE_ID or _get_default_voice_id() response = _client.audio.speech.complete( model=config.TTS_MODEL, input=text, voice_id=effective_voice_id, response_format="wav", ) return base64.b64decode(response.audio_data)