Communitygithub.com

demo-reel

Enregistrez une vidéo de démonstration fluide, image par image à 60 fps, de n'importe quelle application web à partir d'un court script — curseur scripté avec halo lumineux, ondulations de clic, légendes et un badge dans le coin, rendu sur une horloge virtuelle pour une fluidité parfaite, encodé en MP4 qui boucle dans PowerPoint, Keynote ou sur le web. À utiliser lorsque l'utilisateur souhaite une vidéo de démonstration, une visite guidée, un enregistrement d'écran, une présentation de fonctionnalité, une vidéo avant/après, un clip pour un diaporama ou une page d'atterrissage.

Qu'est-ce que demo-reel ?

demo-reel est un outil professionnel de création de vidéos de démonstration pour applications web. Il transforme un scénario scripté en une vidéo fluide à 60 ips, imitant parfaitement un enregistrement d'écran. Sa particularité est l'injection d'une horloge virtuelle dans le navigateur Chromium headless, ce qui garantit une fluidité parfaite même sur des machines lentes, contrairement aux enregistrements d'écran classiques. L'outil automatise le déplacement du curseur avec un halo lumineux, les clics avec ondulations, l'ajout de légendes et de badges, ainsi que les défilements, tout en pilotant l'application réelle. Il est idéal pour créer des démos produits, des tutoriels, des présentations avant/après ou des clips pour slides. Le résultat est un fichier MP4 H.264 prêt à être utilisé dans PowerPoint, Keynote ou sur le web. Il nécessite Node.js, ffmpeg et un navigateur Chromium, et son API simple permet de scénariser des actions comme naviguer, cliquer, taper du texte ou faire défiler, avec des contrôles précis sur les légendes et les transitions.

Compatible avec~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/hasuwini77/demo-reel/tree/main/skills/demo-reel

Demander à votre IA préférée

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

Aperçu

Depuis le README du skill

Documentation

Que fait demo-reel ?

Transformez une visite scriptée d'une application web en une vidéo qui ressemble à un enregistrement d'écran soigné : mouvement fluide à 60 ips, curseur visible avec un halo, ondulations de clic, légendes. L'outil pilote l'application réelle dans Chromium headless — les images sont le produit, pas une maquette.

Pourquoi la vidéo est fluide

Un enregistrement d'écran normal capture les images quand le navigateur se trouve à peindre ; sur une machine lente (ou avec WebGL logiciel), cela donne environ 10–15 ips avec des saccades. demo-reel injecte à la place une horloge virtuelle (scripts/inject.js) : requestAnimationFrame, performance.now et Date n'avancent que lorsque l'enregistreur le dit, et les animations CSS/Web sont synchronisées via document.getAnimations(). Chaque image est avancée exactement de 1/fps, capturée en écran, puis transmise à ffmpeg. Quel que soit le temps de rendu d'une image, le résultat final est parfaitement fluide.

