Motion Graphics
Générez une vidéo en écrivant une page web que le moteur de rendu parcourt image par image. Les deux voies ci-dessous font la même chose ; la différence réside dans ce que vous écrivez.
- HyperFrames — un fichier HTML, du CSS pour l'apparence, une timeline GSAP en pause pour
l'animation.
node scripts/hf.mjsexécute la version épinglée et l'installe une fois depuis un fichier de verrouillage validé. Apache 2.0. - Remotion — des composants React.
useCurrentFrame()renvoie la trame, vous faites le calcul. Mature, typé, et sous licence gratuite uniquement jusqu'à trois personnes.
Chargez la référence correspondante avant d'écrire le code de composition. Les règles du framework ne sont pas devinables et la plupart des échecs sont silencieux : un fichier HyperFrames qui n'enregistre jamais sa timeline produit une image fixe sans message d'erreur.
Le chemin facile
L'utilisateur décrit ce qu'il veut en termes simples. Il n'écrit jamais de brief, de cahier des charges ou de feuille de rythme — Claude le compile.
/motion un logo animé percutant de 6 secondes pour Inkwell
Voilà toute l'entrée. En dessous :
- Compile la demande en
brief.json— durée, taille, images par seconde, rythme avec les timings, copie textuelle, easings, palette, et une listeassumptions. Chaque manque est comblé par une valeur par défaut provenant du tableau dansreferences/brief.md; rien n'est laissé indéfini et rien n'est remis en question. - Affiche le résumé compact (un tableau, ~12 lignes) et construit dans le même tour de parole. Pas de validation intermédiaire.
- Construit à partir du modèle, valide, rend, examine les images.
L'itération se fait ensuite par modification d'un champ, pas par reconstruction. « Rends-le
plus percutant » modifie feel.ease et deux durées ; le code ne bouge pas sauf si kind ou
motion change.
Le brief est l'artefact qui rend tout cela économique. Il vit sur le disque à côté de la composition, donc une révision est un diff, et un nouveau rendu est une seule commande.
Choix de la voie
| Signal | Voie |
|---|---|
| Nouvelle construction, aucun framework nommé | HyperFrames |
| HTML/CSS/GSAP/Lottie existant à réutiliser | HyperFrames |
Pas de node_modules, pas d'étape de build souhaitée | HyperFrames |
| Équipe de plus de trois personnes | HyperFrames (licence) |
| L'utilisateur nomme Remotion, ou le dépôt est React/TS | Remotion |
| A besoin d'un système de design, d'une bibliothèque de graphiques ou de composants complexes | Remotion |
| Portage d'un projet Remotion existant | Remotion (le portage coûte plus qu'il ne rapporte) |
Lorsque l'utilisateur nomme les deux, construisez en HyperFrames et dites en une ligne que le portage Remotion est disponible sur demande. Deux constructions complètes de la même pièce est du travail inutile sauf si une comparaison a été demandée.
La boucle
1. Compiler le brief
Transformez la demande en brief.json avant d'écrire du code. Le schéma, la
grammaire kind/motion, le tableau des valeurs par défaut et les leviers d'itération se trouvent dans
references/brief.md.
node scripts/brief.mjs brief.json # valider + tableau récapitulatif
node scripts/brief.mjs brief.json --json # résultat machine
Affichez le résumé à l'utilisateur, corrigez toute ERROR, puis continuez dans le même
tour de parole. Les avertissements sont des décisions subjectives — un avertissement que vous
acceptez va dans assumptions.
Le brief porte également le score. Choisissez audio.bpm pour que la grille de beats divise
les coupes — à bpm, un beat dure 60 / bpm secondes et chaque beats[].start doit
se trouver sur l'un d'eux. 120 BPM donne un beat toutes les 0,5 s et convient à la plupart des
pièces courtes. Si brief.mjs signale off_beat_grid, déplacez la coupe ou changez le tempo ; ne
l'ignorez pas, car c'est la différence entre une pièce qui tombe juste et une autre qui donne une
sensation étrange et subtile.
Terminé quand brief.mjs se termine avec le code 0, chaque beat a une fenêtre de temps, un type, un
mouvement et une copie textuelle, et aucune coupe n'est hors de la grille de beats.
2. Initialiser
Partez des modèles de cette compétence plutôt que d'un fichier vierge — ils sont corrects par rapport aux deux contrats et évitent les deux étapes sujettes aux échecs (enregistrement de timeline, séquencement temporel).
# HyperFrames — HTML simple. Le moteur épinglé s'installe une fois, ce qui nécessite un réseau
cp -R assets/hyperframes/. ./my-video/
node scripts/hf.mjs lint ./my-video
# Remotion — projet React, nécessite ses dépendances épinglées une fois
cp -R assets/remotion/. ./my-video/
cd my-video && npm ci
# PowerShell
Copy-Item -Recurse -Force assets\hyperframes\* .\my-video\
Copy-Item -Recurse -Force assets\remotion\* .\my-video\
Terminé quand node scripts/hf.mjs lint réussit, ou npx remotion compositions src/index.tsx liste l'identifiant de la composition.
3. Construire les beats
Un fichier par beat. Gardez la timeline plate — des temps absolus dans HyperFrames,
des trames <Sequence> dans Remotion. Mettez à jour la feuille de beats lorsque la construction
modifie le timing, puis revérifiez la durée totale.
Nommez chaque scène d'après son beat, car c'est ainsi que les vérifications la trouvent :
data-brief-beat="<id>" sur l'hôte HyperFrames, <Sequence name="<id>"> dans
Remotion. Si une séquence Remotion calcule son timing, placez également
data-brief-beat="<id>" sur un élément à l'intérieur, afin que le timing puisse être mesuré.
Terminé quand chaque beat de la feuille existe dans le code, la durée de la composition
correspond à la feuille, et il n'y a pas de TODO, de maquettes ou de copie de remplacement.
4. Vérifier le mouvement
Les images fixes mentent sur le timing. Examinez les images, puis examinez le rendu.
node scripts/check-contract.mjs --dir ./my-video --preflight # statique, sans installation
node scripts/check-contract.mjs --dir ./my-video --runtime # inspecte aussi dans un navigateur
node scripts/hf.mjs check ./my-video # mise en page, mouvement, contraste
node scripts/hf.mjs snapshot ./my-video --review # images des beats et des coutures en PNG
node scripts/hf.mjs keyframes ./my-video --selector "#card" --shot motion.png
npx remotion still src/index.tsx LaunchCard out/f.png --frame=45
npx remotion studio src/index.tsx # regardez le à 1x
check-contract.mjs détecte les échecs silencieux sans rien installer : une timeline
non enregistrée, une clé window.__timelines non concordante, une timeline non mise en pause,
une durée manquante, Math.random() dans une composition, un interpolate non limité.
C'est une vérification préliminaire, pas un remplacement pour les vrais linters.
Lorsque brief.json se trouve dans le projet (ou que --brief en pointe un), il
compare également la construction avec le brief. Chaque exigence se retrouve verified (vérifiée), failed (échouée)
ou unverified (non vérifiée). unverified n'est pas un succès, et bloque le rendu.
| Code | Statut | Signification |
|---|---|---|
brief_format_mismatch | failed | La largeur, la hauteur, les fps ou la longueur diffèrent de format |
brief_scene_count, brief_scene_unmapped | failed | Un beat n'a pas de scène déclarant son id, ou une scène n'a pas de beat. La position n'est pas l'identité |
brief_timing_mismatch | failed | Une scène ne commence ou ne dure pas ce que dit le brief, à une image près |
brief_copy_missing | failed | La ligne n'est pas dans la scène. Les commentaires et les variables inutilisées ne comptent pas |
brief_copy_wrong_scene | failed | La ligne se trouve uniquement dans une autre scène |
copy_not_visible | failed | Runtime : dans le DOM mais caché, transparent, hors écran, coupé, flouté ou recouvert |
copy_hold_short | failed | Runtime : lisible pendant moins de beats[].hold |
first_frame_empty | failed | Runtime : visibleAtStart est défini et rien du beat n'est affiché |
first_frame_unverified | unverified | Runtime : seul un graphisme est sur la première image, ou le beat n'a pas de copie. Regardez l'image |
brief_timing_unverified, brief_format_unverified | unverified | La source le calcule. Le runtime peut le régler |
brief_copy_dynamic, brief_copy_unverified | unverified | Savoir si elle est affichée est une question de runtime. La copie Remotion est toujours celle-ci jusqu'au runtime |
La correspondance ignore la casse et les espaces et replie les guillemets courbes. Les autres signes de ponctuation sont
significatifs, et "12,480" correspond à un compteur qui se stabilise sur 12480. Lorsque
les exigences restent non vérifiées, dites lesquelles, et essayez de les rendre mesurables avant
de les accepter. Accepter une signifie la nommer, avec
--allow-unverified "beat.lines.copy[0].visible", et le signaler à l'utilisateur.
N'acceptez jamais un modèle générique, et les échecs ne peuvent pas être acceptés du tout. Lisez
references/validation.md pour les règles et limites exactes.
Corrigez la dérive dans le code, ou modifiez le brief intentionnellement et relancez brief.mjs —
ne laissez jamais les deux en désaccord.
Examiner les images — tous les contrôles peuvent réussir sur un film défectueux
Un contrat réussi indique que le timing et la copie correspondent, et que le texte est lisible. Il ne dit rien sur la palette, les easings, les transitions, ou si le film fonctionne. Les vérifications de lint, de mise en page, de mouvement, de contraste et de qualité ont toutes réussi sur le propre modèle de cette compétence alors qu'il passait au noir sur chaque beat et quittait son compteur à l'instant où le nombre atterrissait. Seule l'examen des images l'a détecté. Capturez-les :
# HyperFrames : images des beats, une bande de 5 images autour de chaque coupe, et une feuille téléphone
node scripts/motion-render.mjs --brief ./my-video/brief.json --snapshot --no-render
# Remotion : pour chaque coupe c (en images), prenez c-6, c et c+6, plus le milieu de chaque beat
# (le modèle coupe à 75, 210 et 270)
node scripts/motion-render.mjs --dir ./my-video --no-render \
--stills 37,69,75,81,142,204,210,216,240,264,270,276,315
Lisez ensuite les images et répondez à chaque question. Corrigez le code, recapturez, regardez à nouveau :
- La coupe est-elle noire ? Plus d'une image vide autour d'une coupe signifie que la sortie
se termine trop tôt et que le beat suivant s'estompe à partir de rien. Déplacez la sortie pour qu'elle
se termine sur la coupe (
start = durée du beat − durée de sortie). - L'effet final tient-il ? Le dernier nombre d'un compteur, ou le dernier mot d'une ligne,
reste immobile à l'écran pendant au moins un beat (
60 / bpms) avant de partir. Le quitter dès qu'il atterrit signifie qu'il n'est jamais lu. - Est-ce lisible sur un téléphone ? Vérifiez
review-phone.jpg, ou imaginez l'image à 360 px de large. En 1080p, les étiquettes et les accroches en dessous d'environ 40 px échouent généralement. - Est-ce la police du brief ? Un substitut comme Segoe UI ou Arial signifie que la police n'a jamais
été chargée, et une autre machine rendra un film différent.
--runtimeprévient avecfont_fallback, et le rapport enregistre les polices réellement dessinées. Remotion a besoin de@fontsource/*(le choix du modèle) ou de@remotion/fontspour un fichier local. - Est-ce la transition du brief ?
feel.transitionse trouve dans les images autour de chaque coupe, pas un fondu au noir.
Terminé quand vous avez lu les images des beats et chaque bande de couture, répondu à chaque question ci-dessus, et que chaque ligne de la liste sous « Checklist de livraison » est valide.
5. Rendu
node scripts/motion-render.mjs --brief ./my-video/brief.json
node scripts/motion-render.mjs --brief ./my-video/brief.json --batch rows.json --no-audio
Le wrapper lit la voie, le format, les fps et le slug depuis le brief, et s'arrête avant que
du temps de rendu ne soit dépensé si le brief, les vérifications statiques, les outils, les ressources ou la vérification
d'exécution le disent. Il se termine avec le code 1 en cas d'échec et 3 en cas d'exigences non vérifiées.
--no-checks ignore la validation et l'enregistre dans le rapport. Ensuite, il linte, rend,
mesure le fichier avec ffprobe, et écrit <out>.report.json.
Lorsque audio.policy est generated, sur l'une ou l'autre voie, il synthétise le fond sonore avec
music.mjs, rend l'image vidéo uniquement, et multiplexe les deux avec un seul appel ffmpeg
— ainsi la composition ne dépend jamais du mixeur audio d'un moteur. Il mesure ensuite
que le fichier a un flux audio et qu'il n'est pas silencieux.
Pour la transparence : --format webm ou --format mov — le H.264 ne peut pas transporter l'alpha.
--no-audio ignore le fond sonore et donne un fichier sans flux audio. GIF ne peut pas transporter
d'audio, donc un brief avec un fond sonore est refusé pour GIF, sauf si vous passez --no-audio.
Avec --batch, un brief plus un fichier de lignes donne N vidéos : HyperFrames rend
les lignes nativement, Remotion obtient un fichier de props par ligne. Le batch est vidéo uniquement, et un brief
avec un fond sonore est refusé sauf si vous passez --no-audio.
Terminé quand le fichier existe, le rapport indique rendered, sa liste notVerified est
vide ou vous avez informé l'utilisateur de son contenu, et ses premières et dernières images sont
intentionnelles.
Le contrat qui échoue silencieusement
HyperFrames — quatre choses, toutes obligatoires :
- L'élément racine porte
data-composition-id,data-width,data-height,data-duration,data-start="0". - Chaque élément temporel porte
id+data-start+data-duration. - La timeline GSAP est construite avec
{ paused: true }. window.__timelines[<data-composition-id>] = tl— même clé. Après avoir ajouté les tweens. Manquez cela et vous obtenez une image fixe, sans erreur.
Remotion — trois :
registerRoot()dans le fichier d'entrée,<Composition>dans le composant racine.interpolate(..., { extrapolateLeft: "clamp", extrapolateRight: "clamp" })— bloquez les deux extrémités, chaque fois.- Pas de
Math.random(), pas deDate.now()dans une composition. Utilisezrandom(seed).
Les quatre règles qui décident si le rendu a l'air bon marché
Ce sont celles qui posent le plus souvent problème. Ensemble complet avec code dans
references/motion-craft.md.
- Ne bougez que ce qui est actif. Les éléments fixes restent immobiles. Une échelle de « respiration » au repos donne l'impression d'être générée par machine.
- Chevauchez, ne mettez pas en file d'attente. Décalage d'environ 15–30 % de la durée de l'élément. La chose suivante commence pendant que la précédente se stabilise encore.
- Pas de rebond sur le texte. Les dépassements sont pour les objets avec de la masse — cartes, volets,
personnages. Le texte, les nombres et les étiquettes d'interface utilisateur utilisent
power3.out/expo.outet ne dépassent jamais. - Une idée par mouvement.
y + opacitysignifie « arrivée ». Ajoutez une rotation et une échelle, et le mot est illisible en vol.
Aussi : une transition est la sortie, pas un fondu au noir suivi d'un fondu en entrée. Cela rend une coupe brutale avec un affaissement au milieu.
Checklist de livraison
-
brief.jsonvalide : les timings s'emboîtent, la première image est intentionnelle, la tenue finale ≥ 2 s. -
check-contract.mjs --runtimeindiqueverified, ou les identifiants non vérifiés sont acceptés par nom et signalés à l'utilisateur. - La copie, les nombres, les identifiants et les URL sont tels qu'écrits — pas de lorem, pas de placeholder.
- Timeline enregistrée sous l'identifiant de composition (HyperFrames), ou composition
listée par
npx remotion compositions(Remotion). - Lint/check propre.
-
<out>.report.jsonindiquerendered, avec la taille, les fps, la durée et l'audio attendus. - Images inspectées : chaque beat, chaque couture, largeur téléphone — pas de noir à une coupe,
…
[fin de la traduction]