Vidéo de walkthrough PR
Créer une vidéo de walkthrough narrée pour une pull request. Cela est conçu comme un artefact interne, offrant les mêmes bénéfices qu'une vidéo loom créée par l'auteur de la pull request — parcourir les changements de code, expliquer ce qui a été fait et pourquoi, afin que quiconque regarde puisse comprendre le PR rapidement.
Entrée : Une URL de pull request GitHub (par exemple, https://github.com/tldraw/tldraw/pull/7924). Si seul un numéro de PR ou une autre description est fourni, supposer que la PR est sur le repository tldraw/tldraw.
Sortie : Une vidéo MP4 à 1280×720 (30 fps) avec narration audio, sous-titres jaune sur noir synchronisés avec Whisper, et diapositives intro/outro standardisées, sauvegardée dans out/pr-<number>-walkthrough.mp4.
Tous les fichiers intermédiaires (audio, manifest, scripts) vont dans tmp/pr-<number>/ relatif à ce répertoire de skill. Ce répertoire est gitignored. Seul le .mp4 final réside dans out/.
Exécuter les commandes qui référencent ./scripts ou ./video depuis ce répertoire de skill.
Philosophie
Ceci est un walkthrough du point de vue de l'auteur. L'objectif est le même que si l'auteur du PR s'asseoit avec quelqu'un et lui parcourt les changements — en montrant du code spécifique, en expliquant ce qui a changé et pourquoi, dans un ordre qui construit la compréhension. Le spectateur devrait repartir en comprenant à la fois ce que le code fait et comment penser aux changements.
Cela signifie :
- La narration pilote tout. Rédiger d'abord la narration du walkthrough comme une explication continue du PR. Ensuite déterminer ce qui devrait être à l'écran à chaque moment pour soutenir ce qui est dit.
- Montrer le code. Le visuel par défaut est un diff ou un fichier source. Les diapositives texte sont l'exception (intro, brèves transitions, outro), pas la règle. Quand la narration parle d'une fonction, le spectateur devrait regarder cette fonction.
- Parcourir les changements dans un ordre logique, pas nécessairement l'ordre des fichiers ou des commits — mais toujours ancrés à du code concret, pas des descriptions abstraites.
- Expliquer le « pourquoi », pas juste le « quoi ». Le code à l'écran montre ce qui a changé. La narration ajoute le raisonnement — pourquoi cette approche, quel problème elle résout, quels cas limites elle gère.
Flux de travail
Étape 1 : Comprendre le PR
Lire les commits, le diff et la description du PR. Comprendre l'arc narratif :
- Quel problème cela résout-il ?
- Quelle est l'approche ?
- Quels sont les mécanismes clés ?
gh pr view <number> --json title,body,commits
git log main..HEAD --oneline
git diff main..HEAD --stat
Ignorer les fichiers générés. Lors de la lecture du diff, ignorer les fichiers qui sont auto-générés plutôt qu'édités manuellement. Ceux-ci ajoutent du bruit sans informer le walkthrough. Les exemples courants dans ce repository :
**/api-report.md,**/api-report.api.md— rapports de surface API générés**/*.api.json,**/temp/*.api.json— sortie d'API extractorapps/docs/content/reference/**— docs de référence généréesyarn.lock,package-lock.json— fichiers de verrouillage**/CHANGELOG.md— changelogs auto-générés- Tout fichier que l'outillage du repository régénère (snapshots, dumps de schéma, assets bundlés)
Si vous n'êtes pas sûr qu'un fichier est généré, chercher un commentaire d'en-tête comme « DO NOT EDIT » ou vérifier si le repository a une commande de générateur qui le produit. Filtrer ceux-ci lors du choix des fichiers à mettre en avant dans les diapositives diff/code.
Étape 2 : Rédiger la narration
Rédiger la narration comme un texte continu, cassé en segments logiques. Chaque segment est un battement du walkthrough — un concept, un changement, ou un groupe de changements liés. Sauvegarder cela comme tmp/pr-<number>/SCRIPT.md.
La narration devrait se lire comme l'auteur expliquant le PR à un collègue : « Donc voici ce qu'on fait... Le problème fondamental était X... L'approche que j'ai prise était Y... Si vous regardez cette fonction ici... »
Structure : intro → contexte/problème → walkthrough du code → résumé. Voir Structure du script ci-dessous.
Si les commits sont simples et bien organisés (souvent sur une branche avec -clean dans son nom), vous pouvez suivre leurs messages de commit et descriptions pour guider votre narration. Sinon, examiner le code et créer votre propre narrative. Introduire les concepts dans un ordre qui s'appuie sur les précédents.
Éviter la redondance, surtout entre l'intro et le premier segment de contenu.
Étape 3 : Générer l'audio et les timestamps
Générer des clips audio par segment avec un appel TTS par segment. Cela évite complètement le chunking, l'alignement et le découpage — chaque segment est assez court pour un appel TTS fiable.
Écrire un fichier narration.json, puis exécuter l'outil CLI generate-audio.sh :
./scripts/generate-audio.sh narration.json tmp/pr-<number>/
Clé API : Sourced automatiquement à partir du fichier .env du repository (GEMINI_API_KEY).
Format JSON de narration
{
"style": "Read the following walkthrough narration in a calm, steady, professional tone. Speak at a measured pace as if the author of a pull request were walking a colleague through the code changes.",
"voice": "Iapetus",
"slides": [
"This pull request adds group-aware binding resolution to the arrow tool...",
"The core problem was that arrow bindings broke when the target shape...",
"If you look at the getBindingTarget method in ArrowBindingUtil.ts..."
]
}
style— Persona de voix et instructions de rythme. Gardez-le court et spécifique.voice— Nom de voix Gemini (par défaut :Iapetus).slides— Tableau de texte de narration, une entrée par segment.
Comment ça marche
- Pour chaque segment, le script construit un prompt : préambule de style + texte du segment.
- Un appel API à
gemini-2.5-pro-ttspar segment génère un clip WAV directement. - Chaque clip est validé (contrôle de santé de durée vs nombre de mots) et automatiquement réessayé si la sortie est mauvaise.
- Le silence en début/fin est coupé de chaque clip.
Sortie : Clips audio par segment (audio-00.wav, ...) et un fichier durations.json mappant chaque nom de fichier audio à sa durée en secondes.
Dépendances : ffmpeg / ffprobe. Aucun paquet Python requis au-delà de la stdlib.
N'utilisez PAS de balises de markup [pause long] ou [pause medium] dans le texte de narration — le modèle peut les lire à haute voix littéralement.
Étape 4 : Écrire le manifest
Le manifest est un fichier JSON qui décrit chaque diapositive de la vidéo. Il comble le fossé entre l'étape narration/audio et le rendu hyperframes.
Lire le durations.json de l'étape 3 pour obtenir la durée (en secondes) pour chaque clip audio. Puis écrire un manifest.json à côté des fichiers audio :
{
"pr": 7865,
"slides": [
{
"type": "intro",
"title": "Fix canvas-in-front z-index layering #7865",
"date": "February 14, 2026",
"audio": "audio-00.wav",
"durationInSeconds": 3.2
},
{
"type": "diff",
"filename": "packages/editor/editor.css",
"language": "css",
"diff": "@@ -12,7 +12,7 @@\n --tl-z-canvas: 100;\n- --tl-z-canvas-in-front: 600;\n+ --tl-z-canvas-in-front: 250;\n --tl-z-shapes: 300;",
"audio": "audio-01.wav",
"durationInSeconds": 25.8
},
{
"type": "code",
"filename": "packages/editor/src/lib/Editor.ts",
"language": "typescript",
"code": "function getZIndex() {\n return 250\n}",
"audio": "audio-02.wav",
"durationInSeconds": 13.5
},
{
"type": "text",
"title": "Summary",
"subtitle": "Moved canvas-in-front from z-index 600 to 250.",
"audio": "audio-07.wav",
"durationInSeconds": 7.4
},
{
"type": "list",
"title": "Key changes",
"items": ["Lowered z-index", "Updated tests", "Added migration"],
"audio": "audio-06.wav",
"durationInSeconds": 10.2
},
{
"type": "outro",
"durationInSeconds": 3
}
]
}
Types de diapositive
| Type | Champs requis | Description |
|---|---|---|
intro |
title, date, audio, durationInSeconds |
Logo + titre + date |
diff |
filename, language, diff, audio, durationInSeconds |
Diff unifié avec coloration syn. |
code |
filename, language, code, audio, durationInSeconds |
Code source avec coloration syn. |
text |
title, audio, durationInSeconds |
Titre + subtitle optionnel |
list |
title, items, audio, durationInSeconds |
Titre + items numérotés |
image |
src, audio, durationInSeconds |
Image pré-rendue (fallback) |
segment |
title, durationInSeconds |
Carte titre silencieuse |
outro |
durationInSeconds |
Logo seul, pas d'audio |
Scroll animé avec focus
Pour les longs diffs ou codes (plus de ~30 lignes), le renderer maintient la police à 16px lisible et utilise une fenêtre animée qui scrolle entre les points de focus. Ajouter un tableau focus aux diapositives diff ou code :
{
"type": "diff",
"filename": "packages/editor/src/lib/Editor.ts",
"language": "typescript",
"diff": "... diff de 60 lignes ...",
"focus": [
{ "line": 3, "at": 0 },
{ "line": 25, "at": 0.4 },
{ "line": 50, "at": 0.8 }
],
"audio": "audio-03.wav",
"durationInSeconds": 30
}
line— Le numéro de ligne (0-indexé dans les lignes de diff/code parsées) à centrer à l'écran.at— Quand arriver à cette position, comme une fraction de la durée de la diapositive (0 = début, 1 = fin).
La fenêtre se déplace en douceur entre les points de focus. Avant le premier point, elle s'arrête à la première position ; après le dernier, elle y reste.
Quand utiliser focus : Tout diapositive diff ou code avec plus de ~30 lignes. Sans focus, le contenu long démarre en haut et reste statique — le spectateur ne peut pas voir le bas. Avec focus, vous guidez l'œil du spectateur au code discuté à chaque moment.
Quand omettre focus : Les diffs courts (≤30 lignes) s'adaptent à l'écran à 16px et n'ont pas besoin de scroll.
Écrire les champs diff
Pour les diapositives diff, coller le diff unifié pour les hunks pertinents. C'est la sortie de git diff pour cette section du fichier — incluant le header de hunk @@ et les préfixes de ligne +/-/` `. Le renderer parse ces préfixes pour appliquer les arrière-plans vert/rouge et la coloration syntaxique.
Pour obtenir un diff pour un fichier spécifique :
git diff main..HEAD -- path/to/file.ts
Inclure uniquement les hunks pertinents, pas tout le diff du fichier. Supprimer les lignes d'en-tête diff --git et ---/+++ — commencer du header du hunk @@.
Pour les diapositives code, coller le code source pertinent (une fonction, une classe, une section). Pas de préfixes diff nécessaires.
Diapositives de titre de segment
Insérer une diapositive segment avant chaque segment de contenu pour l'introduire — sauf avant l'intro et les segments contexte/aperçu. Cela inclut les segments du walkthrough du code et du résumé/conclusion. Chaque diapositive segment est 3 secondes de silence avec le titre du segment centré à l'écran.
{
"type": "segment",
"title": "Zoom state machine",
"durationInSeconds": 3
}
Celles-ci fournissent des coupures visuelles claires entre les sections et donnent au spectateur un moment pour s'orienter avant chaque nouveau sujet.
Étiquettes de titre de segment sur les diapositives code/diff
Ajouter un champ title aux diapositives code et diff pour montrer une petite étiquette dans le coin supérieur gauche identifiant le segment dans lequel se trouve le spectateur. Utiliser le même titre que la diapositive segment précédente. Cela aide à orienter les spectateurs, surtout quand un segment s'étend sur plusieurs diapositives.
{
"type": "diff",
"title": "Zoom state machine",
"filename": "packages/editor/src/lib/ZoomTool.ts",
...
}
Étape 5 : Rendre la vidéo
Exécuter le script render.sh :
./video/render.sh \
tmp/pr-<number>/manifest.json \
out/pr-<number>-walkthrough.mp4
Le script :
- Copie les fichiers audio/image référencés dans
video/assets/. - Exécute la transcription whisper sur chaque fichier audio →
video/transcripts/audio-NN.json(idempotent — ne re-transcrit que si l'audio est plus récent que la transcription existante). - Exécute
build.mjs <manifest>pour générervideo/index.html— une composition hyperframes avec des clips programmés pour chaque diapositive, timeline GSAP pour les transitions et pans de focus de code, et des clips de légende jaune-sur-noir avec les heures de début/fin dérivées des transcripts whisper. - Lint la composition et rend des frames 1920×1080 via
npx hyperframes render. - Réduit à l'échelle à 1280×720 / 30fps et recompresse avec ffmpeg (CRF 26 + AAC 96k) pour le MP4 final petit mais net.
Dépendances : Node.js 22+, ffmpeg, Python 3 (utilisé par render.sh pour parser le manifest). hyperframes est invoqué via npx --yes, donc pas d'étape d'installation. Whisper s'exécute localement (modèle small.en, ~150MB au premier téléchargement).
Synchronisation des légendes via whisper
Les légendes apparaissent comme texte jaune sur une forme noire solide, ancrées au bas du cadre. Leurs heures de début/fin proviennent de transcripts whisper au niveau des mots groupés en chunks de 5–7 mots, cassant tôt sur des pauses naturelles (gaps >450ms = limites de phrases). Une implication : whisper transcrit les noms de marque/code phonétiquement — « tldraw » → « TL Draw », « OverlayUtil » → « overlay util ». C'est acceptable pour les légendes mais pourrait être normalisé plus tard via une table de substitution dans build.mjs.
Boutons de taille de fichier
Le rendu par défaut cible ~30–60 MB pour une vidéo de 8 minutes. Pour ajuster :
--crf <n>dans l'étape de rédimensionnement ffmpeg à l'intérieur derender.sh— 22 est proche du sans perte, 26 est le défaut, 30+ est beaucoup plus petit. CRF 28–30 est une bonne plage pour un résultat de qualité docs.- Le rendu 1080p hyperframes utilise
-q draft --crf 30pour maintenir le fichier intermédiaire petit (le rédimensionnement domine la taille finale de toute façon).
Organisation des fichiers
La sortie finale réside dans ce répertoire de skill. Tous les fichiers intermédiaires vont dans tmp/ (gitignored) :
pr-walkthrough/
├── SKILL.md # Ce fichier
├── scripts/ # Outils CLI (checked in)
│ └── generate-audio.sh # narration.json → WAVs par slide + durations.json
├── video/ # Projet Hyperframes (checked in)
│ ├── hyperframes.json # config hyperframes
│ ├── meta.json # project meta
│ ├── build.mjs # manifest.json → composition index.html
│ ├── render.sh # manifest.json → MP4 720p (pipeline complet)
│ ├── assets/ # Auto-rempli au moment du rendu (gitignored)
│ ├── transcripts/ # JSON word-level Whisper (gitignored, cached)
│ └── renders/ # Rendus 1080p intermédiaires (gitignored)
├── out/ # Sorties finales (gitignored)
│ └── pr-XXXX-walkthrough.mp4
└── tmp/ # Fichiers intermédiaires (gitignored)
└── pr-XXXX/
├── SCRIPT.md # Script de narration
├── narration.json # Entrée pour generate-audio.sh
├── durations.json # Nom fichier audio → durée en secondes
├── manifest.json # Entrée pour render.sh
└── audio-XX.wav # Clips audio par segment
Configuration API
- Clé API Gemini : Stockée comme
GEMINI_API_KEYdans le fichier.envà la racine du projet. Utilisée pour TTS et l'alignement audio. - Modèle TTS :
gemini-2.5-pro-tts - Voix TTS :
Iapetus(toujours)
Structure du script
Le walkthrough suit un arc narratif cohérent. Pas chaque section n'a besoin de son propre segment — combiner ou sauter des sections en fonction de la complexité du PR. L'objectif est 8-12 segments au total, avec la grande majorité montrant du code.
Intro (1 segment)
La carte intro : logo tldraw + titre du PR + date. La narration devrait être une seule phrase qui encadre ce que ce PR fait au niveau élevé. Ne pas entrer dans les détails à ce stade.
Type de diapositive manifest : intro.
Contexte (0-1 segments)
Brève orientation avant de plonger dans le code. Quelle était la situation avant ce PR ? Quel problème ou besoin a motivé le travail ? Gardez cela court — juste assez de cadrage pour que le walkthrough du code ait du sens.
- Être concret : « Arrow bindings broke when the target shape was inside a group » pas « There were issues with bindings »
- Nommer la zone du codebase affectée
Si le contexte peut être expliqué en montrant le premier morceau de code pertinent, sauter le segment contexte autonome et le plier dans le premier segment de code.
Type de diapositive manifest : text ou diff (si montrant le code problématique).
Walkthrough du code (6-10 segments)
La majeure partie de la vidéo. Parcourir les changements de code réels, en montrant des diffs et des fichiers spécifiques tout en expliquant ce qui a été fait et pourquoi.
Chaque segment devrait montrer du code. Utiliser les diapositives diff pour les changements et les diapositives code pour le code de référence inchangé.
Directives :
- Nommer les fichiers et les fonctions. Chaque segment narré devrait référencer au moins un fichier ou une fonction spécifique.
- Montrer le diff. Le visuel pour chaque segment devrait être le diff réel discuté. Utiliser
git diff main..HEAD -- path/to/filepour obtenir le diff, puis extraire les hunks pertinents. - Ordonner par compréhension, pas par fichier. Présenter les changements dans l'ordre qui construit la compréhension. Si un nouveau type est défini dans un fichier et consommé dans un autre, montrer la définition d'abord.
- Expliquer le « pourquoi », pas juste le « quoi ». Le diff montre ce qui a changé — la narration ajoute le raisonnement, les cas limites qu'il gère, les alternatives considérées.
- Ignorer le boilerplate, mais le mentionner. Ne pas consacrer un segment à chaque changement d'import ou export de type, mais le mentionner en passant : « There are also some type exports added in
index.ts— those are just re-exports of the new types we'll see next. » - Grouper les petits changements liés. Si trois fichiers ont tous reçu le même fix d'une ligne, un segment peut couvrir les trois. Mentionner chaque fichier par nom.
Résumé (1 segment)
Brièvement récapituler ce que le PR a accompli. C'est un court résumé — une phrase ou deux résumant le changement global, mentionnant les limitations connues ou le travail de suivi si pertinent.
Type de diapositive manifest : text.
Outro (1 segment, silencieux)
Le logo tldraw, 3 secondes de silence. Toujours inclure cela comme la diapositive finale.
Type de diapositive manifest : outro avec durationInSeconds: 3.
Conseils de rédaction de narration
- Être spécifique sur le code. Dire « In
BindingUtil.ts, theonAfterChangehandler now checks for group ancestors » — pas « The binding system was updated. » Nommer les fichiers et les fonctions pour que le spectateur puisse connecter la narration à ce qui est à l'écran. - Chaque segment = un changement ou un groupe de changements étroitement liés. Si vous ne pouvez pas pointer un diff spécifique pour le segment, c'est probablement trop abstrait.
- Rédiger comme l'auteur. Le ton devrait être explicatif et naturel — comme parcourir son travail avec quelqu'un. « So the main thing here is... » ou « The tricky part was... » sont corrects.
- Éviter la redondance entre l'intro et le premier segment de contenu.
- Mentionner les fichiers qui ne sont pas montrés. Si un PR touche 15 fichiers mais seuls 6 sont intéressants, brièvement reconnaître les autres : « The remaining changes are type exports and test fixtures. »
- Viser 5-7 minutes de narration totale.
Checklist
- [ ] Lire tous les commits de PR et comprendre le diff complet
- [ ] Rédiger la narration dans SCRIPT.md (8-12 segments)
- [ ] Générer l'audio par segment (voix Iapetus)
- [ ] Lire durations.json pour obtenir les durées par segment
- [ ] Écrire manifest.json avec les types de diapositives, diffs/code, et références audio
- [ ] Rendre la vidéo avec render.sh
- [ ] Vérifier la sortie finale : 1280×720 / 30 fps, audio synchronisé, légendes lisibles, outro présente