Que fait stickman-explainer ?
Transforme un script court en un fichier MP4 1080×1920 terminé. Vous écrivez un storyboard JSON ; scripts/render.py dessine chaque image (traits d'encre tremblés, « line-boil » à 8 fps, révélations fondu/wipe/pop) et encode avec ffmpeg.
assets/example_storyboard.json n'est qu'un exemple de syntaxe (3 scènes) ; ne pas copier sa mise en page ou ses accessoires de personnages comme modèle.
Valeurs par défaut du style visuel
- Utilisez un canevas 9:16 (1080×1920, 30 fps). Gardez chaque scène pour une seule idée, généralement 2,5–4 secondes, avec des sous-titres courts dans les zones de sécurité de la plateforme.
- Utilisez un papier chaud (
#f3ebdd), une encre foncée (#2b2723) et un accent rouge brique sémantique (#b8372d). L'or soleil (#d89d48) est optionnel uniquement pour le motif du soleil. Gardez le reste plat : pas de dégradés, d'ombres ni d'effets 3D. - Dessinez des personnages simples à l'encre noire avec des visages clairs et beaucoup d'espace négatif. N'ajoutez pas de casques, couronnes, capes, vêtements rouges ou autres accessoires sauf si l'histoire ou l'utilisateur le demande explicitement. Ne déduisez pas de costumes d'un nom historique.
- Gardez les scènes lisibles : au maximum 2 personnages et 5 petites icônes. Rendez le sujet principal dominant ; évitez la superposition des sous-titres et des accessoires. Utilisez au maximum 2 lignes de sous-titres.
- Rendez les accessoires complexes reconnaissables. Dessinez un nœud avec des boucles distinctes qui se croisent, un centre serré et des extrémités de corde visibles. Donnez à une épée une poignée claire, une garde et une lame pointue. Après une coupe, laissez un espace visible entre les deux extrémités de la corde.
- Lorsque les durées de narration sont disponibles, faites suivre l'emphase rouge du sous-titre au mot actuellement parlé tout au long de la narration. Utilisez un intervalle
karaokepour chaque mot du sous-titre séparé par des espaces ; les mots inactifs restent dans la couleur de texte normale. - Préférez des dispositions asymétriques et laissez délibérément de l'espace négatif. Les lignes de sol, soleils, cartes de titre, bulles de pensée et autres motifs sont optionnels ; choisissez-les uniquement lorsqu'ils soutiennent la scène.
Flux de travail
- Script → scènes. Découpez l'histoire en scènes d'une phrase (≤ 6 mots de sous-titre, 2,5–4 s chacune). Motif : carte de titre → accroche/conflit → révélation concrète. Chaque scène ajoute exactement une nouvelle information. Lorsqu'une narration est fournie, alignez les limites des scènes sur les phrases parlées.
- Choisissez un visuel pour chaque phrase. Une métaphore par scène : personnage + accessoire, bulle de pensée ou une rangée d'icônes (X = « n'a pas »). Gardez ≤ 2 personnages et ≤ 5 icônes.
- Écrivez le storyboard (schéma ci-dessous). Avec des durées de narration mot par mot, ajoutez un intervalle
karaokepour chaque mot du sous-titre séparé par des espaces afin que l'accent rouge suive le mot prononcé. Utilisez**mot**uniquement pour un surlignage statique lorsque la durée des mots n'est pas disponible ; ne combinez pas le balisage statique aveckaraoke. - Vérifiez d'abord les images fixes (rapide, ~2 s/scène) :
python scripts/render.py story.json --stills out/stills→ ouvrez chaquescene_NN.png, corrigez les superpositions/placement, répétez. - Vidéo brouillon :
python scripts/render.py story.json out/draft.mp4 --draft(pas de sur-échantillonnage, 12 fps). - Final :
python scripts/render.py story.json out/final.mp4 [--audio voiceover.mp3]. Les durées des scènes doivent correspondre aux longueurs des phrases de la voix off ; mesurez l'audio avecffprobeet définissezdurationen conséquence. Les intervalles des mots sont en secondes depuis le début de leur scène et doivent provenir de l'alignement de la narration ou d'une écoute attentive. Vérifiez que chaque mot rouge apparaît avec son audio parlé, pas avant ni après.
Messages d'erreur et validation
render.py valide le storyboard avant de le dessiner : il rejette type/pose/face/kind/font inconnus, champ obligatoire manquant, id répété dans la même scène, duration <= 0, chemin lines à un seul point, résolution impaire (non paire) et id de carry inexistant avec un message clair au lieu d’un traceback. Il émet un avertissement pour les éléments qui ne seront jamais visibles (at après la fin de la scène). Si le fichier audio est plus court que la vidéo, la fin est remplie de silence ; s'il est plus long, il est coupé ; la durée de la vidéo ne change jamais.
Schéma du storyboard
{"meta": {"width":1080,"height":1920,"fps":30,"boil_fps":8,"transition":0.18,
"palette":{"accent":"#b8372d"}, "audio":null, "font_dirs":[]},
"scenes":[{"duration":3.4, "caption":"C'était **Alexandre** le Grand.",
"caption_at":0.5, "caption_y":null, "elements":[ ... ]}]}
Pour une emphase mot-par-mot suivant la parole, fournissez un tableau karaoke avec un objet { "start": secondes, "end": secondes } par mot séparé par des espaces dans caption. Les temps sont locaux à la scène ; l'accent rouge bascule à chaque intervalle. Le sous-titre ne doit pas contenir de balisage **...** lorsque karaoke est présent.
{"duration":2.8, "caption":"Il coupa le nœud d'un seul geste.", "caption_at":0.35,
"karaoke":[{"start":0.35,"end":0.76},{"start":0.76,"end":1.02},
{"start":1.02,"end":1.53},{"start":1.53,"end":2.15}],
"elements":[ ... ]}
Chaque élément : type, id optionnel (pour carry), at (s, temps d'apparition), dur (s, durée de révélation), anim = fade | wipe | rise | pop, until/out_dur (s, fondu de sortie).
Les positions x,x2,y,ground ≤ 2 sont lues comme des fractions de la largeur/hauteur, sinon en pixels. Les tailles sont toujours en pixels.
| type | champs clés |
|---|---|
ground | y (obligatoire) ; x, x2 optionnels, si absents ~%2–%98 du canevas — ligne de base tremblée |
sun | x,y,r,rays |
text | text (balisage **accent**), x,y,size,font = sans/sans_bold/sans_light/serif, tracking, caps, color, max_w, max_lines (défaut 3), rules (double ligne pour carte de titre ; largeur et espacement automatiques selon le texte) |
figure | x,ground,h,head_r, pose = stand/arms_open/point/wave/think/shrug/sit, dir (±1), face = neutral/smile/sad/surprised/angry, accessory optionnel = helmet/crown/hat/glasses, cape optionnel, look |
prop | kind = barrel/bowl/bucket/house/book/box/triangle/coins/tree, x,y (coin supérieur gauche), w,h, cross (X accent), flip |
thought | x,y,w,h, tail_to [px,py] (position de la tête), inner : liste d'éléments en px locaux dans la bulle |
lines | paths (liste de listes de points, px locaux), closed, smooth, color, width — échappatoire pour toute forme personnalisée |
Continuité entre scènes (carry)
Pour transporter un élément d'une scène à la suivante, donnez-lui un id, puis appelez-le dans la scène suivante avec carry. L'élément transporté commence avec la même vibration de ligne (même seed), il apparaît donc comme le « même dessin » même si la scène change ; la transition de nouvelle scène est par défaut une coupe (pas de fondu enchaîné).
"carry": [
"ground",
{"id": "sun", "set": {"r": 122}},
{"id": "alex", "set": {"x": 120, "face": "smile"}, "tween": {"at": 0.1, "dur": 0.9}},
{"id": "diog", "until": 0.1, "out_dur": 0.5}
]
Les lignes sont exécutées dans l'ordre : "ground" est transporté tel quel · sun est transporté avec des champs modifiés (le changement est instantané au début de la scène) · alex change de champs et sa position glisse pendant la durée (tween) · diog est transporté mais disparaît en fondu pendant la scène.
"carry": "all"transporte tout ("carry_except": ["id", ...]exclut). Les sous-titres ne sont jamais transportés.- Un élément non transporté disparaît instantanément au début de la scène ; utilisez
until/out_durpour une disparition en douceur. Un élément qui disparaît avecuntilne peut pas être transporté à la scène suivante. tweenn'anime que la position (x et y/ground) ; les changements de taille (r,h,w) et d'apparence (face,pose) sont appliqués instantanément au début de la scène.- Les éléments transportés sont dessinés sous les nouveaux éléments. Les
iddoivent être uniques entre les scènes pour les éléments transportés. - Un élément avec le même
idutilise la même seed dans chaque scène ; pour un nouvel élément propre à une scène, ne donnez pas d'id.
Ajoutez de nouveaux accessoires/poses en étendant PROPS / hands dans render.py ; conservez le même style d'encre (utilisez stroke(), jamais de lignes simples).
Disposition : décidez à nouveau pour chaque vidéo
Pas de coordonnées/modèles fixes. Comme le sujet de chaque vidéo est différent, dérivez la disposition du contenu de la scène :
- Choisissez d'abord l'idée visuelle unique de cette phrase (comparaison, liste, rêve, voyage, nombre…), puis construisez la disposition qui lui convient. La disposition peut changer entre les scènes d'une même vidéo.
- Faites de la place pour l'élément important : le sujet principal doit être le plus grand/le plus visible ; les éléments secondaires restent petits. Laissez délibérément un espace vide (espace négatif).
- La taille, la position et le nombre varient selon le contenu (1 personnage ou 2 ; rangée d'icônes ou un seul grand objet ; carte/chronologie). Si nécessaire, utilisez
linespour dessiner une forme personnalisée. - N'ajoutez pas automatiquement de costumes/coiffes aux personnages historiques. Les figures sont dessinées sans accessoires par défaut ; casque, couronne, chapeau ou cape ne sont ajoutés que si le récit ou l'utilisateur le demande explicitement. N'étendez pas la couleur d'accent comme décoration sur les vêtements du personnage.
- Pour les scènes de nœud et de coupe, rendez l'objet lisible : dessinez la corde avec plusieurs boucles distinctes qui se croisent, montrez le nœud serré au centre et distinguez les extrémités. Pour l'épée, la poignée, la garde et la lame pointue doivent être visibles. Après la coupe, laissez les deux extrémités de la corde coupée espacées et clairement visibles ; l'épée et le nœud ne doivent pas être masqués par les sous-titres. Dessinez les formes spéciales de corde/épée avec
lines. - Les motifs comme la ligne de sol, le soleil, la carte de titre sont optionnels ; ne les utilisez pas s'ils ne correspondent pas au sujet (par ex. intérieur, nuit, concept abstrait).
- Placez le sous-titre lisiblement et loin de l'interface de la plateforme ; en cas de conflit, ajustez avec
caption_y. Vérifiez visuellement les superpositions et les débordements sur les images fixes, ne vous fiez pas aux chiffres. - Les coordonnées de dessin sont données en pixels (ou en proportion 0–2) sur le canevas 1080×1920 ; planifiez les valeurs à zéro pour chaque scène.
Pièges
- Visualisez toujours les images fixes avant l'encodage ; les éléments superposés et les rayons de soleil coupés sont les défauts courants. Utilisez
--draftpour le brouillon, le rendu complet pour les détails : les images inchangées ne sont pas redessinées, une vidéo de 10 s prend environ 20 s en pleine qualité. - Le sous-titre passe à la ligne jusqu'à 86 % de largeur (3 lignes max), mais si un mot ne tient pas ou dépasse 3 lignes, la taille est réduite. Scindez une phrase de plus de 2 lignes en deux scènes. La position par défaut du sous-titre n'est qu'une sauvegarde ; si nécessaire, fournissez
caption_ydans la scène. - Polices : installez Poppins et Playfair Display (ou Lora), ou fournissez
--font-dir. Si aucune police TrueType n'est trouvée, le moteur de rendu émet un avertissement et les caractères turcs (İ ş ğ â) peuvent manquer ; vérifiez sur les images fixes. Les dossiers de polices Linux, macOS et Windows sont scannés. - Gardez la couleur d'accent sémantique (sujet, mot-clé, négation). N'ajoutez pas d'autres couleurs.
- Lorsque
karaokeest présent, l'accent rouge du sous-titre suit le mot par mot de la narration en utilisant les intervalles fournis. Ne laissez pas un mot-clé rouge fixe à l'écran ; rendez les mots inactifs dans la couleur de texte du sous-titre.