Voix off (Gemini TTS) et musique (Lyria)
Premier essai réel du pipeline, le 2026-10-15. La voix off du plan 1 devait durer 3 secondes ; elle en dure 7,5. Le pipeline s'arrête avant d'appeler Veo, avec ce message : plan p1 : voix de 7.5 s pour un plan de 4 s, raccourcir le texte. La transcription du fichier donne la cause : « Dites d'une voix chaleureuse et posée avec un léger sourire. Cannellade Cold Brew. Infusé 16 heures à froid. » Le modèle a lu la consigne de style à voix haute.
Deux enseignements, que le code applique depuis : la voix se génère avant la vidéo, parce qu'elle coûte un millième de dollar et qu'elle échoue souvent ; et la consigne de style ne s'écrit jamais dans le texte lu.
Le mécanisme
sequenceDiagram
participant P as Pipeline
participant T as Gemini TTS
participant F as ffmpeg
participant G as Gemini (transcription)
participant V as Veo
P->>T: texte du plan + style en annotation
T-->>P: WAV 24 kHz mono
P->>F: retrait des silences de tête et de queue
F-->>P: durée nette mesurée
alt durée > plan - 0,4 s
P-->>P: arrêt, 0 $ de vidéo dépensé
end
P->>G: que dit réellement l'audio ?
alt transcription bien plus longue que le texte
P-->>P: arrêt : consigne prononcée
end
P->>V: seulement maintenant, les clips
Une prise par plan. Chaque plan a sa propre ligne de voix, générée séparément. Le début de la prise est connu (début du plan + 0,2 s), sa durée est mesurée : les sous-titres se calent sans transcription ni alignement automatique.
Le style en annotation. gemini-3.8-flash-tts traite le texte comme une transcription mot à mot (c'est écrit dans la documentation). Le style (« chaleureuse et posée, léger sourire ») passe dans une annotation speech_metadata de l'API Interactions. Avec generateContent, ce modèle refuse l'instruction système (400 Developer instruction is not enabled for this model, constaté le même jour) : il n'y a pas d'autre endroit propre pour le style.
La musique. lyria-3-clip-preview produit toujours un extrait de 30 s en MP3 44,1 kHz stéréo, pour 0,04 $. L'instrumental se demande dans le prompt (« Instrumental only, no vocals ») ; il n'y a pas de paramètre dédié. Le montage n'en garde que 8 s, avec un fondu d'entrée de 0,3 s et de sortie de 0,8 s, et l'abaisse automatiquement quand la voix parle (compression en sidechain, détaillée en leçon 5).
La mise en œuvre
# gemini-3.8-flash-tts lit le texte mot à mot : une consigne écrite dans le texte est
# PRONONCÉE (constaté le 2026-10-15). Le style passe en annotation séparée.
it = self.client.interactions.create(
model=modele,
input=[{"type": "user_input", "content": [{
"type": "text", "text": texte,
"annotations": [{"type": "speech_metadata", "style": style}] if style else []}]}],
response_format={"type": "audio"},
generation_config={"speech_config": [{"voice": voix}]})
data = base64.b64decode(it.output_audio.data)Sur Vertex AI, le même modèle se pilote par generateContent (l'API Interactions n'y est pas utilisée ici) ; le style est alors ignoré, avec un avertissement dans le journal. Les formats de sortie diffèrent selon le modèle : WAV complet pour gemini-3.8-flash-tts, PCM 16 bits brut (audio/L16;codec=pcm;rate=24000) pour gemini-2.5-flash-tts sur Vertex. pcm_en_wav() normalise les deux.
net = m.nettoyer_voix(gen.voix(b.voix.modele, b.voix.voix, b.voix.style, p.voix), ...)
d = m.duree(net)
if d > p.duree - 2 * MARGE_VOIX_S:
raise BriefRefuse(f"plan {p.id} : voix de {d:.1f} s pour un plan de {p.duree} s, raccourcir le texte")
if controle_voix and hasattr(gen, "transcrire"):
entendu = gen.transcrire(net)
if voix_suspecte(p.voix, entendu): # transcription > 1,5 × le texte attendu
raise BriefRefuse(f"plan {p.id} : la voix dit « {entendu} » (consigne de style prononcée ?)")Le retrait des silences est un filtre ffmpeg appliqué deux fois, à l'endroit puis à l'envers :
coupe = "silenceremove=start_periods=1:start_threshold=-45dB:start_silence=0.05"
ffmpeg(["-i", str(wav), "-af", f"{coupe},areverse,{coupe},areverse", str(sortie)])Résultat obtenu
Exécutions réelles du 2026-10-15 :
| Prise | Texte | Brut | Après retrait des silences | Coût |
|---|---|---|---|---|
| Essai 1 (consigne dans le texte) | « Dites d'une voix chaleureuse… : Calade Cold Brew. Infusé seize heures, à froid. » | 8,04 s | 7,47 s, refusé | 0,0018 $ |
| p1 | « Calade Cold Brew. Seize heures à froid. » | 3,28 s | 2,70 s | 0,0007 $ |
| p2 | « Ouvrez. C'est prêt. » | 1,88 s | 1,29 s | 0,0004 $ |
| Musique | prompt lo-fi acoustique, 92 BPM, ré majeur | 30,77 s | 8 s montées | 0,04 $ |
Contrôle de transcription consigné dans manifeste.json :
"controle_voix": {
"p1": "Canat Cold Brew. 16h à froid.",
"p2": "Ouvrez, c'est prêt."
}La transcription confirme qu'aucune consigne n'est prononcée. Elle ne confirme pas la prononciation de « Calade » : sur quatre essais, elle a donné « Cannellade », « Égalade », « Gaillarde », « Canat ». Un nom de marque inventé met en échec la transcription autant que la synthèse ; seule une écoute humaine tranche. Si la prononciation est fausse, écrivez le nom phonétiquement dans le texte envoyé au TTS, et gardez l'orthographe correcte dans les sous-titres.
Ce qui casse en production
| Symptôme | Cause | Parade |
|---|---|---|
| La voix lit la consigne de style | style écrit dans le texte d'un modèle « mot à mot » | annotation speech_metadata ; contrôle de longueur de la transcription |
400 Developer instruction is not enabled | instruction système envoyée à un modèle TTS | ne pas utiliser system_instruction avec le TTS |
| Voix qui déborde sur le plan suivant | texte trop long, silences de queue non retirés | mesure après silenceremove, refus avant Veo |
| Fichier WAV illisible | PCM brut écrit tel quel (Vertex, modèles 2.5) | en-tête WAV ajouté par pcm_en_wav() |
| Nom de marque mal prononcé | mot absent des données d'entraînement | écoute humaine, graphie phonétique côté TTS |
| Musique avec voix chantée | « instrumental » absent du prompt Lyria | « Instrumental only, no vocals » en tête de prompt ; la sortie texte de Lyria renvoie <instrumental> quand c'est respecté |
La règle de décision
Voix off TTS pour tout texte publicitaire : il change souvent et se refait pour presque rien. Gardez une voix par marque (ici Sulafat, décrite comme « Warm ») et ne la changez pas d'une campagne à l'autre. N'utilisez pas de clone de voix d'une personne réelle sans contrat écrit (leçon 6). Pour la musique, Lyria Clip suffit à un format de 8 à 30 s ; au-delà, lyria-3.5 produit des morceaux de quelques minutes pour 0,08 $. Une musique du commerce exige une licence de synchronisation, que le brief référence (source: licence:<contrat>).
Pratique
Le pipeline refuse un brief : « plan p2 : voix de 3,9 s pour un plan de 4 s ». Le client tient à son texte. Que proposez-vous ?
Le plan est l'unité de temps : allonger le plan à la durée suivante acceptée par Veo (6 s) règle le problème sans dégrader la voix. Une accélération de 15 % s'entend nettement sur une voix posée. Désactiver la vérification fait chevaucher la voix sur la coupe suivante et décale les sous-titres. Changer de voix modifie l'identité sonore de la marque pour une question de 0,4 s.
Sur Vertex AI, votre voix off sort sans le ton chaleureux obtenu avec la clé d'API. Le journal affiche « style ignoré ». Quelle est la décision la plus raisonnable ?
Écrire la consigne dans le texte est précisément ce qui a fait prononcer la consigne lors du premier essai. Le style passe par l'annotation de l'API Interactions, utilisée ici avec la clé d'API. Un modèle plus cher ne change pas la façon dont le style est transmis.
Exercice. Écrivez un test sans réseau qui vérifie que la musique est abaissée sous la voix : le graphe de filtres doit contenir sidechaincompress, alimenté par la voix, et ne pas en contenir quand aucun plan n'a de voix off.
Voir la solution
Le test existe déjà dans tests/test_montage.py :
def test_mixage_duck_et_loudnorm():
g = filtre_mixage(2, [0.2, 4.2], 8.0, 0.35)
assert "adelay=200|200" in g and "adelay=4200|4200" in g
assert "sidechaincompress" in g and "loudnorm=I=-14" in g
assert "afade=t=out:st=7.2" in g
assert "sidechaincompress" not in filtre_mixage(0, [], 8.0, 0.35)Il teste la construction du graphe, pas le son. Le rendu sonore se vérifie à l'écoute et par la mesure de sonie de la leçon 5.
- Je génère et je mesure la voix avant de payer la vidéo
- Je passe le style en annotation, jamais dans le texte lu
- Je contrôle par transcription qu'aucune consigne n'est prononcée, et j'écoute les noms propres
- Je demande une musique instrumentale explicitement et j'en connais le coût (0,04 $ l'extrait)