درس 3 — Générer la voix

Synthèse vocale : une voix, un ton, un fichier

5 دقائق قراءة

هذا المحتوى غير مترجم بعد. تُعرض النسخة الأصلية.

Synthèse vocale : une voix, un ton, un fichier

Comment fonctionne un TTS Gemini

Un modèle TTS (Text-To-Speech) reçoit du texte et renvoie de l'audio. Avec Gemini, c'est un appel generate_content ordinaire, avec deux réglages en plus :

  • response_modalities=["AUDIO"] : on veut du son, pas du texte ;
  • speech_config : quelle voix utiliser. La documentation propose une trentaine de voix préenregistrées (prebuilt voices) nommées, par exemple Kore (ferme) ou Puck (enjouée), toutes multilingues.

Le style (« chaleureuse, posée », « agacé ») se donne soit dans le texte (« Dis calmement : … »), soit, sur les modèles récents, dans le champ speech_metadata.style de la Part, ce qui évite que l'instruction soit lue à voix haute.

sequenceDiagram
  participant App as "Votre code"
  participant API as "gemini-3.8-flash-tts"
  App->>API: "Part(text, speech_metadata.style) + voix Kore + AUDIO"
  API-->>App: "inline_data : audio (WAV ou PCM brut)"
  App->>App: "save_wav() : ajoute l'en-tête RIFF si besoin"
  App->>App: "reponse.wav (24 kHz, 16 bits, mono)"

Le code

files/voix.py
def tts(c, text: str, path: str, voice: str = "Kore", style: str | None = None) -> float:
    """Une seule voix. Le style (« chaleureux, posé ») passe par speech_metadata."""
    from google.genai import types
    part = types.Part(text=text, speech_metadata=types.SpeechMetadata(style=style) if style else None)
    cfg = types.GenerateContentConfig(
        response_modalities=["AUDIO"], speech_config=types.SpeechConfig(voice_config=_voice(voice)))
    r = with_retry(lambda: c.models.generate_content(model=TTS_MODEL, contents=[part], config=cfg))
    return save_wav(r.candidates[0].content.parts[0].inline_data.data, path)

Le piège du format : WAV ou PCM brut ?

En testant deux modèles avec le même code, nous avons obtenu deux formats différents :

Modèlemime_type renvoyéContenu
gemini-2.5-flash-preview-ttsaudio/L16;codec=pcm;rate=24000PCM brut, sans en-tête
gemini-3.8-flash-ttsaudio/wavWAV complet, en-tête RIFF inclus

Si vous ajoutez systématiquement un en-tête WAV, le fichier du second modèle en aura deux : il se lira peut-être, mais sa durée sera fausse et certains lecteurs refuseront de l'ouvrir. D'où une fonction qui regarde les 4 premiers octets :

files/voix.py
def save_wav(data: bytes, path: str, rate: int = RATE) -> float:
    if not data.startswith(b"RIFF"):          # PCM brut : on l'enveloppe
        buf = io.BytesIO()
        with wave.open(buf, "wb") as w:
            w.setnchannels(1); w.setsampwidth(2); w.setframerate(rate)
            w.writeframes(data)
        data = buf.getvalue()
    with open(path, "wb") as f:
        f.write(data)
    return wav_seconds(data)                  # durée réelle, utile pour vérifier

Pourquoi save_wav teste-t-il si les données commencent par RIFF ?

Combien ça coûte, combien de temps ça prend

L'audio produit se compte en tokens de sortie. Lors de notre test, une phrase d'accueil de 19 tokens de texte a produit 5,0 s d'audio comptées 125 tokens audio en sortie, soit environ 25 tokens par seconde produite. La génération a pris 3,6 s : plus rapide que la lecture, mais pas instantanée. Pour un serveur vocal, cela compte (module 3).

Résultat obtenu

Sortie réelle du 2026-10-15 (même texte, même voix Kore) :

gemini-3.8-flash-tts         audio/wav                       246112 octets  3,60 s  sortie : 125 tokens AUDIO
gemini-2.5-flash-preview-tts audio/L16;codec=pcm;rate=24000  192526 octets  2,94 s  sortie : 100 tokens AUDIO

Et le message de rappel produit par le projet final (12,0 s, voix Kore, style « chaleureuse, posée ») :

Message de rappel généré par gemini-3.8-flash-tts

Un message vocal de 12 s a été produit. Combien de tokens audio de sortie attendre, d'après notre mesure ?

Exercice : trois tons pour un même message

Avec voix.tts, générez le même message (« Votre colis part aujourd'hui en express. ») avec trois styles différents : « neutre », « très enthousiaste », « désolée, à voix basse ». Écoutez, puis vérifiez que le texte prononcé n'inclut pas l'instruction de style.

Voir la solution
import voix

c = voix.client()
for i, style in enumerate(["neutre", "très enthousiaste", "désolée, à voix basse"]):
    d = voix.tts(c, "Votre colis part aujourd'hui en express.", f"ton_{i}.wav", style=style)
    print(style, f"{d:.1f} s")

Pour vérifier que le style n'est pas lu, retranscrivez chaque fichier avec voix.transcribe(c, "ton_0.wav") : le texte doit être identique au message, sans « neutre » ni « enthousiaste ». C'est l'intérêt de speech_metadata.style par rapport à une consigne écrite dans le texte. Notre essai du 2026-10-15 avec le style « très enthousiaste » : 3,4 s d'audio, retranscrit exactement Votre colis part aujourd'hui en express.

Je sais faire

  • Générer un fichier WAV avec une voix nommée et un style
  • Gérer les deux formats de sortie (WAV complet ou PCM brut)
  • Vérifier la durée produite et estimer les tokens de sortie
  • Signaler au client qu'une voix est synthétique
التالي