Image produit, image-to-video et cohérence entre plans
En texte seul, Veo invente la bouteille à chaque appel : forme, couleur du liquide, typographie de l'étiquette. Deux plans générés séparément donnent deux produits différents, et la publicité montre une bouteille qui change entre la quatrième et la cinquième seconde. Le texte ne suffit pas à tenir une identité visuelle. Une image, si.
Le mécanisme : une image produit, des images dérivées, des clips qui partent de ces images
L'image-to-video fournit à Veo la première image du clip ; le modèle l'anime au lieu d'inventer la scène. La cohérence se construit donc en amont, sur les images fixes, là où elle coûte 0,067 :
- une image produit générée une fois par
gemini-3.1-flash-image, au ratio final (9:16) ; - pour chaque plan qui change de cadrage ou ajoute un élément, une image dérivée obtenue en éditant l'image produit (on envoie l'image et la consigne de modification), jamais en régénérant de zéro ;
- un clip Veo par plan, qui part de son image.
flowchart LR BP["bible.produit + décor + lumière"] --> I0["Image produit<br/>gemini-3.1-flash-image<br/>0,067 $"] I0 -->|"plan p1 : telle quelle"| V1["Veo Lite 4 s<br/>0,20 $"] I0 -->|"édition : « même scène,<br/>une main entre par la droite »"| I1["Image dérivée<br/>0,067 $"] I1 --> V2["Veo Lite 4 s<br/>0,20 $"] V1 --> M["Montage"] V2 --> M
Les leviers de cohérence, du plus fort au plus faible :
| Levier | Disponible sur | Ce qu'il tient | Limite |
|---|---|---|---|
| Même image de départ (ou image dérivée par édition) | Lite, Fast, standard | produit, décor, lumière, cadrage | une seule image par clip |
| Images de référence (jusqu'à 3) | Fast, standard | un personnage ou un objet vu sous plusieurs angles | clip de 8 s imposé, pas sur Lite |
| Première et dernière image (interpolation) | Lite, Fast, standard | le point d'arrivée du plan, donc le raccord avec le suivant | le chemin entre les deux reste libre |
| Bible répétée dans chaque prompt | tous | style, palette, ambiance | ne fixe ni un visage ni une étiquette |
seed identique | selon modèle | réduit la variance d'un essai à l'autre | la documentation précise qu'il ne garantit pas le déterminisme |
La mise en œuvre
base = gen.image(b.image_produit.modele, prompt_image_produit(b), ratio)
for p in b.plans:
depart = base if p.image == "produit" else gen.image(
b.image_produit.modele, p.image, ratio, source=base) # édition, pas régénération
brut = gen.video(b.video.modele, prompt_video(b, p), depart,
duree=p.duree, ratio=ratio, resolution=b.video.resolution)L'appel Veo, avec le suivi de l'opération longue (long-running operation) :
op = self.client.models.generate_videos(
model=modele, prompt=prompt,
image=types.Image(image_bytes=img, mime_type="image/png"),
config=types.GenerateVideosConfig(
aspect_ratio=ratio, resolution=resolution, duration_seconds=duree,
person_generation="allow_adult", # seule valeur admise en image-to-video et dans l'UE
number_of_videos=1))
while not op.done:
if time.time() - t > self.attente_max_s:
raise TimeoutError(...)
time.sleep(10)
op = self.client.operations.get(op)
vids = op.response.generated_videos if op.response else None
if not vids: # filtre de sécurité : vidéo non produite, non facturée
raise RuntimeError(f"vidéo bloquée par les filtres : {op.response.rai_media_filtered_reasons}")
self.client.files.download(file=vids[0].video) # conservée 2 jours côté serveur
vids[0].video.save(str(sortie))Chaque média payé est écrit dans sorties/cache/ sous une clé calculée à partir du modèle, du prompt, des paramètres et des octets de l'image de départ, avec une fiche JSON de son coût. Relancer le pipeline après un échec de montage ne rachète aucun clip.
Résultat obtenu
Génération réelle du 2026-10-15, veo-3.1-lite-generate-preview, 720p, 9:16, deux clips de 4 s :
pubcourte.generation INFO cache 423af6b25805de4d.png (image, 0.0670 $ déjà payés)
POST .../veo-3.1-lite-generate-preview:predictLongRunning "HTTP/1.1 200 OK"
pubcourte.generation INFO video veo-3.1-lite-generate-preview : 0.2000 $ en 32.8 s
POST .../gemini-3.1-flash-image:generateContent "HTTP/1.1 200 OK"
pubcourte.generation INFO image gemini-3.1-flash-image : 0.0670 $ en 11.1 s
POST .../veo-3.1-lite-generate-preview:predictLongRunning "HTTP/1.1 200 OK"
pubcourte.generation INFO video veo-3.1-lite-generate-preview : 0.2000 $ en 33.2 sLes clips sortent en H.264 720×1280, 24 images/s, avec une piste AAC stéréo 48 kHz (ambiance et bruitages générés). Image produit (à gauche) et image dérivée par édition pour le plan 2 (à droite) :
La bouteille, l'étiquette CALADE, la lumière rasante et la rue sont identiques sur les deux images, donc sur les deux clips. Deux écarts au brief restent visibles, et c'est l'intérêt de les regarder avant de monter :
- le décor : le brief demandait un comptoir de café près d'une fenêtre, le modèle d'image a posé la bouteille sur le rebord extérieur de la vitrine. Le défaut est dans l'image produit, il se propage à tous les plans ;
- le plan 1 : à la première seconde, un bec verseur entre par le haut du cadre puis disparaît (objet parasite inventé par Veo). Le plan 2 montre deux mains alors que le prompt en décrivait une.
Ce qui casse en production
| Symptôme | Cause | Parade |
|---|---|---|
| Le produit change entre deux plans | clips générés en texte seul | image de départ commune, images dérivées par édition |
| Défaut répété dans tous les plans | erreur dans l'image produit, propagée | valider l'image produit avant de lancer Veo (0,067 ) |
| Objet ou membre en trop au milieu du clip | Veo complète la scène ; l'exclusion par prompt réduit sans supprimer | couper au montage (le plan 1 peut démarrer à 1,2 s), ou regénérer ce seul plan |
RuntimeError: vidéo bloquée par les filtres | contenu jugé sensible (visage reconnaissable, audio) | reformuler ; l'essai bloqué n'est pas facturé |
TimeoutError après 10 min | file d'attente chargée : la documentation annonce de 11 s à 6 min | relancer plus tard ; l'opération peut être reprise par son nom |
| Clip introuvable 3 jours plus tard | vidéos supprimées du serveur après 2 jours | téléchargement immédiat dans le cache local |
La règle de décision
Toute publicité à plusieurs plans d'un même produit passe par une image produit validée, puis par l'image-to-video. Le texte seul convient aux plans d'ambiance sans produit (une rue, une tasse générique). Quand un personnage revient dans plusieurs plans, le Lite ne suffit plus : passez sur Fast avec des images de référence, en 8 s.
Pratique
Sur le rendu, l'étiquette CALADE est nette dans le plan 1 mais devient « CALAPE » dans le plan 2. Que faites-vous ?
Veo anime l'image qu'on lui donne : une étiquette fausse au départ reste fausse. Rééditer l'image coûte 0,067 $, un clip Lite 0,20 $. Répéter le texte dans le prompt aide peu sur des lettres. Le standard coûte 8 fois plus et part de la même image fautive. La retouche image par image sur 96 images est hors budget pour un format de 8 s.
Le montage plante sur une erreur ffmpeg après la génération des deux clips. Vous corrigez et relancez `make e2e`. Combien payez-vous ?
La clé de cache dépend du modèle, du prompt, des paramètres et des octets de l'image de départ ; rien de cela n'a changé, les deux clips sont relus depuis sorties/cache/. C'est ce qu'a montré la relance du 2026-10-15 : dépense de l'exécution 0,000 $, coût des médias 0,575 $ (déjà payés). Attention : modifier une virgule du prompt change la clé et relance la génération.
Exercice. Le client veut que le plan 2 se termine exactement sur la bouteille ouverte posée, étiquette face caméra, pour enchaîner sur un plan 3. Quel paramètre Veo utilisez-vous, et que modifiez-vous dans GeminiGenerateur.video ?
Voir la solution
L'interpolation : last_frame dans GenerateVideosConfig, avec l'image de départ (image=) obligatoire. Elle est disponible sur Lite, Fast et standard. La dernière image se fabrique comme les autres, par édition de l'image produit (« same bottle, cap removed, standing, label facing camera »).
config=types.GenerateVideosConfig(
aspect_ratio=ratio, resolution=resolution, duration_seconds=duree,
person_generation="allow_adult",
last_frame=types.Image(image_bytes=fin.read_bytes(), mime_type="image/png") if fin else None,
)Ajoutez un champ image_fin au Plan, et intégrez les octets de cette image dans la clé de cache, sinon deux plans aux fins différentes partageraient le même fichier.
- Je génère et je valide l'image produit avant tout appel Veo
- Je dérive les images des autres plans par édition, pas par régénération
- Je suis une opération Veo jusqu'au bout, avec délai maximal et gestion du filtrage
- Je télécharge et je mets en cache chaque clip le jour même