Vidéo histoire en style bloc-toon (look « Busted by the Shoeshine Man »)
Utilisez ceci quand l'utilisateur souhaite une vidéo verticale (720x1280 ou 1080x1920, 30 ips) de narration à la première personne, de 30 à 130 secondes, racontée avec une voix off, avec des personnages 3D en blocs arrondis chunky, un mouvement continu fluide et des légendes colorées percutantes. Tout est code : scène three.js dans Chromium headless, une fonction déterministe frame(t), des captures d'écran envoyées à ffmpeg.
La priorité de l'utilisateur est la fidélité du design des personnages et la fluidité de l'animation. Ne sautez pas les vérifications des personnages à l'étape 5.
1. À quoi ressemble la vidéo de référence (mesurée)
- 720x1280, 30 ips, ~122 s, AAC stéréo environ -14 LUFS avec un fond sonore continu et calme sous la voix.
- ~47 coupes franches (coupes sur une seule image, jamais de fondu enchaîné), durée moyenne d'un plan ~2,6 s (intervalle 1,2-5 s). À l'intérieur d'un plan, la caméra est TOUJOURS en mouvement : zoom avant avec easing, zoom arrière, dérive grue/angle plongée, poussée en contre-plongée, whip-tilt vers un plafond ou une fenêtre. Moins de 1 % des images sont statiques.
- Légendes dans le tiers inférieur (en haut d'environ 64 à 68 % de la hauteur), 2-3 mots en minuscules par groupe, police géométrique arrondie en gras (Poppins ExtraBold), fond blanc avec les mots-clés colorés (or #f5c32c, cyan #48c9f0, corail #ff5b4d, rose #ff7ac0, vert #2ecc8f, orange #ffa21f), contour noir épais, petite échelle pop sur chaque groupe.
- Environnement : intérieur chaud crème/tan (mur #dcbf96, sol #c7aa7d, pierre #d9c39b, lambris #b99a6f, ciel #c7dcec) avec des accents bordeaux #570d0e, marine #1c2a49, or #e0990d, canard #1f8f8c, rouge #a3160f.
- Éclairage : doux, lumineux, légèrement chaud ; dégradé d'ombrage lisse et ombres portées douces. Pas de bandes dures cel.
2. Bible de design des personnages (suivre exactement)
Silhouette et proportions
- Chaque partie du corps est une boîte arrondie. Le rayon de biseau est d'environ 15-18 % de la plus petite dimension de la pièce. N'utilisez jamais de sphères ou de capsules pour les parties du corps.
- Tête : un cuboïde légèrement plus large que haut (0,44 x 0,41 x 0,39 unités), environ 1/4 à 1/3 de la hauteur de la figure dans les plans serrés, de sorte que la figure mesure ~4 têtes de haut dans les plans larges. Bloc de cou court. Torse 0,46 x 0,56 x 0,26. Bras 0,27 + 0,26 (haut + avant-bras). Jambes 0,30 + 0,27. Hauteur totale environ 1,7 unité.
- Les mains sont des blocs de moufles massifs : un bloc de paume avec quatre doigts courts à deux segments et un pouce séparé. Elles peuvent se recroqueviller en poing ou s'ouvrir et s'écarter. Les mains ne doivent jamais sembler arachnéennes.
- Bande de manchette blanche à chaque poignet. Chaussures noires, un bloc plat avec un bout arrondi.
- Pas de cassures visibles aux articulations. Là où deux boîtes arrondies se rejoignent au niveau d'une articulation pliée (hanche, genou, épaule, coude, poignet, cou/torse, hanche/torse), les biseaux ouvrent un espace ou une entaille qui ressemble à une cassure. Cachez chaque articulation sous une couche de tissu de la même couleur que les vêtements : une petite sphère (
ball()) centrée sur le pivot de l'articulation, de la taille de la demi-largeur du membre + ~0,002 et étirée le long du membre (sc[1,1.2-1.4,1]) ; un bloc de bassin reliant les deux hanches ; un bloc de col au-dessus de la couture cou/torse ; une pièce de poignet de couleur chair sous la manchette. Ne surdimensionnez pas les sphères (la demi-largeur du membre + 0,015 a donné des genoux et coudes bossus). Vérifiez avec un test de stress de poses pliées (bras levé, posture de marche, jambe levée), en zoomant.
Visage (peint à plat sur le devant de la tête, redessiné à chaque image sur une texture canvas)
- Grands yeux ronds : sclère blanche, iris vert ou marron, pupille noire, reflet blanc. Ligne de paupière supérieure noire et épaisse. Les yeux peuvent regarder autour (lookX/lookY), cligner des yeux et plisser les yeux.
- Sourcils épais effilés qui s'inclinent (en colère/inquiet) et se lèvent. Des lunettes à monture noire rondes, beaucoup plus grandes que les yeux (rayon d'anneau environ 2x le rayon de l'œil). Les lunettes doivent être une seule monture connectée : les deux anneaux sont reliés par un pont de courbe incurvé sur le nez et chaque anneau a une ligne de tempe allant jusqu'au bord du visage. Deux anneaux flottants semblent cassés (un utilisateur l'a signalé sur la première fiche de distribution).
- Pas de nez. La bouche est une toute petite ligne/courbe, ou une bouche ouverte remplie avec une langue rose quand elle parle. Valeur de sourire -1..1.
- Optionnel : blush doux.
Cheveux, chapeaux, accessoires (blocs chunky empilés)
- Héros : quiff marron avec une mèche pointue, pièces latérales et un bloc arrière. Autres : courts, ou longs (rideaux latéraux + bloc arrière). La frange noire est un bloc fin incliné.
- Casquette de police : bloc de couronne marine + visière noire + badge doré. Casquette plate : bloc marron + bord court + bouton clair.
- Homme âgé : bloc de moustache blanche + patch de tempe blanc.
- Les tenues sont en blocs de couleur et simples : costume (veste bordeaux, chemise blanche, cravate foncée, revers, lanyard doré en V + badge ID blanc avec bande bleue), uniforme (marine, badge doré, ceinture noire + boucle dorée), tablier (chemise blanche, tablier marron avec boutons), plus des variantes gris/vert/tan/marine/canard pour les figurants.
- Tons de peau : pêche #efaa71, tan #d89a62, marron #8a5230, foncé #5a3320, clair #f4c49a. Cheveux marron #59321a, noir #14110f.
Rendu
- Contour à l'encre foncé et fin (#1c0f06) sur chaque pièce via un shader d'enveloppe inversée avec une largeur constante à l'écran. La largeur est une fraction de la distance à la caméra :
w = px / ((H/2) / tan(fov/2))oùpxest l'épaisseur de ligne souhaitée en pixels écran (environ 0.003 pour les plans vidéo 720x1280, environ 1,5-2 px). Pour les feuilles multi-personnages lointaines en 1600x900 utilisezK.setOutlineWidth(.0009), pour les bustes environ .0016 ; la valeur par défaut est bien trop épaisse pour une caméra distante. Les visages et les petites pièces de garniture sautent le contour. - Matériau Lambert, lumière hémisphérique (environ 2,9) + un soleil d'ombre douce (environ 1,5). Ombres PCF douces.
- La foule en arrière-plan utilise
lod: 1(pas de doigts, moins de blocs de cheveux). Ne construisez jamais un design unique par figurant ; variez la couleur de peau, les cheveux, la couleur des vêtements et la coiffure.
Règle de variété : chaque personnage nommé doit être reconnaissable par sa seule silhouette (chapeau, forme du bloc de cheveux, lunettes, moustache, ton de peau, couleur de la tenue). Utilisez les mêmes proportions pour tous.
Feuilles de référence (à faire quand l'utilisateur demande un design de personnage ou un environnement, avant toute vidéo)
Construisez un designsheet.html et un envsheet.html à côté de scene.html (même kit, même render.cjs, exécutez node render.cjs designsheet.html out 0,1,2 --w 1600 --h 900). frame(t) choisit un mode avec Math.round(t) ; les étiquettes sont une superposition DOM (Poppins) positionnée en projetant des points 3D avec new THREE.Vector3(x,y,z).project(camera) ; le titre de la page et les échantillons sont aussi en DOM.
- Fichier de distribution
cast.js: une tableDEF(nom, rôle, 4 échantillons [peau, cheveux, tenue, accent]), une tableBUILDd'un constructeur par personnage (makeChar+ quelquesbox()d'accessoires supplémentaires),makeOne(name),makeCast(), etpose(name, C)qui réinitialise le visage et la pose à chaque image et applique une pose de fiche modèle propre. Rendez chaque personnage unique par sa silhouette :scale/headScale, coiffure, chapeau, lunettes, bloc de moustache, ton de peau et une couleur d'accent différents. - Feuille 0 alignement : tous les personnages en ligne (caméra environ [0,1.02,15], fov 16.5), nom + rôle + carrés d'échantillons sous chacun. Feuille 1 tournant du personnage principal à yaw 0, .75, PI/2, PI (face, 3/4, profil, dos). Feuille 2 expressions : six bustes (neutre, heureux, en colère, surpris, inquiet, suffisant) dessinés sous forme de tuiles ciseaux, car empiler six personnages dans une même scène 3D met les rangées sous le sol et en chevauchement. Motif de tuile :
renderer.autoClear=false; renderer.setScissorTest(true);puis par tuilesetViewport(x, H-y-h, w, h); setScissor(...); renderer.clear(); camera.aspect = w/h; camera.updateProjectionMatrix(); renderer.render(scene, camera);et réinitialisez la fenêtre d'affichage, le test de ciseau et autoClear à la fin deframe. - Convention de signe des sourcils :
face.brow = [height, tilt, arch], tilt > 0 = extrémités intérieures vers le bas (en colère), tilt < 0 = extrémités intérieures vers le haut (inquiet). DansdrawFace, c'estang = -s * f.brow[1] * .55. (La première version l'avait inversé et un officier en colère avait l'air triste.) - Feuille d'environnement 0 : quatre tuiles ciseaux 760x318 (héros large depuis l'entrée, mur du fond avec la fenêtre en arc, le coin de l'accessoire clé, une grue en plongée avec
hall.ceiling.visible = false), chacune légendée ; placez le casting dans le décor pour l'échelle et faites-les face à la caméra ou les uns aux autres (vérifiez yaw : yaw 0 fait face à +z vers une caméra sur +z ; un personnage tournant le dos à la caméra est un bug de mise en scène). Feuille 1 : chaque accessoire seul sur une scène d'accessoires éloignée (x = 400 + i*20) pour qu'il n'apparaisse jamais dans les plans du hall, tuiles 486x300. Feuille 2 : plan au sol vu du dessus avecup = [-1,0,0](l'axe long va horizontalement), caméra environ [-1,120,-13] regardant droit vers le bas, fov étroit (environ 9,3) et étiquettes épinglées projetées à partir de points du monde. Incluez toujours une bande de palette (10 échantillons) dans l'en-tête. - Le plafond et les poutres du hall vivent dans un groupe exposé comme
H.ceiling(ombres projetées désactivées, pour que le soleil éclaire toujours le hall) ; cachez-le pour les plans grue et vue du dessus. Le texte des panneaux doit être dimensionné à son plan : un panneau de 1,9 x 0,5 prend800 56px Poppins, pas 78px (coupé). - Gotcha de Three.js :
Object3D.positionet.rotationsont des propriétés en lecture seule ; définissez les composants (m.rotation.x = ...,m.position.set(...)) et n'utilisez jamaisObject.assign(mesh, { rotation: ... }), qui lance une erreur et arrête__ready. - Livrez les PNG plus les fichiers
.jsavec SendUserFile ; re-rendez et renvoyez chaque feuille après tout changement de kit pour que l'utilisateur ne conserve jamais de feuilles obsolètes.
3. Règles de fluidité d'animation
- L'animation est une fonction pure du temps :
frame(t)définit tout à partir det. Pas d'état conservé entre les images, donc n'importe quelle image peut être rendue dans n'importe quel ordre ou en parallèle. - Utilisez
kf([[t0,v0],[t1,v1]], t, easeFn)pour chaque mouvement. UtilisezeaseOutpour les zoom avant et les arrivées,easeIOpour les mouvements de grue/whip,easepour les petites choses. Jamais linéaire pour une caméra ou un membre. - La caméra n'est jamais statique. Chaque plan a une position + regard + fov + quelques degrés de roulis avec easing, plus une dérive de caméra portée toujours active
[sin(.5t)*.03, sin(.37t)*.02]. - Coupes franches uniquement aux limites des plans, sur un mot ou un temps. Pas de fondus enchaînés. Plan moyen 2-3 s.
- Couches inactives sur chaque personnage, tout le temps : mouvement de respiration, balancement, roulis de tête, clignements toutes les ~3-4 s (0,13 s avec easing), petits mouvements rapides des yeux.
K.idle()fait cela. - L'ouverture de la bouche provient de l'intensité réelle de la voix off (
lipsync.pyécritenv.jsonà 30 ips). Jamais de battement en boucle. - Gestes : anticipation (petit retrait avant un point/coup), dépassement (
easeBack), maintien, stabilisation. La tête mène, le torse suit (utilisezheadYawavanttwist). - Cycle de marche : jambes opposées, chaque bras balance en opposition à la jambe du même côté, les genoux ne plient que vers l'arrière (
Math.max(0, knee)), mouvement de pompage 2x par cycle, petite torsion et inclinaison du torse. La phase est en cycles : utilisez environ 1 cycle/s pour une marche normale. - Les personnages ne doivent jamais chevaucher le mobilier : placez-les avec des x/z explicites et vérifiez une image fixe.
4. Pipeline
- Script : première personne, accroche dans les 3 premières secondes, phrases courtes et percutantes, escalade, rebondissement, punchline finale. Environ 2,5 mots/s.
- Voix off : l'utilisateur fournit généralement un fichier ElevenLabs. Sinon, demandez-le ou synthétisez une piste de remplacement. Enregistrez sous
voice.wav. - Timings des mots : obtenez les horodatages au niveau du mot (alignement forcé sur le texte du script, par exemple pocketsphinx ou horodatages de mots whisper). Enregistrez
words.jsoncomme[{"w":"mon","s":0.20,"e":0.41}, ...], en utilisant l'orthographe et la ponctuation du script (les légendes utilisent la ponctuation finale pour diviser les groupes). - Enveloppe de synchronisation labiale :
python3 lipsync.py voice.wav env.json. - Liste des plans : une ligne par plan : heure de début/fin, ce que voit le spectateur, mouvement de caméra, qui agit. Visez une coupe toutes les ~2,6 s. Variez les cadrages : gros plan visage, plan moyen deux personnages, poussée contre-plongée, grue plongée au-dessus de la foule, whip-tilt, plan par-dessus l'épaule, insert sur un accessoire (téléphone, badge). Écrivez les plans dans
SHOTSdansscene.html. - Construisez le casting + décor à partir de
blockkit.js/props.js(voir le code). Réutilisezroom(),chair(),bench(),crowd(); construisez de nouveaux accessoires avecbox()dans la palette ci-dessus. - Vérifiez d'abord les images fixes (peu coûteux) :
node render.cjs scene.html outstills 1,4,7,...; regardez les gros plans de chaque personnage nommé, une marche, un geste, un plan large. Corrigez avant de rendre la vidéo. - Rendu vidéo (environ 1-1,7 s/image en 720x1280 sur 2 cœurs, donc 40 s c'est environ 15-25 min ; exécutez les morceaux en parallèle et en arrière-plan) :
export NODE_PATH=$(npm root -g) # playwright est global dans le bac à sable ; sinon : npm i playwright && npx playwright install chromium
S=30; for i in 0 1; do (node render.cjs scene.html part$i.mp4 --from $((i*S)) --to $(((i+1)*S)) > r$i.log 2>&1 &); done # 2 morceaux de 30 s ; attendez les deux
printf "file 'part0.mp4'\nfile 'part1.mp4'\n" > list.txt && ffmpeg -y -f concat -safe 0 -i list.txt -c copy silent.mp4
- Audio + muxage (fond sonore continu et calme sous la voix, environ -14 LUFS) :
D=130 # >= longueur vidéo
ffmpeg -y -f lavfi -i "sine=f=55:d=$D" -f lavfi -i "sine=f=82.4:d=$D" -f lavfi -i "sine=f=110:d=$D" -filter_complex "[0][1][2]amix=inputs=3:normalize=0,lowpass=f=500,tremolo=f=0.12:d=0.35,aformat=channel_layouts=stereo,afade=t=in:d=1[bed]" -map "[bed]" -ar 44100 bed.wav
ffmpeg -y -i silent.mp4 -i voice.wav -i bed.wav -filter_complex "[2:a]volume=0.12[b];[1:a][b]amix=inputs=2:duration=first:normalize=0,loudnorm=I=-14:TP=-1.5:LRA=7,aformat=channel_layouts=stereo[a]" -map 0:v -map "[a]" -c:v copy -c:a aac -b:a 192k final.mp4
- Contrôle qualité avant livraison : (a) fiche contact, une image par ~2,6 s, vérifiez personnages/légendes ; (b) vérification du mouvement, différence d'image moyenne sur un flux gris réduit, attendez 0 image statique :
python3 - <<'E'
import subprocess, numpy as np
W,H=90,160
a=np.frombuffer(subprocess.run(['ffmpeg','-v','error','-i','final.mp4','-vf',f'scale={W}:{H},format=gray','-f','rawvideo','-'],capture_output=True).stdout,dtype=np.uint8).reshape(-1,H,W).astype(np.float32)
d=np.abs(np.diff(a,axis=0)).mean(axis=(1,2)); print(len(a),'images; statique:',(d<.02).sum(),'; pics(coupes):',(d>14).sum())
E
(c) intensité sonore ffmpeg -i final.mp4 -af ebur128 -f null - (environ -14 LUFS), (d) les légendes correspondent à la parole.
Livrez avec SendUserFile (mp4 + une image de couverture).
5. Configuration
mkdir -p work && cd work && npm init -y >/dev/null && npm i [email protected] @fontsource/poppins
# créez les fichiers ci-dessous dans ce dossier : blockkit.js props.js captions.js render.cjs lipsync.py scene.html
# (ou copiez les fichiers prêts à l'emploi du dossier kit/ de ce dépôt, qui contient aussi des modèles...)