Les minuteries (setTimeout/setInterval) sont délibérément laissées réelles — les simuler ferait boucler indéfiniment les chaînes de temporisation à délai zéro dans les chargeurs et planificateurs (c'est pourquoi la propre page.clock de Playwright bloque sur de nombreuses pages WebGL).

Prérequis

  • Node 18+, ffmpeg dans le PATH.
  • Une fois, dans le répertoire de cette compétence : npm install && npx playwright install chromium.

Flux de travail

  1. Servez l'application comme les utilisateurs la voient — idéalement une version de production (vite build && vite preview, next build && next start) avec des données locales ou simulées. Les serveurs de développement fonctionnent mais sont plus lents et peuvent afficher des superpositions de développement. N'enregistrez jamais de données réelles ou sensibles — utilisez des données de démonstration.
  2. Écrivez un scénario (*.scenario.mjs, voir l'API ci-dessous). Commencez par examples/tasks.scenario.mjs. Planifiez-le comme une histoire : 20–60 s, une idée par légende, pauses de 1–2 s après chaque résultat pour que les spectateurs puissent lire.
  3. Enregistrez : node scripts/record.mjs mon.scenario.mjs --out demo.mp4 Un clip de 60 s en 1080p prend environ 3–10 min (les pages WebGL lourdes sont les plus lentes). Lancez-le en arrière-plan.
  4. QA : scripts/sheet.sh demo.mp4 écrit une planche contact (une image toutes les 2,5 s). Regardez-la : les légendes correspondent à ce qui est à l'écran, pas de spinners de chargement, rien de caché par la légende, badge/curseur visible. Vérifiez la fluidité en extrayant quelques images consécutives — chacune doit légèrement différer pendant le mouvement.
  5. Livrez (voir « Utilisation de la vidéo »).

API du scénario

export default {
  size: "1920x1080",          // zone d'affichage = taille vidéo
  fps: 60,
  theme: { captionPosition: "bottom", haloFill: "rgba(250,204,21,.30)" }, // optionnel
  async run(d) {
    await d.open("http://localhost:4173/");     // navigation + stabilisation (non enregistré)
    d.badge("NOUVEAU", "#15803d");                  // badge dans le coin (null masque)
    d.caption("Le nouveau tableau de bord");             // pilule de légende (null masque)
    await d.hold(1500);                          // enregistre 1,5 s tel quel
    await d.click(d.page.getByRole("button", { name: "Filtres" }));
    d.caption("Les filtres vivent dans un seul panneau");     // définir les légendes APRÈS l'action
    await d.hold(1200);
    await d.type("Stockholm");                  // frappe à vitesse humaine
    await d.press("Enter");
    await d.scroll(600);                        // défilement fluide à la molette
    await d.step("partie optionnelle", async () => { /* les échecs sont journalisés, l'enregistrement continue */ });
  },
};
AppelFait
d.open(url, { settle })Navigue, laisse la page se stabiliser (non enregistré, 2500 ms par défaut).
d.hold(ms)Enregistre la page telle quelle.
d.settle(ms)Avance le temps sans enregistrer — ignore les états de chargement, les morceaux paresseux, les animations de mise en page que vous ne voulez pas montrer.
d.move(target, { ms }) / d.hoverGlisse le curseur (avec accélération) vers une cible.
d.click(target, { ms, button })Déplace, clique avec une ondulation. button: "right" pour les menus contextuels.
d.doubleClick(target)Double-clic.
d.type(text, { delay })Tape une touche à la fois (90 ms/touche par défaut).
d.press(key)Raccourci clavier, par ex. "Escape", "Control+K".
d.scroll(dy, { ms })Défilement fluide à la molette.
d.caption(text) / d.badge(text, color) / d.cursor(bool)État de la superposition ; survit aux navigations complètes de page.
d.step(name, fn)Étape nommée ; les erreurs sont journalisées, non fatales.
d.page, d.point(target), d.width, d.heightÉchappatoires.

Les cibles peuvent être un Locator Playwright, une chaîne de sélecteur CSS, { x, y }, ou une fonction asynchrone (page) => ({ x, y }).

Interfaces utilisateur rendues sur canvas (graphes, cartes, graphiques) n'ont pas d'élément DOM par élément — passez une fonction qui demande à la bibliothèque les coordonnées écran. Exemple pour Cytoscape.js :

const node = (label) => (page) => page.evaluate((label) => {
  const el = [...document.querySelectorAll("div")].find((e) => e._cyreg?.cy);
  const n = el._cyreg.cy.nodes().filter((n) => n.data("label") === label)[0];
  const r = el.getBoundingClientRect(), p = n.renderedPosition();
  return { x: r.left + p.x, y: r.top + p.y };
}, label);
await d.click(node("Server A"));

Écrire de bonnes démos

  • Les légendes disent la vérité. Ne revendiquez que ce que montrent les images. Si un bug que vous voulez montrer ne se reproduit pas dans la prise, changez la légende — ne la conservez pas.
  • Légende après l'action, pas avant — sinon la légende de l'écran suivant apparaît sur le précédent.
  • Montrez les résultats, pas les clics. Faites une pause de 1–2 s sur chaque résultat ; coupez le chargement avec d.settle.
  • Avant/après : enregistrez deux scénarios avec la même histoire et les mêmes étapes, badge AVANT/APRÈS (rouge/vert), et conservez-les dans des fichiers séparés plus une concaténation optionnelle.
  • Moments héroïques (une page d'atterrissage, une animation) : accordez-leur 5–10 s — les spectateurs ont besoin d'un moment pour les assimiler.
  • Gardez le curseur calme : 600–900 ms par déplacement, éloignez-le du contenu pendant les pauses.
  • Les minuteries réelles continuent de tourner pendant le rendu des images (~50–300 ms de temps réel par image), donc les UI pilotées par minuteur — toasts, recherche différée, infobulles à disparition automatique — peuvent disparaître plus tôt dans la vidéo que dans la vie réelle. Déclenchez-les juste avant de vouloir les voir à l'écran, ou faites une pause plus courte.

Utilisation de la vidéo

  • PowerPoint : Insertion → Vidéo → Ce périphérique. Onglet Lecture : Démarrer : Automatiquement et Lire en continu jusqu'à l'arrêt. Le MP4 H.264 yuv420p (ce que demo-reel écrit) fonctionne sous Windows et macOS.
  • Keynote : glissez-déposez ; Film → Répéter : en boucle.
  • Web : <video src="demo.mp4" autoplay loop muted playsinline></video>.
  • GIF (README, chat) : ffmpeg -i demo.mp4 -vf "fps=20,scale=960:-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse" demo.gif
  • Concaténer des clips de même taille/fps : printf "file 'a.mp4'\nfile 'b.mp4'\n" > l.txt && ffmpeg -f concat -safe 0 -i l.txt -c copy both.mp4

Dépannage

SymptômeCorrectif
Images blanches / « Chargement... »settle plus long dans d.open, ou await d.settle(1500) après un clic de navigation.
Cible non visibleElle est hors écran ou dans un menu fermé — ouvrez/défilez d'abord ; pour les éléments canvas, utilisez une fonction de point.
WebGL/3D rendu noirConservez les indicateurs de lancement par défaut (--use-angle=swiftshader) ; c'est lent (~0,25 s/image) mais correct.
Vidéo trop courte/longue par rapport au planLes durées sont exactes : somme des hold + déplacements. Le temps settle n'est jamais enregistré.
Couleurs délavées dans PowerPointNe ré-encodez pas sans -pix_fmt yuv420p ; la sortie de demo-reel est déjà correcte.
Texte légèrement flou--png capture des images sans perte (plus lent, plus volumineux).

Skills associés