Vidéo produit avec Remotion
Une démo produit sans visage (format 3 de short-video-formats) construite en code. La voix pilote le timing : chaque ligne de script est prononcée, les prises sont placées bout à bout, et les scènes et sous-titres de la vidéo suivent ces timestamps. L'interface utilisateur est reconstruite en React à partir des styles du produit, ce qui la rend nette à n'importe quel zoom et se re-rend quand le produit ou le script change. Tout sauf le MP4 rendu vit dans git, donc toute ancienne version est à un checkout et un rendu d'intervalle.
Entrées : Node 22, ffmpeg, un TTS (edge-tts pour les brouillons ; une voix sous licence avant publication), les tokens CSS
du produit et son balisage (une page d'atterrissage avec démo produit est souvent la meilleure source), et le script.
Workflow
1. Configuration
npx create-video@latest --blank reel && cd reel && npm i @remotion/google-fonts
Gardez un seul fichier theme.ts avec le cadre (1080×1920, 30 ips), les bandes de mise en page et les tokens de couleur du produit copiés
depuis son CSS. Nos bandes : bande titre centrée à y=118, bande UI blanche de y=220 sur 1250 px (65 % de la
hauteur, pleine largeur), sous-titres centrés à y=1640.
2. Écrire le script en tant que données
script.json, une entrée par ligne de sous-titre ; *étoiles* marquent les mots mis en évidence :
{ "voice": { "name": "fr-FR-DeniseNeural", "rate": "+10%" }, "gap": 0.16,
"lines": [
{ "scene": "wall", "text": "J'ai arrêté de *soliciter* Claude Code." },
{ "scene": "wall", "text": "Je l'ai *embauché*.", "pause": 0.5 },
{ "scene": "end", "text": "Emb achez votre premier *employé IA*.", "say": "Crewmark. Emb achez votre premier employé IA." }
],
"post": { "tiktok": { "caption": "…", "hashtags": [] }, "instagram": { "caption": "…", "hashtags": [] } } }
say est utilisé quand la voix diffère du sous-titre ; pause maintient une scène plus longtemps après une ligne.
3. Voix → timeline
Un petit script Node prononce chaque ligne dans son propre fichier WAV, supprime le silence, les place bout à bout avec gap/pause
entre eux, les mixe dans public/voice.wav (adelay + amix, puis loudnorm=I=-14 pour le volume social),
et écrit public/timeline.json avec les from/to de chaque ligne en images :
{ "durationInFrames": 959, "lines": [ { "scene": "wall", "text": "J'ai arrêté de *soliciter* Claude Code.", "from": 12, "to": 51 } ] }
Mettez en cache chaque prise par son texte et sa voix pour que la modification d'une ligne ne re-parle pas le reste. La durationInFrames
de la composition provient de ce fichier.
4. Les scènes suivent la timeline
Regroupez les lignes consécutives avec la même scene en une seule <Sequence> ; donnez à chaque scène ses décalages de ligne („beats“) afin que
les animations tombent sur les mots. Les sous-titres sont un composant qui lit la ligne courante.
La pièce maîtresse est une caméra au-dessus de la bande UI : disposez l'UI en pixels CSS réels, puis zoomez et panoramiquez.
export function Cam({ zoom, x = 0, y = 0, children }: { zoom: number; x?: number; y?: number; children: React.ReactNode }) {
return (
<div style={{ position: "absolute", inset: 0, overflow: "hidden", background: "#fff" }}>
<div style={{ position: "absolute", width: 1080 / zoom + x, height: STAGE_H / zoom + y,
transform: `scale(${zoom}) translate(${-x}px, ${-y}px)`, transformOrigin: "0 0" }}>{children}</div>
</div>
);
}
À zoom = 1080 / 520 une disposition de 520 px de large (deux cartes de 248 px) remplit exactement la largeur. Animez zoom, x et
y avec une interpolation adoucie entre les beats pour zoomer sur le bouton cliqué.
Idées de scènes qui fonctionnent bien : un marché de cartes, un formulaire qui se remplit tout seul (texte tapé avec un curseur), un tableau où une carte change de colonne, un fil d'activité, un graphique qui se dessine (étiqueté „Données de démo“), un chat où l'agent demande et vous approuvez, une carte de fin dans le style du héros de la page d'atterrissage.
5. Rendre et regarder
node voice.mjs && npx tsc --noEmit
npx remotion still src/index.ts Reel /tmp/f0.png --frame=0 # la couverture
npx remotion render src/index.ts Reel out/reel.mp4
ffmpeg -v error -y -i out/reel.mp4 -vf fps=1,scale=200:-1,tile=8x5 -frames:v 1 out/sheet.jpg
Regardez la planche contact et une image fixe de chaque scène : la bande UI complète, rien coupé en bas de la bande, les sous-titres dans la bande inférieure, la bonne scène sous chaque ligne, l'image 0 pas vide.
6. Versionner
Commitez le source, le script et voice.wav/timeline.json ; gardez out/ et les téléchargements dans .gitignore. Pour retrouver
une ancienne version, checkout son commit et rendez à nouveau.
Checklist
- Couleurs, polices et texte repris du CSS et du texte du produit
- Bande UI ≥ 50 % de la hauteur, pleine largeur ; bandes seulement au-dessus et en dessous
- Image 0 rendue et vérifiée comme couverture
- Chaque scène montre une fonctionnalité que le produit possède réellement
- Numéros de démonstration étiquetés
- Planche contact examinée après chaque rendu
- Source et voix commités ; pas le MP4
Pièges que nous avons rencontrés
- Fondu d'entrée sur la première scène a laissé l'image 0, la couverture, vide. Ignorez l'animation d'entrée pour la première scène.
- Contenu coupé par la caméra. Un graphique dimensionné pour un niveau de zoom a dépassé le bas de la bande au zoom de la scène. Vérifiez une image fixe à la fin de chaque mouvement de caméra, pas seulement au début.
- Moitiés inférieures clairsemées. À faible zoom, les scènes courtes laissaient le bas de la bande blanche vide. Zoomez plus ou ancrez quelque chose en bas (une entrée de message, un pied de page) pour que la bande ressemble à un écran réel.
- Un script de révision qui nécessitait un fichier .srt. L'outil de révision de notre ancien pipeline ffmpeg lisait les timings de sous-titres que
Remotion n'écrit pas. Une planche contact avec
fps=1,tilefait le même travail. - Duplicate
transformdans un même objet de style (une animation de pression plus une animation d'entrée) échoue à la vérification de type. Enveloppez l'élément et mettez une transformation sur chaque couche. - Edge TTS pour le montage final. C'est gratuit et acceptable pour les brouillons, mais robotique comparé à quelque chose d'humain, et il n'est pas accompagné de licence commerciale. Remplacez la fonction TTS avant de publier à grande échelle.
Compétences associées
- short-video-formats : règles de mise en page et quel format construire.
- social-publish : la copie de publication conservée dans
script.json. - video-retro : quoi changer dans le prochain rendu.