Communitygithub.com

pedrojaques99/site-reels

Enregistre les meilleures scènes d'un site ou d'une appli web (héros WebGL, 3D, scroll) en MP4 1080p pour les decks, les téléverse dans Figma Slides et construit des showreels de marque ou de produit dans Remotion.

Qu'est-ce que site-reels ?

Ce skill enregistre des séquences scriptées d'un site web local en MP4 1920×1080 à 30 ips (codec x264, < 100 Mo) via `puppeteer-core` et `puppeteer-screen-recorder`, sans capture d'écran, mais en utilisant le screencast du protocole CDP combiné à ffmpeg. Il peut également générer des séquences verrouillées à 60 ips avec `snap.mjs` et l'horloge virtuelle timeweb, garantissant que chaque image est unique et sans tremblement. Les MP4 sont téléversés dans Figma Slides (via les compétences `figma-use` et `figma-use-slides`), en créant des diapositives masquées contenant des cadres où l'utilisateur glisse-dépose la vidéo, car l'API Figma ne supporte pas l'upload vidéo direct. Enfin, le skill assemble des showreels de marque ou de produit dans Remotion, en composant des plaques propres avec des effets (brouillard, grain, Ken Burns, transitions) et des séquences filmées réelles (Playwright + CDP), avec des recommandations précises sur le rythme, la durée et le style pour un résultat professionnel et percutant.

Compatible avec~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/pedrojaques99/visant-skills-public/tree/HEAD/site-reels

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Site Reels: site → MP4 → deck

Prérequis

Nécessaire pourQuoiVérification
toutWindows, macOS ou Linux avec un GPU et un bureau visible (Chrome s'exécute en mode headful)n/a
toutNode.js 18+node -v
toutffmpeg + ffprobe dans le PATH (ou FFMPEG=<chemin complet>)ffmpeg -version
toutChrome, Chromium ou Edge (détection automatique, voir ci-dessous)node -e "import('./scripts/find-chrome.mjs').then(m=>console.log(m.findChrome()))"
record.mjspuppeteer-core + puppeteer-screen-recorder (npm, dans le projet)npm ls puppeteer-core puppeteer-screen-recorder
snap.mjspuppeteer-core + timesnap (apporte timeweb)npm ls timesnap
showreelProjet Remotionnpx remotion --version
audits (optionnel)ImageMagickmagick -version
Téléversement FigmaFigma MCP (use_figma, upload_assets) + compétences figma-use, figma-use-slidesListe 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.png et regardez.
  • 1 Mo pour 19s = scène vide. > 100 Mo = ne rentre pas dans Figma (le paramètre crf de 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-FPS avec 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

  1. 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: false intentionnel : le mode headless tombe sur SwiftShader et le WebGL bégaye.
  2. 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.
  3. Un sélecteur invalide tue toute la règle : \[ dans un template literal JS devient [ et .z-[60] invalide header, nav, .z-[60]. Une règle par sélecteur risqué.
  4. 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).
  5. 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.
  6. Le screencast délivre ~32 images uniques/s → enregistrez à 30 ips (60 donne des images répétées irrégulières). Le trim par défaut de 0,3s supprime la première image mise en cache.
  7. 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.mjs mesure (mpdecimate=hi=64:lo=24:frac=0.05) et réenregistre jusqu'à 2x en dessous de MIN_FPS (24). Pour les plaques WebGL/3D, préférez snap.mjs.
  8. Bannière de cookies : acceptez-la dans prepare (clic générique sur "Accepter/Aceitar/OK") avant l'enregistrement.
  9. 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 à un input synthé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énement playing → video.play().catch() lance une exception et la page devient blanche.
  • preserveDrawingBuffer: true forcé sur getContext (désactiver avec preserve: 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 MAE entre 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 :

  1. Couverture de chaque vidéo : ffmpeg -ss <t> -i x.mp4 -frames:v 1 -q:v 2 _capas/x.jpg.
  2. use_figma (compétences figma-use + figma-use-slides) : createSlideRow(idx) avant la dernière diapositive, row.name = 'WIP · Motion (caché)', un createSlide(idx, i) par vidéo avec isSkippedSlide = true, fond noir, un rectangle 1920×1080 nommé VIDEO · x.mp4 et l'instruction dans speakerNotes.
  3. upload_assets avec les nodeIds des rectangles → requête POST multipart des couvertures.
  4. 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 axe wght variable, palette en bandes, typographie, graphiques superposés, 3D avec titre en mix-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 avec magick compare -metric RMSE entre 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, Series avec 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 et block: ['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 superposition mix-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 via use_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 globalThis défini par son propre fichier d'entrée importé avant le Root.
  • Piste continue à travers les coupures : trimBefore sur la piste sonore pour qu'elle continue là où le segment précédent s'est arrêté.
  • Loudness : rendu, mesure et normalisation avec loudnorm en 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.mjs avec KEEP_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.

Skills associés