Le kit est prêt, approuvé, contrôlé. Avant de cliquer sur « Publier », la directrice juridique d'Ondée pose trois questions : à qui appartiennent ces images, faut-il dire qu'elles sont générées, et peut-on le prouver si un concurrent conteste ? Cette leçon assemble le kit LinkedIn, exécute la chaîne complète de bout en bout, répond à ces trois questions et nettoie tout.
Le mécanisme
flowchart LR
A["Visuels et vidéo approuvés"] --> E["exporter_linkedin()"]
E --> C1["carrousel-1.png<br/>fond Instagram + accroche"]
E --> C2["carrousel-2 à 4.png<br/>bandeau photo + argument"]
E --> C5["carrousel-5.png<br/>signature"]
E --> P["post.md<br/>+ hashtags + mention IA"]
E --> V["video.mp4<br/>Veo + logo"]
subgraph Traces["Ce qui permet de répondre en cas de contestation"]
M["manifest.json : versions, consignes, approbateur, coûts"]
S["SynthID dans les pixels générés"]
end
Le carrousel LinkedIn est publié comme un document : cinq images 1080x1350 de même format. La couverture reprend le fond Instagram validé avec l'accroche de la première diapositive ; les trois suivantes alternent les aplats de la palette sous un bandeau photo recadré différemment ; la dernière porte la signature.
La mise en œuvre
Le test de bout en bout enchaîne tout, avec les vrais modèles, puis approuve explicitement (approuve_par="e2e") et exporte :
make e2e # ≈ 0,80 $, 2 à 3 minutes
ls sorties/e2e/linkedin/La sortie réelle de la campagne Ondée est versionnée dans exemple-campagne-ondee/ : post.md, cinq PNG, video.mp4.
Résultat obtenu
make test, puis make e2e du 2026-10-15 (extrait, Gemini API) :
20 passed in 5.81s
20:43:38 INFO studio.models kit gemini-3.5-flash-lite coût=0.0016 $
20:43:42 INFO studio.models textes gemini-3.5-flash-lite coût=0.0025 $
20:43:58 INFO studio.models image gemini-3-pro-image coût=0.1401 $
...
20:45:29 INFO studio.models video veo-3.1-lite-generate-preview coût=0.3000 $
20:45:39 INFO studio.pipeline kit LinkedIn exporté : ['carrousel-1.png', ..., 'carrousel-5.png', 'post.md', 'video.mp4']
linkedin 1200x627 v1 contraste 4.91:1 palette 10.5 texte parasite non QC OK
instagram_portrait 1080x1350 v1 contraste 13.2:1 palette 10.0 texte parasite non QC OK
story 1080x1920 v1 contraste 11.94:1 palette 9.4 texte parasite non QC OK
banniere 1584x396 v1 contraste 6.01:1 palette 22.3 texte parasite non QC OK
kit LinkedIn : carrousel-1.png (996 Ko), carrousel-2.png (916 Ko), carrousel-3.png (689 Ko),
carrousel-4.png (768 Ko), carrousel-5.png (48 Ko), post.md (0 Ko), video.mp4 (1633 Ko)
images générées : 5 coût total : 0.7196 $ durée : 123 sLe même parcours sur Vertex AI (make e2e-vertex, région global, sans vidéo) : quatre visuels QC OK, sonde concluante, 6 images, 423 s. Les coûts affichés sont calculés avec la grille de l'API Gemini ; vérifiez la grille Vertex AI de votre contrat avant d'en faire un budget.
Droits, mention IA et SynthID
À qui appartiennent les images. Les conditions de l'API Gemini indiquent que Google ne revendique pas la propriété du contenu généré, mais aussi que le service peut générer un contenu identique ou similaire pour d'autres. Vous avez donc l'usage, pas l'exclusivité. La protection par le droit d'auteur d'une image produite par un modèle est incertaine : en droit français, elle suppose une création originale portant l'empreinte de la personnalité d'un auteur humain, et l'Office américain du droit d'auteur a conclu en 2025 qu'un prompt seul ne suffit pas. Ce qui est clairement à vous : les choix humains (sélection, consignes, mise en page, textes) que le manifeste documente version par version.
Ce qui doit rester exact. Un visuel publicitaire montre le produit vendu. Si le flacon généré diffère du vrai (contenance, forme, couleur), le visuel peut constituer une pratique commerciale trompeuse. La parade est dans le flux : l'image de référence doit être validée contre le produit réel, et toutes les mentions chiffrées sont des calques exacts.
Le dire. L'article 50 du règlement européen sur l'IA, applicable depuis le 2 août 2026, impose aux fournisseurs de marquer les contenus générés de façon lisible par une machine, et aux utilisateurs professionnels de signaler les hypertrucages (contenus qui ressemblent à des personnes, objets ou lieux existants et peuvent passer pour authentiques). Une photo de produit photoréaliste s'en approche. La décision prise ici : la mention « Visuels créés avec l'IA (Gemini, Veo), sélectionnés et validés par notre équipe » est ajoutée par le code à chaque post, jamais laissée au modèle ni à la mémoire de quelqu'un.
Le prouver. Toutes les images générées par les modèles Gemini et toutes les vidéos Veo portent un filigrane invisible SynthID, d'après la documentation officielle. Deux limites à connaître :
- les pixels dessinés par le code (aplats du carrousel, texte, logo) ne portent pas de filigrane : seule la partie photographique est marquée ;
- SynthID est conçu pour résister aux transformations courantes (recadrage, compression), mais nous n'avons pas vérifié la détection après nos recadrages, voiles et réencodages JPEG et H.264. Ce point n'a pas été testé.
Nos fichiers ne portent pas non plus de manifeste C2PA (Pillow et ffmpeg réécrivent les fichiers) : la mention textuelle est la seule information visible pour le lecteur. Le manifeste JSON, conservé avec la campagne, est la preuve interne : quel modèle, quelle consigne, quelle version, qui a approuvé, quand.
Les polices. Inter et DM Serif Display sont sous SIL Open Font License 1.1, qui autorise leur usage dans des images commerciales ; les fichiers de licence sont dans fonts/.
Ce qui casse en production
| Symptôme | Cause | Parade |
|---|---|---|
| Le carrousel s'affiche en mosaïque au lieu de défiler | les 5 PNG publiés comme post multi-images ; le format qui défile est le post « document », qui attend un PDF | assembler les 5 PNG en un PDF (Image.save(..., save_all=True, append_images=...)) au moment de la publication |
| Vidéo refusée ou recompressée en bouillie | débit trop élevé ou profil exotique | H.264, yuv420p, +faststart, 2,3 Mbit/s : 1,5 Mo pour 6 s |
| Mention IA oubliée sur un post republié à la main | mention ajoutée par une personne | mention ajoutée par post_linkedin() ; un test vérifie qu'elle termine le post |
make cloud-down dit « aucune ressource » alors que gcloud a échoué | erreur de commande ignorée | le Makefile échoue si cloud-status échoue (vérifié avec un identifiant de projet invalide : code de sortie 2) |
Nettoyage
Exécuté le 2026-10-15 :
$ make cloud-down
Aucune ressource formation-lab=studio-contenu-de-marque dans le projet.
$ make clean
Artefacts supprimés.
$ make reset
Artefacts supprimés.
Environnement supprimé (.env conservé).Le projet ne crée aucune ressource Google Cloud (Vertex AI est facturé à l'appel) : cloud-down vérifie qu'aucun bucket ni service Cloud Run étiqueté formation-lab=studio-contenu-de-marque n'existe, et échoue si la vérification elle-même échoue. Les vidéos Veo sont supprimées du serveur de l'API au bout de 2 jours.
La règle de décision
Publiez un contenu généré quand trois conditions sont réunies : un humain l'a approuvé et c'est tracé, la mention IA est posée par le code, et le produit montré correspond au produit vendu. Si l'un des trois manque, l'export doit échouer, pas avertir.
Pratique
Un concurrent publie un visuel presque identique au vôtre, généré lui aussi avec Gemini. Que pouvez-vous faire valoir ?
Les conditions de l'API excluent l'exclusivité. SynthID prouve qu'une image vient d'un modèle Google, pas qui l'a demandée. Ce qui vous distingue est ce que vous avez créé ou choisi, avec ses dates.
Le flacon réel d'Ondée a une étiquette imprimée. Le visuel généré montre un flacon sans étiquette. Quelle est la bonne décision avant publication ?
Interdire le texte généré n'autorise pas à montrer un autre produit. La photo réelle du produit peut servir de référence au modèle, ou de fond direct avec les mêmes calques. Une étiquette dessinée par le modèle ramène le texte parasite.
Exercice. Ajoutez à l'export un fichier provenance.json qui liste, pour chaque fichier du kit, le modèle qui a produit ses pixels, la version, l'approbateur et la date d'approbation.
Voir la solution
# pipeline.py, à la fin de exporter_linkedin()
prov = {
"carrousel-1.png": {"source": self.manifest["assets"]["instagram_portrait"]["fond"],
"modele": self.s.image_model,
"approuve_par": self.manifest["assets"]["instagram_portrait"].get("approuve_par"),
"approuve_le": self.manifest["assets"]["instagram_portrait"].get("approuve_le")},
"carrousel-2.png": {"source": "hero.jpg", "modele": self.s.image_model_final},
"video.mp4": {"modele": self.manifest["video"]["modele"], "approuve_par": self.manifest["video"].get("approuve_par")}
if self.manifest.get("video") else None,
"mention": MENTION_IA,
}
(cible / "provenance.json").write_text(json.dumps(prov, ensure_ascii=False, indent=2), encoding="utf-8")Testez que chaque fichier image du kit a une entrée et que la mention correspond à MENTION_IA.
- Je sais exporter un kit LinkedIn complet depuis des visuels approuvés
- Je sais ce que les conditions de l'API disent de la propriété des contenus générés
- Je sais ce que SynthID prouve et ce qu'il ne prouve pas
- Je sais vérifier qu'aucune ressource du projet ne subsiste