Site Reels: site → MP4 → deck
Prérequis
| Nécessaire pour | Quoi | Vérification |
|---|---|---|
| tout | Windows, macOS ou Linux avec un GPU et un bureau visible (Chrome s'exécute en mode headful) | n/a |
| tout | Node.js 18+ | node -v |
| tout | ffmpeg + ffprobe dans le PATH (ou FFMPEG=<chemin complet>) | ffmpeg -version |
| tout | Chrome, Chromium ou Edge (détection automatique, voir ci-dessous) | node -e "import('./scripts/find-chrome.mjs').then(m=>console.log(m.findChrome()))" |
record.mjs | puppeteer-core + puppeteer-screen-recorder (npm, dans le projet) | npm ls puppeteer-core puppeteer-screen-recorder |
snap.mjs | puppeteer-core + timesnap (apporte timeweb) | npm ls timesnap |
| showreel | Projet Remotion | npx remotion --version |
| audits (optionnel) | ImageMagick | magick -version |
| Téléversement Figma | Figma MCP (use_figma, upload_assets) + compétences figma-use, figma-use-slides | Liste des outils MCP |
Détection de Chrome (scripts/find-chrome.mjs, utilisé par record.mjs et snap.mjs) : variable d'environnement CHROME si définie ; sinon Chrome/Edge installé (Windows : Program Files et LocalAppData ; macOS : /Applications et ~/Applications ; Linux : google-chrome, chromium, microsoft-edge dans le PATH) ; sinon le plus récent ms-playwright/chromium-<N> (ou PLAYWRIGHT_BROWSERS_PATH) ; sinon il s'arrête et vous indique comment définir CHROME ou exécuter npx playwright install chromium. Sous Windows, il ajoute --use-angle=d3d11 ; sur les autres OS, Chrome conserve son backend GPU par défaut.
Ce qu'il fait
Enregistre des scènes scriptées d'un site exécuté localement et produit des MP4 prêts pour Figma (1920×1080, 30 ips, x264, < 100 Mo). Moteur validé : puppeteer-core + puppeteer-screen-recorder (screencast CDP + ffmpeg). Jamais de capture d'écran.
Exécution
mkdir <proj>-reels && cd <proj>-reels && npm init -y && npm i puppeteer-core puppeteer-screen-recorder
mkdir -p scripts && cp <skill-dir>/scripts/{record,find-chrome}.mjs scripts/
cp <skill-dir>/scripts/scenes.example.mjs scenes.mjs # éditer les scènes
# dans le dépôt du site : npx next build && npx next start -p 3100 (build production : WebGL plus fluide)
OUT="<dossier de sortie>/Videos" node scripts/record.mjs scenes.mjs [scene ...]
Exécutez les scripts copiés dans le projet : ESM résout puppeteer-core depuis le dossier du script, pas depuis le répertoire de travail courant.
Environnement : BASE (par défaut http://localhost:3100), OUT, CHROME, FFMPEG, W, H, DPR, MOBILE=1 (émulation tactile), KEEP_UI=1 (conserve la barre de navigation), MIN_FPS (24). Téléphone 4:5 : W=360 H=450 DPR=2 MOBILE=1 donne 720x900.
Chaque scène = { name, path, theme?, trim?, crf?, css?, prepare(page, ctx), act(page, ctx) }. prepare s'exécute avant l'enregistrement (faire défiler jusqu'à la section, attendre le chargement de la 3D) ; act est ce qui apparaît dans la vidéo. ctx dispose de wait, scrollTo(page, y|selecteur, ms) et scrollThrough(page, selecteur|null, ms, from, to) avec easing. Introduction qui s'exécute au chargement : page.reload() dans act et utilisez trim pour couper le chargement.
Script de marque qui fonctionne : introduction logo/héros (clair et sombre), échange du média héros, section 3D avec la caméra qui entre au défilement, parcours immersif (walk), mot de bas de page et un passage pleine page. 5–7 scènes de 12–55s.
Audit avant livraison (ne pas se fier au code de retour)
- Planche contact par vidéo :
ffmpeg -i x.mp4 -vf "fps=6/<dur>,scale=480:-1,tile=3x2" -frames:v 1 sheet.pnget regardez. - 1 Mo pour 19s = scène vide. > 100 Mo = ne rentre pas dans Figma (le paramètre
crfde la scène corrige ; 24 pour une scène avec grain + trails). - Bandeau supérieur (
crop=1920:120:0:0) à plusieurs moments pour attraper une barre de navigation qui fuit. LOW-FPSavec une longue pause au début/fin est une fausse alerte (le compteur compte les images fixes) ; vérifiez la planche contact.
Pièges connus
- Fenêtre cachée fige rAF → images figées. Le lancement passe déjà
--disable-backgrounding-occluded-windows --disable-renderer-backgrounding --disable-background-timer-throttling --disable-features=CalculateNativeWinOcclusion.headless: falseintentionnel : le mode headless tombe sur SwiftShader et le WebGL bégaye. - Le CSS Hide-UI disparaît au rechargement et quand l'hydratation React vide
<head>→ le script rattache une<style id="reels-hide">au body toutes les 100 ms. - Un sélecteur invalide tue toute la règle :
\[dans un template literal JS devient[et.z-[60]invalideheader, nav, .z-[60]. Une règle par sélecteur risqué. - Ne pas cacher
[data-cursor]: les sites marquent le canvas 3D et les carrousels avec. Ciblez l'élément curseur personnalisé (position fixed + mix-blend + pointer-events-none). - 3D chargée paresseusement (IntersectionObserver + import dynamique) : attendez 5–7s après avoir atteint la section. La lecture du canvas WebGL revient vide sans
preserveDrawingBuffer: utilisez une attente fixe ou une capture d'écran. - Le screencast délivre ~32 images uniques/s → enregistrez à 30 ips (60 donne des images répétées irrégulières). Le
trimpar défaut de 0,3s supprime la première image mise en cache. - Le screencast bégaye sur une machine chargée (Photoshop ouvert, rendu parallèle) : le MP4 indique "30 ips" avec 9–18 images uniques/s.
record.mjsmesure (mpdecimate=hi=64:lo=24:frac=0.05) et réenregistre jusqu'à 2x en dessous deMIN_FPS(24). Pour les plaques WebGL/3D, préférezsnap.mjs. - Bannière de cookies : acceptez-la dans
prepare(clic générique sur "Accepter/Aceitar/OK") avant l'enregistrement. - Une page outil sans défilement (générateur, éditeur d'effets) enregistre une image fixe : la scène doit interagir (téléverser un fichier dans
input[type=file], cliquer sur les préréglages par le texte du bouton). Un slider personnalisé ne répond pas à uninputsynthétique.
Plaques verrouillées à 60 ips : snap.mjs (déterministe)
scripts/snap.mjs injecte timeweb (l'horloge virtuelle de timesnap : rAF, performance.now, setTimeout et video), avance de 1/60s par image et prend des captures d'écran. Indépendant de la charge de la machine. Scène : { name, path, theme, css, dur, wait (ms réels pour charger la 3D), pre (ms virtuelles avant le défilement, 2000 ; **0 pour une intro qui s'exécute au chargement**), start (s virtuelles ignorées), scroll: { sel, from, to, at, len } | { bottom: [from, to] en hauteurs d'écran }, frame(page, t) (ex. souris) }. Le défilement est une fonction du temps virtuel.
npm i puppeteer-core timesnap && mkdir -p scripts
cp <skill-dir>/scripts/{snap,find-chrome,patch-timeweb}.mjs scripts/
npm pkg set scripts.postinstall="node scripts/patch-timeweb.mjs" && npm run postinstall
FPS=60 OUT=... node scripts/snap.mjs scenes-snap.mjs [scene]
- N'utilisez pas
timesnap()lui-même : avec Next + R3F + Lenis, il a enregistré une page blanche et laissé des étendues figées sur le héros WebGL (rAF ne s'exécute pas). La boucle personnalisée (goTo + scrollTo + capture d'écran) a fonctionné partout : 60/60 images uniques. - Le patch timeweb est obligatoire : il remplace
video.play()par une version sans Promise et sans événementplaying→video.play().catch()lance une exception et la page devient blanche. preserveDrawingBuffer: trueforcé sur getContext (désactiver avecpreserve: false).- Les images vont dans le répertoire temporaire du système d'exploitation, jamais sur un disque synchronisé (des milliers de jpg qui se synchronisent).
- Aucun travail GPU en parallèle (rendu Remotion, Photoshop) : WebGL perd son contexte et enregistre des secondes de fond seulement. Le script avertit
FLASH(image qui clignote par luminance) ; une longue étendue blanche n'apparaît que sur la planche contact. Regardez. - Audit de fluidité réelle :
magick compare -metric MAEentre images voisines en pleine résolution. 0 = image répétée. - Livraison :
scripts/deliver.example.sh(copier et renommer les plaques ;V=requis) construit des révélations, des boucles (loop.sh) et de la 3D propre, en augmentant crf de 3 jusqu'à < 95 Mo.
Téléversement vers Figma Slides (caché, sans déranger l'équipe)
Le MP4 ne passe pas par MCP : use_figma n'a pas de réseau et l'API Plugin ne crée pas de vidéo à partir d'octets ; upload_assets n'accepte que les images (≤ 10 Mo). Donc :
- Couverture de chaque vidéo :
ffmpeg -ss <t> -i x.mp4 -frames:v 1 -q:v 2 _capas/x.jpg. use_figma(compétencesfigma-use+figma-use-slides) :createSlideRow(idx)avant la dernière diapositive,row.name = 'WIP · Motion (caché)', uncreateSlide(idx, i)par vidéo avecisSkippedSlide = true, fond noir, un rectangle 1920×1080 nomméVIDEO · x.mp4et l'instruction dansspeakerNotes.upload_assetsavec lesnodeIdsdes rectangles → requête POST multipart des couvertures.- L'utilisateur glisse le MP4 sur le rectangle (remplace la couverture par la vidéo). Dites-le clairement ; ne promettez jamais une vidéo insérée.
getSlideGrid() omet les diapositives masquées : vérifiez la structure via SLIDE_GRID.children[i].children.
Showreel de marque (Remotion) : plaques + scènes conçues
Pour un showreel de marque (logo, couleurs, typographie, mises en page, manifeste, maquettes), enregistrez uniquement des plaques propres du site (pas de texte : le CSS par scène cache le titre/la barre de navigation) et composez le reste dans Remotion (rendu image par image, sans tremblement). Blocs de construction réutilisables utiles dans src/fx.tsx : Mist, MistWords, Grain, KenBurns, Plate, une transition nevoa() (brouillard), avec des données de marque dans brand.ts.
- L'identité visuelle vient du deck, pas du site : vérifiez la typographie et la palette officielles dans les diapositives (les sites s'éloignent du guide de marque).
- Scènes qui fonctionnent : phrase d'ouverture dans le brouillard, révélation enregistrée, construction du logo (
evolvePath+ guides mesurés sur le raster du logo), phrase par phrase du manifeste, piliers avec axewghtvariable, palette en bandes, typographie, graphiques superposés, 3D avec titre enmix-blend-difference, colonnes de mise en page défilante, montage maquette 1/2/3, produit synchronisé avec le nom, fermeture sur le mot de marque. - Révision par images fixes (
npx remotion still ... --frame=N+ montage) avant le rendu. - Rendu avec
--concurrency=3: à 8, avec Photoshop ouvert, une machine de 32 Go manquait de mémoire. - Boucles :
scripts/loop.sh in out [start] [dur] [crossfade]. Vérifiez la jointure avecmagick compare -metric RMSEentre la première et la dernière image (< 0,5 % = invisible). - Une mauvaise plaque est un rebut : un solide de révolution qui tourne ne montre aucun mouvement ; une caméra cadrée pour un format portrait est petite en 16:9.
- Showreel frénétique (rythme approuvé) : 25–35s, 60 ips,
Seriesavec coupures franches sur une grille de 120 BPM (30 images), rafales d'images toutes les 3–6 images, recadrage géant du logo sur une grille, poids de police clignotants, blocs de palette se réarrangeant, portrait dans un masque circulaire, rafale graphique, phrase cinétique, maquettes en un temps, logo de fermeture maintenu ~2,5s. Zéro étiquettes : texte uniquement comme graphiques. Un showreel lent de 1:40 a été rejeté. - Showreel doux/enfumé : enregistrez la fumée du héros sans logo ni photo, noir sur blanc pur (scène avec
storage= params dans localStorage etblock: ['assets/logo.png']; le PNG blanc doit être transparent ; ne bloquez pas la vidéo : une vidéo cassée gèle timeweb). Utilisez la plaque comme superpositionmix-blend-mode: multiply: ouvre au début de la scène, une version inversée (ffmpeg -vf reverse) la ferme ; la coupe atterrit sur le noir. Transition à ~1,1s (taux 2), ou la moitié de la scène devient noire. - Assemblage de mises en page Figma Slides : clonez la diapositive (masquée), dupliquez chaque carte 3x avec seulement le fond / le logo / le texte visible, téléchargez chaque nœud avec
download_assets(échelle 2) et supprimez les clones. Slides n'exporte pas la transparence : exportez le logo/texte avec la diapositive noire puis blanche et extrayez l'alpha par différence (ImageMagick). Ne renvoyez jamais de base64 viause_figma(fait exploser le contexte).
Showreel produit (application réelle filmée)
Les écrans sont filmés pour de vrai (Playwright + CDP screencast à 2x, étapes goto/move/click/type/scroll/wait, trace du pointeur sauvegardée sous <id>.cursor.json) et le curseur est redessiné dans Remotion par-dessus.
Rejeté, ne pas répéter :
- Coller un showreel conçu sur l'écran réel (ouverture illustrée + milieu filmé + CTA dessiné) : deux langages, le personnage disparaît et revient, la carte noire entre les actes arrête le rythme. Un langage par vidéo.
- Écran produit petit et accéléré dans une fenêtre 16:9 : le flux et le classement deviennent un mur illisible.
- Zoom qui recadre l'écran et texte estampillé sur la vidéo (verbe géant, compteur 01/07, dégradé) : intrusif.
- Attente vide à l'écran ("attente…") : coupez directement à l'action.
- Copie vague : elle doit être évidente et dans le langage courant du public.
La forme qui fonctionne : par action, d'abord le texte seul, centré et court (2 temps), puis l'écran entier, non recadré, dans une fenêtre centrée au format de capture, sur un fond propre. Texte discret (poids 700, entrée mot par mot avec léger flou), pas de compteur, pas de dégradé. Coupures sur la grille de piste (14 images = 128,57 BPM). 40s max.
Technique qui a payé :
- Accélérer une capture :
<OffthreadVideo trimBefore={from} playbackRate={rate}>et recalculer les images de clic/ rate(son et anneau du curseur). - Démarrer une autre composition à mi-parcours :
<Sequence from={X}><Sequence from={-start}><Comp/></Sequence></Sequence>. Vérifiez la première image du segment : un élément partagé "volant" arrive lâche. Sautez quelques images et replacez le son d'impact qui est tombé avant. - Langage par composition avec une timeline au niveau du module : un drapeau
globalThisdéfini par son propre fichier d'entrée importé avant le Root. - Piste continue à travers les coupures :
trimBeforesur la piste sonore pour qu'elle continue là où le segment précédent s'est arrêté. - Loudness : rendu, mesure et normalisation avec
loudnormen 2 passes (-14 LUFS / -1 dBTP), en copiant la vidéo. Mesurez uniquement à la fin : le mix brut de Remotion dépasse 0 dBFS. - Audit : planche contact horodatée (
fps=2,drawtext=%{pts\:hms},tile=8x6) de chaque rendu, et une planche dense (fps=6) à chaque jonction de segment. - Un projet sans git, édité par une autre session en même temps : nouvelle coupe dans un nouveau fichier, sauvegarde
*.bak-*avant d'éditer un fichier partagé, et prévenez l'utilisateur.
Cartes de portfolio et showreel studio
record.mjsavecKEEP_UI=1(la carte montre le site tel quel) et le préréglage téléphone ci-dessus.- Scènes générées à partir d'un fichier d'inventaire (uniquement les éléments publics avec une URL en ligne) ; pas d'URL en ligne = Ken Burns de la capture d'écran.
- Package de carte : boucle crossfade de la fin au début, débit binaire en 2 passes jusqu'à un plafond de Ko, VP9 WebM en parallèle, poster WebP de l'image 1.
- Logo avec transparence pour le web : `-
…
[optionnel] Vous pouvez ajouter des informations supplémentaires ici, mais elles ne seront pas utilisées pour la génération de la sortie.