promo-video — Pipeline vidéo promotionnelle zéro-coût
Transformez un projet web en vidéo promotionnelle ou publicité sociale. Insight clé : si l'application se déroule automatiquement (animation, canvas, auto-play), on l'enregistre directement – pas besoin de « générer » la vidéo. Si c'est une galerie d'images, on télécharge les meilleures et on crée un montage Ken Burns. Le reste (narration, vérification audio, composition, marque, CTA) est partagé entre les modes. Validé en production : battlefield-editor/tools/trailer (capture d'écran) + publicité catime « Le chat qui lit le monde » (Ken Burns + actualités → révélation d'images) + « Glitch et M. Trou Noir » intégralité des 7 chapitres en jeu complet (variante avec son, 60 minutes, 700 doublages automatiques : https://youtu.be/J9OMebCjr9Y ).
Arbre de décision : choisir le mode de capture
- App interactive (3D/canvas/auto-déroulement, auto-play) → MODE A Capture d'écran (
capture-app.mjs). - Galerie d'images / flux photo (images IA, galerie) → MODE B Montage Ken Burns (téléchargement des meilleures images +
kenburns.sh). - Enregistrer le son émis par l'application elle-même (variante avec son du MODE A ; ce qui est joué et sa durée sont décidés par l'application en direct, pas de piste audio complète disponible) → abandonner
recordVideo, utiliser ffmpeg pour capturer simultanément la vidéo et l'audio, voir la section « Quand il faut vraiment enregistrer le son de l'application ». Si vous avez la piste audio complète, ne passez pas par cette méthode – le muxage ultérieur donne une meilleure qualité audio (GOTCHA 24). - Repo non visuel (CLI / bibliothèque / API / backend / pipeline de données, pas d'interface à enregistrer) → MODE C Terminal / explication (
term-player.html+capture-term.mjs) : transformez « commande + sortie réelle » en une animation de frappe enregistrée. Pour un outil dev, on fait une « version pédagogique narrée », pas un « teaser rapide » (voir GOTCHA 22) : structure = accroche sur le problème → étapes (installation → exécution → compréhension de la sortie → commandes avancées) + narration edge-tts étape par étape + sous-titres étapes → CTA. La synchronisation de la narration se fait viawindow.__STEPLOGdeterm-player.html(secondes d'apparition de chaque étape). Le script compose ajoute un adelay à chaque phrase de narration pour correspondre à l'étape. Expérience : vidéo pédagogique CLI multi-repo wip (~60s, musique à 0.13 pour que la narration reste au premier plan). - Court métrage / extrait fort en format portrait (9:16 Reels/Shorts) → MODE E Enregistrement sur modèle (
short-9x16.html+capture-short.mjs) : course-pipeline extrait d'abord les meilleures phrases du script, coupe le segment 16:9, ici on l'intègre dans un modèle HTML 9:16 puis on enregistre. Pourquoi utiliser HTML et non un filtre ffmpeg ? Le recadrage est possible avec ffmpeg, mais les animations de mots-clés se font en dix lignes de CSS, alors qu'avec le format ASS pour les sous-titres, c'est un enfer ; modifier la mise en page se fait en modifiant le CSS, pas en ajustant un filtergraph à l'aveugle. Frontière : course-pipeline coupe, promo-video emballe, l'interface est « une mp4 déjà coupée + les timestamps des mots sur la timeline finale ». - Mixte : une capture d'écran comme base, avec des inserts Ken Burns / terminal, tout est possible.
Quatre phases
- Capture — MODE A :
xvfb-runpour lancer Chromium en mode « headed » (nécessaire pour WebGL), PlaywrightrecordVideoenregistre la séquence à fort potentiel (automatique) ; MODE E en headless (voir GOTCHA 47). MODE B : récupération de la liste des sources, sélection, téléchargement. - Narration (optionnelle) — edge-tts
zh-TW-YunJheNeural --rate=-5%génère la voix off. - Vérification (optionnelle) — Gemini pro vérifie la tonalité mot par mot ; Gemini flash sélectionne la musique libre de droits.
- Composition —
brand.pycrée les éléments de marque (carton, logo, CTA, QR), ffmpeg superpose les sous-titres, assemble, mixe, et sort plusieurs formats + version condensée.
Les six modèles (modifier la config en haut / passer des paramètres pour les utiliser)
brand.py— PIL : compositeur respectant les proportions (cover/contain) , cartons, logo permanent, CTA, mini galerie, QR (via API).kenburns.sh— Ken Burns sans tremblement clip + collage de texte sur les bords + concat + mixage (mode principal du MODE B).capture-app.mjs— Playwright + xvfb pour la capture d'écran (MODE A) ; serveur HTTP intégré, cache le HUD, clique sur les nœuds pour sauter les phases / jeu piloté par clavier.term-player.html+capture-term.mjs— MODE C Terminal / explication : lecteur de terminal HTML (animation de frappe + sortie colorée) + enregistrement xvfb. Pas besoin d'asciinema/xterm.short-9x16.html+capture-short.mjs— MODE E : modèle 9:16 (diapositive centrée, grandes sous-titres en haut et en bas, mots-clés en grand avec animation de rebond, logo permanent, barre de progression) + enregistrement xvfb + muxage audio original ultérieur. La zone de diapositive a un mouvement de caméra automatique : on mesure où se trouve le texte sur l'image, on change de composition toutes les 2 à 3 secondes, et on zoome et éclaire la diapositive au moment où le mot-clé est prononcé (GOTCHA 32-37). Pour réenregistrer, utilisez--tag v2pour changer le nom de fichier, n'écrasez pas l'ancienne version — l'ancienne sert de référence. Après l'enregistrement, il imprime automatiquement le « nombre d'événements de luminosité par seconde » et le « niveau sonore final » pour garantir la qualité, aucun contrôle supplémentaire n'est nécessaire.relevel.py— Pour les vidéos déjà publiées qui ont besoin d'ajuster le niveau sonore : ne modifie que l'audio,-c:v copy, testé, le flux vidéo avant/après a le même md5, une seconde par vidéo, pas besoin de réenregistrer. Inutile pour les nouvelles vidéos (capture-short.mjsajuste déjà le niveau lors du muxage).audition.py— Gemini écoute à votre place :music "<usage>" *.mp3(sélectionne la musique libre de droits, note de 0 à 10),tone "<text>" v.mp3(vérifie la tonalité de la narration).
Quand il faut vraiment enregistrer le son de l'application : l'enregistrement réel
Le GOTCHA 24 parle du cas où vous avez la piste audio complète (clip musical, narration edge-tts) : utilisez le fichier original pour le muxage ultérieur, la qualité audio est meilleure. Mais il y a un cas où ce n'est pas possible — ce qui est joué et sa durée sont décidés par l'application en temps réel. Exemple concret : un visual novel complet, 700 fichiers audio sont joués dans l'ordre de l'histoire, il n'y a aucune « piste audio complète » à aligner.
Dans ce cas, abandonnez complètement recordVideo de Playwright et utilisez ffmpeg pour capturer simultanément la vidéo et l'audio :
# 1. Ouvrir un récepteur audio dédié, diriger le son du navigateur, sans mélanger les autres sons du bureau
MOD=$(pactl load-module module-null-sink sink_name=cap)
# 2. Votre écran X
Xvfb :77 -screen 0 1280x808x24 -nolisten tcp &
# 3. Enregistrer vidéo + audio en même temps, fichiers segmentés (une panne ne perd qu'un segment)
ffmpeg -f x11grab -draw_mouse 0 -framerate 25 -video_size 1280x808 -i :77.0 \
-f pulse -i cap.monitor \
-vf crop=1280:720:0:88 \
-c:v libx264 -preset veryfast -crf 24 -pix_fmt yuv420p \
-c:a aac -b:a 160k \
-f segment -segment_time 600 -reset_timestamps 1 seg/part-%03d.mp4 &
# 4. Piloter le navigateur (DISPLAY=:77 PULSE_SINK=cap), une fois terminé, concaténer les segments
Cinq points clés issus de tests réels :
--kioskne fonctionne pas avec Playwright, il gère ses propres fenêtres. La barre d'onglets + barre d'adresse font 88 px fixes, ouvrez l'écran 88 px plus grand et rognez aveccrop. Cachez le curseur avec-draw_mouse 0.- N'utilisez pas de temporisation fixe pour les changements de page. La durée de chaque phrase audio varie ; un délai trop court coupe la fin du mot, un délai trop long double la durée de la vidéo. Interceptez
new Audio(), attendez la fin de la lecture audio avant de passer à la diapositive suivante (ces éléments ne sont pas dans le DOM,querySelectorne les trouve pas). - Il est obligatoire de vérifier que le contenu progresse. Sans cela, si vous rencontrez une carte sur laquelle vous ne pouvez pas cliquer, vous cliquerez à vide jusqu'à la limite, et ffmpeg ne signalera aucune erreur : testé, l'enregistrement a duré 58 minutes, le fichier était normal, mais seulement un quart du livre avait été parcouru. Le critère est « le texte à l'écran a-t-il changé », et un texte vide doit aussi être considéré comme une absence de progression — l'écran de fin n'a pas de texte, l'absence de cette règle a ajouté 33 minutes d'enregistrement.
- Écrivez les fichiers en segments (
-f segment), en cas de panne, un seul segment est perdu, et leconcat -c copyfinal les assemble sans perte. - À la fin, vérifiez quatre choses, ne vous contentez pas de voir que le fichier existe : durée totale, nombre de cartes parcourues, écart type des pixels d'une image échantillon (proche de 0 = image vide), niveau audio moyen de
volumedetect(silence = null sink non connecté, et là encore, aucune erreur n'est signalée).
Exemple concret : glitch-vn/tools/capture/ (record.sh + play_through.mjs). Le résultat est le jeu complet « Glitch et M. Trou Noir » en 7 chapitres, 60 minutes, 700 doublages, réalisé d'une traite sans intervention humaine : https://youtu.be/J9OMebCjr9Y
GOTCHAS — Ce sont les éléments les plus précieux, vérifiez chaque point à chaque fois
-
Respect des proportions:** Ne JAMAIS** utiliser
scale=W:Hsur des images de proportions différentes (cela les étire). Utilisez cover (agrandissement proportionnel puis recadrage) pour l'arrière-plan, et contain (réduction proportionnelle dans le cadre) pour le sujet principal. Le scale de ffmpeg ne peut être qu'un multiple entier (2x) ou ajouterforce_original_aspect_ratio. Avant de finaliser, exécutez un test « ratio d'origine vs ratio d'affichage » (erreur < 0.1 % pour réussir). -
Tremblement Ken Burns : Agrandissez la source 2x avant de la réduire (les sauts de pixels entiers deviennent des sous-pixels) + utilisez le numéro d'image linéaire
z='1+K*on/(D-1)'pour le zoom, NE JAMAIS utiliserz='min(zoom+inc,...)'qui accumule les erreurs d'arrondi. -
Explosion d'images zoompan:** Ne PAS** utiliser
-loop 1 -t(chaque image d'entrée est multipliée par d) ; pour une seule image, utilisez-frames:v D+zoompan d=D. -
Texte collé au bord de l'image : Centrez d'abord l'image verticalement ; la position du texte = centre ± (hauteur de l'image × zmax)/2 ± espace, calculez pour chaque image (PIL sort la hauteur de chaque image en sidecar). Ainsi, les images horizontales ne seront ni trop éloignées ni écrasées.
-
Le fetch
file://est bloqué : L'application utilisefetch(?pkg=)/charge du JSON → lancezpython3 -m http.serveren HTTP (ou le serveur intégré decapture-app.mjs). -
Headless WebGL perd swiftshader/tout noir → Utilisez
xvfb-run -a --server-args="-screen 0 WxHx24"pour lancer Chromium en mode headed. -
Application module ES :
gotoPhasen'est pas sur window → cliquez sur les nœuds de la timeline#tlNodes .node[i].click(), n'utilisez pasevaluate(gotoPhase). -
QR : PEP668 empêche l'installation de qrcode → utilisez
api.qrserver.com/v1/create-qr-code/?data=<lien public>(mettez uniquement des liens publics) ; avant la publication, scannez avec un téléphone pour vérifier. -
Caractères ambigus de edge-tts : Contournez-les d'abord ("没 mò" → changez de mot pour éviter) ; la vérification des tons se fait en demandant à Gemini pro l'évolution de la hauteur tonale, la STT whisper ne peut pas vérifier les tons (elle retranscrirait le même caractère même mal prononcé).
-
Musique : Gemini flash sélectionne la musique CC0, vérifiez l'adéquation (n'utilisez pas de musique de guerre pour un chat) ; notez la source CC0.
-
Multi-formats : 16:9 = YouTube, 9:16 = Reels/Shorts/LINE, 1:1 & 4:5 = FB/IG stories. MODE A : réenregistrez chaque format en natif (viewport portrait) ; MODE B : recomposez chaque format.
-
Logo permanent (watermark) : Marque + URL constamment dans un coin de l'écran (pas seulement sur la carte de fin), superposez un PNG transparent sur toute la vidéo.
-
Une pub doit inciter à l'action : Accroche dans les 2 premières secondes (lecture automatique en mode silencieux, image/gros texte dès la première image) ; sous-titres incrustés (FB en mode silencieux) ; CTA avec une raison « de faire maintenant ».
-
Détails de la plateforme : Les vidéos FB sont publiées dans les posts, mettez le lien dans le premier commentaire (pour éviter la baisse de portée) ;
+movflags +faststart; pour LINE, utilisez l'intégration dans l'album/le bouton vidéo, pas « fichier ». -
Capture d'écran : attendez le chargement complet + vérifiez image par image qu'elles ne sont pas vides : MODE A/gallery capturez les éléments un par un sans utiliser de temporisation fixe (l'image la plus lente sera capturée alors que le « chargement en cours » est encore affiché, donnant une image vide). Interrogez l'état jusqu'à la disparition de l'indicateur de chargement avant de capturer ; après la capture, mesurez l'écart type (stdev) de la zone cible de chaque image (≈0 = vide/non rendu), et récupérez automatiquement les images manquantes en les recapturant. Expérience : roll-formosa Miaoli, un délai fixe de 1.8s a capturé une image vide, corrigé avec wait-for-load + analyse stdev.
-
Zoomez pour vérifier, ne vous fiez pas aux vignettes : Pour valider, prélevez des images en pleine résolution et examinez-les en détail (recadrage, bordures floues, éléments coupés invisibles sur les vignettes). Pour mesurer les coordonnées, tracez des lignes de grille y/x sur l'image originale. Expérience : le bord inférieur d'un monument a été rogné de 55 px, la moitié inférieure floue, invisible sur la vignette, découvert en zoomant et en traçant une grille.
-
Jeu interactif sans auto-play → pilotez-le avec le clavier (sous-mode du MODE A) : Les jeux Katamari/action utilisent Playwright
keyboard.down/up(appuyez sur les touches directionnelles pour rouler). Le jeu commence souvent ~10s après le chargement (precache/PREPARING) : attendez d'abord l'écran START (interrogez l'état du bouton) → cliquez sur START → puis envoyez les touches du clavier. Un bot aveugle peut ne pas attraper d'objets/grandir (roll-formosa en mode paysage est resté à 2 cm) — prenez la fenêtre « la plus visuelle » (balle assez grande, objets nombreux), ou passez à la méthode suivante : showcase. -
Gameplay difficile à filmer → utilisez un showcase/bibliothèque automatique comme source : De nombreuses applications ont une page de « galerie/aperçu des objets » qui défile automatiquement (par exemple
preview.html?city=X), qui rend tout le contenu sans avoir à jouer. C'est une source fiable de montage (roll-formosa 20 monuments). Ces éléments sur fond sombre doivent utiliser un fond sombre uni, pas un flou d'arrière-plan (sinon, le flou crée des artefacts fantômes). -
L'écran « prêt » se trouve à la fin de l'enregistrement : recordVideo enregistre à partir du chargement, le « PREPARING → START » prêt apparaît dans la partie ultérieure ; enregistrez assez longtemps (attendez START + 3s supplémentaires), puis coupez à partir de la fin pour prendre la séquence prête, pas depuis le début (cela couperait le chargement).
-
Le viewport portrait du téléphone est souvent meilleur : La mise en page mobile peut être plus pleine, voire avoir un comportement différent (roll-formosa, le gameplay en portrait agrandit la balle, en paysage elle reste bloquée). Lors de l'enregistrement natif de chaque format, vérifiez en même temps quel viewport donne les meilleurs résultats.
-
Texte précis : La description du personnage/objet utilise la personnalité/les bizarreries plutôt que l'apparence (casting de catime) ; pour la publicité « actualités → images », la phrase d'accroche doit être déduite du champ idea/story des données sources, ne l'inventez pas (publier une mauvaise actualité serait embarrassant).
-
Teaser ≠ tutoriel, ne vous trompez pas de format : Un projet visuel (jeu/application) convient à un teaser rapide de 15-20s (taquiner, attirer l'attention) ; mais un outil CLI/dev en teaser rapide sera ennuyeux, pas pédagogique — créez plutôt un tutoriel narré de ~60-90s : accroche sur le problème →
installation→exécution→compréhension de la sortie→commandes avancéesen étapes + narration edge-tts pas à pas (lent, chaque étape assez longue) + sous-titres étapes → CTA, musique très basse (~0.13) pour laisser la narration au premier plan. Expérience : une première version teaser rapide de wip a été jugée ennuyeuse, la version tutoriel narré a été plus convaincante (« j'ai compris, je vais l'utiliser »). Synchronisation de la narration via__STEPLOGde term-player. term-player doit vider l'écran à chaque étape (problème similaire au GOTCHA 16 : le contenu accumulé sort du champ de vision, la commande prononcée est repoussée). -
Lisez d'abord le README complet / l'aide
--helppour établir la liste des fonctionnalités, puis écrivez le script : Pour un tutoriel/explication d'outil, ne lisez pas seulement le début avant de commencer à filmer — vous risquez de manquer les fonctionnalités phares (wip a oubliédashboard.pyvisual dashboard,wip done,--root,install-skill, support Codex, détection de fichier de plansee:). Listez d'abord toutes les fonctionnalités, puis choisissez celles à montrer. La TTS ne doit pas prononcer les noms de compte/handle/orthographes rares (par exempleyazelinest en fait la transcription pinyin du nom chinois « Ya Zelin », la prononciation lettre par lettre est étrange) — si l'URL est déjà à l'écran, laissez la narration passer à côté (« lien ci-dessous ») ; évitez les mots de début qui coincent (« d'abord… » → « … c'est simple »). edge-tts prononce correctement les termes techniques anglais (cargo/markdown/Claude Code), donc pas de problème, mais vérifiez avec Gemini flash une phrase pour confirmer. -
MODE D Clip musical : une chanson entière + interaction, n'enregistrez pas le son, synchronisez avec le fichier original plus tard.
recordVideode Playwright ne contient pas de piste audio ni de curseur (si vous n'avez pas de piste audio complète, voyez la section « Enregistrement réel »), donc : (a) le son est mixé avec le mp3 original plus tard (meilleure qualité, pas d'impact du null sink de xvfb) ; (b) injectez un curseur factice (collez une image qui suit le movement du pointeur, uniquement pour l'enregistrement, pas dans le repo), sinon, dans la vidéo, les « interactions de la souris » ressembleront à des animations qui se déclenchent toutes seules. La timeline des actions doit être liée àaudio.currentTimeen interrogeant (waitForFunction(t => au.currentTime >= X)), n'utilisez paswaitForTimeoutcumulé — lorsque l'enregistreur perd des images, l'horloge de l'ordinateur dérive, pas l'horloge audio. -
Pour la synchronisation, utilisez l'horloge à l'écran, pas
Date.now()de Node. Il y a un décalage incertain entre la première image du webm etnewContext(), et Playwright ajoute une longue séquence d'images figées à la fin (testé : 160 secondes de contenu → fichier de 239 secondes), la durée deffprobene peut pas être utilisée comme longueur réelle. Méthode : rognez la zone de la barre de progression, mesurez la position du curseur image par image (prenez la colonne avec le plus haut niveau de rose comme pic, puis le centre de masse — les particules qui passent sont de la même couleur, une simple moyenne serait faussée), faites une régression linéaire detemps vidéo → temps audiopour déterminer le début de l'audio. Croisez trois méthodes d'estimation (régression du curseur, moments de changement de ligne des paroles, horloge de l'ordinateur au moment du clic) et prenez la médiane ; la ligne des paroles a un retard d'environ 0.1s dû aux transitions CSS, n'oubliez pas de le soustraire. Avant de finaliser, utilisez la corrélation croisée pour vérifier le délai de la piste audio finale par rapport au fichier original (le priming AAC ajoute ~40ms, normal), puis prenez trois images « loin d'un changement de ligne » pour confirmer que la bonne ligne est en surbrillance. -
Pour le format portrait (9:16), ne vous fiez pas au zoom du navigateur pour augmenter la résolution.
recordVideocolle « l'image de la taille CSS » dans la taille que vous spécifiez : viewport 405×720 avecrecordVideo.size1080×1920 donne un contenu 405×720 collé en haut à gauche, le reste rempli de gris ; si vous utilisezdeviceScaleFactorpour augmenter les pixels de l'appareil, la simulation de viewport de Playwright le force à 1 ; utilisez--force-device-scale-factor+--window-size, xvfb n'a pas de gestionnaire de fenêtres,--window-size,Browser.setWindowBounds,--kioskne modifient pas la fenêtre (innerWidth mesuré à 501).html{zoom:...}ne fonctionne pas non plus, les media queries lisent toujours la largeur d'origine. Solution viable : ouvrez le viewport directement en 1080×1920 natif (DSF 1), puis injectez une feuille de style « version portrait agrandie » (disposition mobile + taille de police/contrôles agrandis proportionnellement, avec!importantpour remplacer la grille de la version desktop). Le style dédié à l'enregistrement ne va pas dans le repo, il est injecté au même endroit queHIDE. Vérifiez la netteté du texte en zoomant (GOTCHA 16) : le texte étendu de 405 à 1080 a des bords flous, le rendu natif est net. -
Si possible, faites en sorte que « l'image parle d'elle-même » : Pour l'enregistrement d'interactions, organisez les opérations à montrer sur une timeline (clic, glisser, arrêt), avec des pauses volontaires pendant lesquelles rien ne se passe pour que l'interface utilisateur qui se cache automatiquement soit masquée — un mouvement continu empêche de voir ce qui est important. N'oubliez pas que recordVideo ne contient pas de curseur, injectez-en un factice (collez une image qui suit le pointeur), sinon le public verra des animations qui se déclenchent toutes seules.
-
Pour l'ancrage temporel, utilisez un « flash blanc », pas un « écran noir ». L'idée était « l'écran devient noir au début, la vidéo ne commence vraiment qu'après, on cherche la première image claire pour t=0 » — test pratique non concluant : recordVideo de Playwright ne commence pas à capturer dès la première image, l'image la plus sombre de toute la vidéo enregistrée est 24.4/255, il n'y a pas d'image noire à reconnaître. Solution : ajoutez un blanc total de 400ms avant le début de la diffusion, coupez-le au moment où la vidéo commence, et cherchez « l'image juste après la fin du flash blanc ». Le blanc 250 par rapport au contenu 24-37 est impossible à confondre. Vérifié : l'erreur de décalage est de ±0.05s sur toute la durée, pas de dérive.
-
metadata=printécrit sur stderr par défaut. La sortie deffmpeg -vf "...,metadata=print:key=X"n'est pas sur stdout, si le script ne récupère que stdout, il lira toujours vide, puis interprétera « rien lu » comme « l'image est noire ». Ajoutez:file=-. -
Le serveur HTTP intégré doit d'abord lire le fichier avant d'envoyer l'en-tête. Si
res.writeHead(200)est écrit avantawait readFile, le 200 est envoyé même si le fichier n'existe pas, et le catch qui tente de renvoyer un 404 génère uneERR_HTTP_HEADERS_SENT— la véritable cause (mauvais chemin) est masquée par cette fausse erreur. Danscapture-app.mjsde ce repo, ce problème était présent, il a été corrigé. -
Pour vérifier la synchronisation A/V, utilisez une mesure discriminante. « Prélever une image au même moment et corréler » semble raisonnable, mais pour un contenu de type diapositive, l'image ne change pas pendant des dizaines de secondes ; un contrôle négatif (retard intentionnel de 6s) donne aussi 0.996, ce qui est équivalent à une non-vérification. Méthode viable : ajoutez une barre de progression sur le modèle (largeur =
currentTime / duration), mesurez sa largeur sur l'image finale pour lire le temps interne du lecteur, et soustrayez-le directement du temps de sortie. Cette barre de progression sert également d'élément visuel pour le produit final, pas seulement de test. -
Les courtes vidéos en 9:16 avec des diapositives fixes ont des images « mortes » — et on peut le mesurer. La différence de niveaux de gris entre images pour une diapositive fixe n'est que de 0.072 / 0.092 (référence d'image gelée : 0.000), le retour des utilisateurs est « trop plat, manque de punch ». Ne fixez pas le seuil par intuition, prenez vos propres vidéos 9:16 déjà publiées et mesurez avec le même outil pour déterminer une fourchette acceptable : neko-tensei clip musical 0.233, k-rider teaser 1.045, battlefield-editor 1.293, roll-formosa 1.912, publicité catime 3.965, teaser catime 6.168 (active_ratio 0.636 = montage rapide). Un court métrage narratif avec des mots-clés ne doit pas être un montage rapide.
Objectif 0.30 à 1.05, active_ratio 0.10 à 0.40Ce seuil est caduc :meanetactive_ratione sont pas normalisés par rapport au framerate, et utiliser vos propres productions comme seuil est un raisonnement circulaire. Le critère actuel estmotion.slide.per_s ≥ 5.5dupunch-score.py(variation de niveaux de gris par seconde,mean × fps, seule valeur qui ne dépend pas du framerate), pas de limite supérieure, le risque de « flash » est géré par le GOTCHA 35. Exemple : la v1 jugée « image morte » avait 1.79 et 2.30, tandis que la production la plus calme publiée (neko-tensei) est à 5.82 et doit passer le test. -
Pour le mouvement de caméra sur des diapositives fixes, la position doit être mesurée sur l'image elle-même, pas définie par un ratio fixe. Les diapositives 960×720 ont seulement 250 lignes de balayage avec du texte, le reste est vide. Un recadrage fixe haut/milieu/bas coupe la petite ligne en haut de la page ou une grande zone vide. Méthode : capturer une image avec canvas → mesurer la quantité d'encre par ligne (différence par rapport à la médiane de l'arrière-plan) → regrouper en bandes → jeter les bandes avec moins de 12% d'encre → les extrêmes gauche et droit des bandes restantes sont la zone de corps du texte (SP) , la caméra ne doit jamais couper la SP. Ne réduisez pas l'image lors de la mesure : en réduisant de 8x (120×90),
drawImagene fait pas une moyenne de surface, les traits fins disparaissent ("的路。" à droite du corps n'a pas été mesuré, SP manque 96 px), et le dégradé d'arrière-plan est interprété comme une zone pleine largeur. Quel que soit le seuil, il y a un compromis, mais avec la résolution originale, le problème disparaît, et l'« encre » peut utiliser la même définition que pour la validation (28 au-dessus de la médiane de l'arrière-plan). Le balayage complet de 960×720 ne prend que quelques dizaines de millisecondes dans Chromium headless. -
Pour le cadrage, mesurez « l'image qui est en train d'être lue », ne la cherchez pas d'abord. Pour mesurer « la page où le mot-clé est prononcé », faites
V.currentTime = milieu du mot-clépuisdrawImage—seekedgarantit seulement que le décodage est terminé, l'image n'a pas encore changé, testé : pour toute la vidéo quote-06, c'est la disposition de la page 0 (la page précédente) qui a été mesurée pour le cadrage : le SP a été mesuré à 596 au lieu de 696, donc le cadre « garantissant de ne pas couper les caractères » était faux depuis le début, et pour le mot-clé phare de 2.8 secondes, la moitié des images avaient un coin du texte manquant. La méthode fiable est de mesurer pendant la lecture : utilisez l'image de la seconde 0 pour le début (c'est ce qui est à l'écran à ce moment), puis mesurez à nouveau à chaque changement de plan. Cela résout également le problème d'une « phrase qui s'étend sur deux pages » — après un changement de page, le changement de plan suivant prend le relais. -
Les événements de luminosité doivent avoir une limite mesurable, ne vous contentez pas de dire « je l'ai fait lentement ». Mesurez le YAVG par image de la bande de diapositives, seuil avec 10% relatif plus un plancher absolu de 4/255 — le fond de la diapositive a un Y d'environ 20, utiliser uniquement une valeur absolue comme seuil ne détecterait pas une augmentation de 9% de luminosité ; utiliser uniquement une valeur relative serait pollué par le bruit dans les zones sombres. Comptez « combien de fois dans la seconde la plus dense », limitez à 3 fois (seuil de risque de crise d'épilepsie photosensible). Testé : vidéo finale : 2 fois/s ; contrôle négatif : scintillement 5 Hz : 26 fois/s, pulsation lente 1 Hz ±6% : 6 fois/s, les deux sont bloqués. Cette vérification est déjà intégrée dans
capture-short.mjs, elle est imprimée automatiquement après l'enregistrement. -
Le filtre
eqdeffmpeg, l'expression brightness n'est évaluée qu'une seule fois par défaut (eval=init). Lors du test de contrôle négatif de « scintillement volontaire », j'ai oublié:eval=frame, le filtre n'a eu aucun effet, et le résultat mesuré était identique à la vidéo finale — j'ai failli conclure à tort que cet indicateur « ne faisait pas la différence ». Le contrôle négatif lui-même doit être validé comme étant réellement défectueux, sinon il ne fera que prouver que votre outil ne peut pas distinguer deux fichiers identiques. -
La différence d'images n'est jamais discriminante pour la synchronisation. Re-test du GOTCHA 31 avec un nouveau modèle : les mots.json ont été intentionnellement décalés de 6 secondes, la différence d'images des diapositives est de ...identiques 0.718 (elle ne regarde pas du tout words.json). Ce qui change, c'est le « moment du changement de bloc de sous-titres vs moment de la plus grande différence d'images dans la zone de sous-titres » (0.091 → 0.567). Les indicateurs dynamiques ne peuvent que décrire la dynamique, la synchronisation doit être mesurée séparément.
-
Le découpage en blocs des sous-titres colle deux mots-clés adjacents dans le même bloc. Le découpage ne change de bloc que lorsque le texte ne tient plus, et lors du passage à un autre mot-clé, il tient souvent encore — testé : « un très grand point d'interrogation » « n'a pas été vérifié » sans aucun mot entre eux, collés en un bloc de 11 caractères, tout en or, deux points forts réduits à un seul (il ne reste que 2 blocs lumineux). Correction : ajoutez une règle : si
k >= 0etk !== k du caractère précédent, forcez un nouveau bloc. Après correction, il y a 3 blocs lumineux, le bloc le plus grand passe de 11 à 6 caractères. Notez quepunch-score.pycontient la même logique de découpage, à mettre à jour en même temps. -
Pendant l'exécution, le zoom est secrètement multiplié, il faut donc soustraire la marge lors du calcul de la largeur de cadrage. Le zoom lent (+3.5%) et le zoom sur le mot-clé (+5%) totalisent 8.7% : un cadrage « parfaitement ajusté », une fois zoomé, le corps du texte sort du cadre, et cela se produit précisément pendant les secondes du mot-clé — le moment où cela ne devrait pas arriver. Il faut faire deux choses : lors du cadrage,
z = largeur autorisée / 1.087, et re-vérifier et ajuster à chaque image (z ≤ largeur autorisée,cxdoit être tel que les deux extrémités de SP sont dans le cadre). La condition doit être vérifiée au moment où l'image est produite, pas seulement au moment du cadrage théorique. -
Une fois que l'en-tête ou le pied de page de la diapositive est placé au centre de l'image, les côtés gauche et droit sont forcément coupés, et cela est compté comme une coupure du corps du texte. La petite ligne d'en-tête (x 24 à 932) et la barre de progression en bas (x 0 à 759) dépassent la zone du corps ; lorsque la caméra zoome, elles sortent des deux côtés de l'image. Lors de la validation, les bords supérieur et inférieur de la bande de diapositives ont été ajustés d'un dixième chacun pour représenter l'en-tête et le pied de page, puis le mouvement de caméra modifié a placé ces éléments à l'intérieur de cette ligne, les faisant compter comme du texte coupé. Correction : toute ligne dont « l'encre dépasse le SP » est marquée comme un élément de mise en page (critère : pas « pleine largeur » — la barre de progression en bas n'a que 79% de la largeur sous le seuil d'encre, un test de pleine largeur la manquerait), puis on limite le centre de la caméra pour que ces éléments restent dans le dixième supérieur ou inférieur de l'image, ou hors du cadre. Laissez quelques pixels de marge sur les bords : si c'est juste sur le dixième, l'arrondi de l'échantillonnage peut faire tomber les dernières lignes de l'élément dans la zone de texte (testé : il restait 15 images, toutes à la ligne 81 et 82).
-
Pour diagnostiquer un problème de cadrage, exécutez d'abord l'algorithme hors ligne sur la même image, ne modifiez pas les paramètres à l'aveugle. Un enregistrement de 40 secondes, essayer des seuils à l'aveugle conduit à « empirer en ajustant » : testé, la désactivation du zoom lent et du zoom sur mot-clé (pensé comme la cause principale) a augmenté le nombre d'images avec texte coupé de 102 à 189. Utilisez ffmpeg pour extraire la même image de détection, exécutez la même logique de bande/SP en Python, et vous verrez en dix secondes que « le SP mesuré par le navigateur est identique à celui de la page 0 », et la cause principale apparaît immédiatement. De plus, imprimez les conditions de cadrage dans le log (SP, z de la largeur autorisée, positions des éléments de mise en page) — lorsqu'un plan est raté, ces trois chiffres indiquent si la largeur a été mal mesurée ou si le couloir a été écrasé.
-
Pour signaler les chiffres d'une production, séparez « ce qui vient du contenu » de « ce qui vient de l'image », et ces derniers doivent être accompagnés du md5 du modèle. Ce qui vient du contenu (accroche, coloration, blocs lumineux, blocs de sous-titres, silence, vitesse d'élocution) est lié à
words.json, et si le md5 de ce fichier ne change pas, ces chiffres ne bougent pas d'un bit ; ce qui vient de l'image (différence d'images, texte coupé) n'est valable que pour la version du modèle utilisée lors de l'enregistrement. Si l'on mélange les deux, la personne suivante qui réenregistre selon la documentation obtiendra des chiffres différents : testé, pour le même clip, la version du modèle avant modification donnait une variation par seconde de 14.80, après modification 27.19 — un facteur 1.8, alors que les six indicateurs de contenu étaient identiques. Cette personne a donc passé un cycle à chercher une régression qui n'existait pas.capture-short.mjsimprime maintenant à chaque enregistrementmodèle short-9x16.html md5 …, n'oubliez pas de le copier dans le rapport. -
L'enregistrement lui-même a des variations, le critère doit tolérer cette variation. En fixant le modèle et l'entrée avec md5, en enregistrant quatre fois de suite, la variation par seconde de la bande de diapositives était de 21.27 / 21.33 / 20.82 / 21.3
...