video-sync
Combine shots.json + la vidéo source pour produire une vidéo : d’un côté l’image, de l’autre les informations du plan en cours, qui se mettent à jour à chaque changement de plan. La liste des plans défile automatiquement et le plan actuel est surligné.
Le layout ne dépend que du ratio de la vidéo source :
| Vidéo source | Layout | Méthode de composition |
|---|---|---|
| Paysage (largeur > hauteur), carré | Image en haut, informations en bas | vstack |
| Portrait (hauteur > largeur) | Image à gauche, informations à droite | hstack |
Dans les deux cas, l’image est redimensionnée sans recadrage ni étirement ; le panneau occupe l’espace restant.
Seulement trois images sont générées ; le mouvement est confié à ffmpeg. Les seules choses qui changent dans le panneau sont : l’endroit où on défile et quelle ligne est surlignée. Donc on calcule une fois les positions des lignes, on découpe une image de fond, et on découpe deux longues images étendues de la liste des plans (une assombrie, une entièrement claire). Le défilement = on découpe une fenêtre dans l’image longue sombre, le surlignage = on découpe une ligne dans l’image longue claire et on la superpose. Les positions sont pilotées par une petite expression envoyée par sendcmd à chaque point de coupe. Pour une vidéo de 53 plans, le nombre de captures d’écran passe de 53 à 3 (12 secondes).
Le plan actif est toujours en deuxième ligne : les plans 1 et 2 ne défilent pas (le surlignage descend d’une ligne), à partir du plan 3, à chaque coupe, on remonte d’une ligne. Le mouvement n’a lieu qu’aux points de coupe — le défilement se fait avec --ease (0,45 s par défaut) puis s’arrête. Pour un plan de 16 secondes, la liste reste immobile pendant toute sa durée, évitant de fatiguer les yeux.
La hauteur des lignes est déterminée par le contenu : une description longue donne une ligne haute ; la barre de surlignage utilise des hauteurs de ligne (la crop ne peut pas être modifiée en cours d’exécution).
{baseDir} = le répertoire contenant ce fichier. Le script {baseDir}/scripts/video-sync.mjs ne nécessite aucune dépendance, s’exécute directement avec node.
Limites (ce que le skill ne fait pas) : ne fait pas l’analyse de découpage (c’est le rôle de video-shots, ce skill consomme son shots.json), ne coupe pas, ne fait pas de transitions, n’ajoute pas de musique, n’incruste pas de sous-titres dans l’image, ne fait pas de rendu image par image (les effets dynamiques sont réalisés par ffmpeg en découpant des fenêtres temporelles).
Étape 0 — Avoir d’abord les données de découpage
Il faut un fichier shots.json (produit par video-shots) et la vidéo source correspondante. Avoir un dossier frames/ (une image clé par plan) est un plus — la liste des plans utilisera la première image comme vignette, sinon une case vide sera affichée.
Le shots.json doit correspondre à la vidéo : si meta.durationSeconds ne correspond pas à la durée de la vidéo, ne pas composer — retour d’abord corriger le découpage.
Étape 1 — Vérifier la géométrie avant de traiter la vidéo
node {baseDir}/scripts/video-sync.mjs plan shots.json --video <fichier_video>
Calcule uniquement (sans composer) et donne le résultat en quelques secondes : layout, zone vidéo, zone panneau, dimensions de sortie. Vérifiez d’abord que ces trois nombres sont corrects avant de continuer — composer une vidéo de 3 minutes prend 1 à 2 minutes, ce serait dommage de recommencer après une erreur.
Si le résultat ne vous convient pas, ajustez : --panel <ratio> (pour un paysage, c’est la hauteur du panneau divisée par la hauteur de la vidéo, défaut 0,8 ; pour un portrait, c’est la largeur du panneau divisée par la largeur de la vidéo, défaut 1,6), --width / --height (limite supérieure de la zone vidéo, défaut 1920 / 1080).
Si la vidéo source est trop petite, agrandissez la zone vidéo : Le panneau est calculé par rapport à la zone vidéo. Pour une vidéo 640×360, le panneau ne ferait que 640×288, les quatre colonnes seraient illisibles. --scale 2 agrandit la zone vidéo à 1280×720 et le panneau à 1280×576, en conservant les proportions. La valeur par défaut est 1 (réduction seulement, pas d’agrandissement). plan / panels / compose / export doivent toutes utiliser le même --scale.
Étape 2 — Rendre le panneau
node {baseDir}/scripts/video-sync.mjs panels shots.json --video <fichier_video> \
--frames frames --lang fr|en [--out panels] [--chrome <chemin_chrome>]
Produit static.png (fond), list-dim.png / list-lit.png (les deux longues images étendues de la liste, une assombrie, une claire), layout.json (les positions de chaque ligne, utilisées lors de la composition pour calculer le défilement), et un fichier panels/panel.html — ouvrez-le dans un navigateur pour prévisualiser le panneau. Changez #S07 pour voir l’effet d’un autre plan (l’animation de défilement dans l’aperçu correspond à celle de l’export).
Pour modifier la mise en page, modifiez {baseDir}/scripts/panel.css et relancez panels — la nouvelle mise en page sera prise en compte sans toucher au script ni repenser la logique de capture d’écran. Les conventions de style sont décrites dans {baseDir}/references/panel-style.md.
Étape 3 — Composer
node {baseDir}/scripts/video-sync.mjs compose shots.json --video <fichier_video> \
[--panels panels] [-o out.mp4] [--crf 20] [--ease 0.45]
La piste audio est copiée telle quelle depuis la vidéo source (si la vidéo est muette, aucune piste audio n’est ajoutée). Une fois la composition terminée, stderr affiche les dimensions, la durée et le layout de la sortie. Vérifiez visuellement.
Les deux étapes peuvent être combinées :
node {baseDir}/scripts/video-sync.mjs export shots.json --video <fichier_video> -o out.mp4 --frames frames
Étape 4 — Vérification par captures d’écran ⛔ Ne pas sauter
Ne vous fiez pas seulement à l’absence d’erreur de composition. Prélevez au moins trois images pour vérifier que les informations correspondent bien à l’image :
for t in 5 30 60; do ffmpeg -v error -y -ss $t -i out.mp4 -frames:v 1 -q:v 3 /tmp/check-$t.jpg; done
Vérifiez trois choses : à quel plan appartient cette image, le numéro de plan sur le panneau correspond-il, la ligne surlignée suit-elle le plan ? Si ce n’est pas le cas, le shots.json ne correspond pas à la vidéo, ou le paramètre --video est erroné.
Rapportez en une phrase : chemin de sortie, dimensions, durée, layout, nombre de plans, et éventuellement quelles images du panneau n’ont pas été rendues.
Limites
- Le défilement n’a lieu qu’aux points de coupe : le défilement dure
--easesecondes puis s’arrête. Le panneau est fixe entre les coupes. Si vous voulez un défilement continu et lent, vous pouvez modifier lemotionPlansegmenté, mais pour les plans longs, cela fatiguera les yeux. - Nécessite un navigateur sans tête (Chrome / Chromium / Edge). C’est le prix à payer pour pouvoir modifier librement la mise en page : si on utilisait
drawtextde ffmpeg pour le panneau, il faudrait gérer les retours à la ligne en chinois, les vignettes et le surlignage soi-même, et modifier une mise en page nécessiterait de retoucher les filtres. - Le vocabulaire (échelle de plan / type / mouvement / transition) est intégré dans ce skill, pas importé depuis un autre module — le skill doit pouvoir être copié dans son intégralité. Si
video-shotsajoute un nouveau terme, il faudra l’ajouter ici aussi, sinon il sera affiché comme une clé d’énumération. - Le rendu du panneau utilise toujours trois captures d’écran, indépendamment du nombre de plans : 53 plans ≈ 12 secondes. La composition dépend de la durée de la vidéo : 203 secondes ≈ 75 secondes.
Auto-test
node {baseDir}/scripts/selftest.mjs
122 assertions, sans utiliser ffmpeg ni navigateur : géométrie (paysage/carré/portrait, dimensions paires, limites, réglages), contrat de données de la page du panneau (vocabulaire fourni, rôles rythmiques, vignettes présentes ou absentes, échappement), commandes d’animation (chacune doit être courte — une expression trop longue fait échouer la configuration de ffmpeg), paramètres ffmpeg. Lancez ce test après avoir modifié le script.