Block-Toon Story-Video („Busted by the Shoeshine Man“-Look)
Verwende dies, wenn der Benutzer ein vertikales (720x1280 oder 1080x1920, 30 fps) First-Person-Story-Video möchte, 30-130 s lang, erzählt über ein Voiceover, mit klobigen, abgerundeten 3D-Figuren, gleichmäßiger, durchgehender Bewegung und knackigen, farbigen Untertiteln. Alles ist Code: three.js-Szene in Headless-Chromium, eine deterministische frame(t)-Funktion, Screenshots werden an ffmpeg weitergeleitet.
Die Priorität des Benutzers liegt auf Charakterdesign-Treue und Animationsglätte. Überspringe die Charakterprüfungen in Schritt 5 nicht.
1. Wie das Referenzvideo aussieht (gemessen)
- 720x1280, 30 fps, ~122 s, AAC Stereo etwa -14 LUFS mit einem leisen, durchgehenden Teppich unter der Stimme.
- ~47 harte Schnitte (Einzelbild-Schnitte, nie Überblendungen), durchschnittliche Einstellung ~2,6 s (Spanne 1,2-5 s). Innerhalb einer Einstellung bewegt sich die Kamera IMMER: sanfter Push-In, Pull-Out, Kran-/Hochwinkelfahrt, Tiefwinkel-Push, Peitschen-Schwenk nach oben zu einer Decke oder einem Fenster. Unter 1 % der Frames sind statisch.
- Untertitel im unteren Drittel (oben etwa 64-68 % der Höhe), 2-3 Wörter pro Gruppe in Kleinbuchstaben, fette, abgerundete, geometrische Sans (Poppins ExtraBold), weiße Basis mit farbigen Schlüsselwörtern (Gold #f5c32c, Cyan #48c9f0, Koralle #ff5b4d, Pink #ff7ac0, Grün #2ecc8f, Orange #ffa21f), dicker schwarzer Strich, kleiner Skalierungs-Pop bei jeder Gruppe.
- Umgebung: warmes, cremiges/beiges Interieur (Wand #dcbf96, Boden #c7aa7d, Stein #d9c39b, Paneel #b99a6f, Himmel #c7dcec) mit Akzenten in Kastanienbraun #570d0e, Marineblau #1c2a49, Gold #e0990d, Petrol #1f8f8c, Rot #a3160f.
- Beleuchtung: weich, hell, leicht warm; gleichmäßige Farbverlaufsschattierung und weiche, geworfene Schatten. Keine harten Cel-Bänder.
2. Character-Design-Bibel (genau befolgen)
Silhouette und Proportionen
- Jeder Körperteil ist eine abgerundete Box. Der Radius der Abrundung beträgt etwa 15-18 % der kleinsten Abmessung des Teils. Verwende niemals Kugeln oder Kapseln für Körperteile.
- Kopf: ein Quader, etwas breiter als hoch (0,44 x 0,41 x 0,39 Einheiten), etwa 1/4-1/3 der Figurenhöhe in Nahaufnahmen, sodass die Figur in weiten Einstellungen ~4 Köpfe hoch ist. Kurzer Halsblock. Torso 0,46 x 0,56 x 0,26. Arme 0,27 + 0,26 (Ober- + Unterarm). Beine 0,30 + 0,27. Gesamthöhe etwa 1,7 Einheiten.
- Hände sind klobige Fäustlingsblöcke: ein Handflächenblock mit vier kurzen, zweigliedrigen Fingern und einem separaten Daumen. Sie können von offen zu Faust und gespreizt gekrümmt werden. Hände dürfen niemals spinnenartig aussehen.
- Weißes Manschettenband an jedem Handgelenk. Schwarze Schuhe, ein flacher Block mit abgerundeter Spitze.
- Keine sichtbaren Gelenkbrüche. Wenn zwei abgerundete Boxen an einem gebeugten Gelenk (Hüfte, Knie, Schulter, Ellbogen, Handgelenk, Nacken/Torso, Hüfte/Torso) aufeinandertreffen, öffnen die Abrundungen eine Lücke oder Kerbe, die als Bruch gelesen wird. Verstecke jedes Gelenk unter einer Stoffschicht in der gleichen Farbe wie die Kleidung: eine kleine Kugel (
ball()), zentriert auf dem Gelenkdrehpunkt, dimensioniert auf die halbe Gliedmaßenbreite + ~0,002 und entlang der Gliedmaße gestreckt (sc[1,1.2-1.4,1]); ein Beckenblock, der die beiden Hüften verbindet; ein Kragenblock über der Nacken/Torso-Naht; ein hautfarbenes Handgelenkstück unter der Manschette. Übergröße der Kugeln vermeiden (halbe Gliedmaßenbreite + 0,015 machte klumpige Knie und Ellbogen). Überprüfe mit einem Stresstest gebeugter Posen (Arm erhoben, mittlerer Schritt, Bein angehoben), hineingezoomt.
Gesicht (flach auf die Vorderseite des Kopfes gemalt, jedes Bild neu auf einer Leinwandtextur gezeichnet)
- Große runde Augen: weiße Lederhaut, grüne oder braune Iris, schwarze Pupille, weißer Glanzpunkt. Dicke schwarze Oberlidlinie. Augen können umherblicken (lookX/lookY), blinzeln und kneifen.
- Dicke, sich verjüngende Augenbrauen, die sich neigen (wütend/besorgt) und heben. Schwarze Ringgläser sind viel größer als die Augen (Ringradius etwa 2x Augenradius). Gläser müssen ein verbundener Rahmen sein: Die beiden Ringe sind durch einen gebogenen Brückenbogen über die Nase verbunden, und jeder Ring hat eine Schläfenlinie, die bis zum Rand des Gesichts verläuft. Zwei schwebende Ringe sehen kaputt aus (dies wurde von einem Benutzer beim ersten Cast-Blatt angemerkt).
- Keine Nase. Der Mund ist eine winzige Linie/Kurve oder ein gefüllter offener Mund mit einer rosa Zunge beim Sprechen. Lächeln-Wert -1..1.
- Optionales, weiches Rouge.
Haare, Hüte, Accessoires (gestapelte, klobige Blöcke)
- Held: braune Tolle mit einem spitzen Büschel, Seitenteilen und einem Hinterkopfblock. Andere: kurz oder lang (Seitenvorhänge + Hinterkopfblock). Schwarze Stirnfransen sind ein dünner, abgewinkelter Block.
- Polizeimütze: marineblauer Kronenblock + schwarzer Schirm + goldener Abzeichen. Schiebermütze: brauner Block + kurze Krempe + heller Knopf.
- Älterer Mann: weißer Block-Schnurrbart + weißer Schläfenfleck.
- Outfits sind farblich abgesetzt und schlicht: Anzug (kastanienbraune Jacke, weißes Hemd, dunkle Krawatte, Revers, goldenes Lanyard V + weißer ID-Anstecker mit blauem Streifen), Uniform (marineblau, goldener Anstecker, schwarzer Gürtel + goldene Schnalle), Schürze (weißes Hemd, braune Schürze mit Knopfpunkten), plus graue/grüne/beigefarbene/marineblaue/petrolfarbene Varianten für Statisten.
- Hauttöne: Pfirsich #efaa71, Bräune #d89a62, Braun #8a5230, Dunkel #5a3320, Hell #f4c49a. Haare Braun #59321a, Schwarz #14110f.
Rendering
- Dünne, dunkle Tintenkontur (#1c0f06) auf jedem Teil über einen invertierten Hüll-Shader mit bildschirmkonstanter Breite. Die Breite ist ein Bruchteil der Kameradistanz:
w = px / ((H/2) / tan(fov/2)), wobeipxdie gewünschte Linienstärke in Bildschirmpixeln ist (etwa 0,003 für 720x1280 Videoaufnahmen, etwa 1,5-2 px). Für ferne Multi-Charakter-Blätter bei 1600x900 verwendeK.setOutlineWidth(.0009), für Büsten-Kacheln etwa .0016; der Standardwert ist bei einer entfernten Kamera viel zu dick. Gesichter und winzige Verzierungensteile überspringen die Kontur. - Lambert-Material, Halbkugelbeleuchtung (etwa 2,9) + eine weiche Schattensonne (etwa 1,5). Weiche PCF-Schatten.
- Hintergrundmenge verwendet
lod: 1(keine Finger, weniger Haarblöcke). Baue niemals ein einzigartiges Design pro Statisten; variiere Haut, Haare, Outfitfarbe und Frisur.
Variationsregel: Jeder benannte Charakter muss allein anhand der Silhouette erkennbar sein (Hut, Haarblockform, Brille, Schnurrbart, Hautton, Outfitfarbe). Verwende für alle die gleichen Proportionen.
Referenzblätter (mach diese, wenn der Benutzer nach einem Charakterdesign oder einer Umgebung fragt, vor jedem Video)
Erstelle eine designsheet.html und eine envsheet.html neben scene.html (gleiches Kit, gleiches render.cjs, ausführen mit node render.cjs designsheet.html out 0,1,2 --w 1600 --h 900). frame(t) wählt einen Modus mit Math.round(t); Beschriftungen sind ein DOM-Overlay (Poppins), positioniert durch Projektion von 3D-Punkten mit new THREE.Vector3(x,y,z).project(camera); der Seitentitel und die Farbfelder sind ebenfalls DOM.
- Cast-Datei
cast.js: eineDEF-Tabelle (Name, Rolle, 4 Farbfelder [Haut, Haare, Outfit, Akzent]), eineBUILD-Tabelle mit einem Builder pro Charakter (makeChar+ ein paar zusätzlichebox()-Accessoires),makeOne(name),makeCast()undpose(name, C), die das Gesicht und die Pose jedes Frame zurücksetzt und eine saubere Posenvorlage anwendet. Mache jeden Charakter durch Silhouette einzigartig: unterschiedlichescale/headScale, Frisur, Hut, Brille, Schnurrbartblock, Hautton und eine Akzentfarbe. - Blatt 0 Aufstellung: alle Charaktere in einer Reihe (Kamera etwa [0,1.02,15], fov 16,5), Name + Rolle + Farbfeldquadrate unter jedem. Blatt 1 Drehung des Hauptcharakters bei Gierwinkel 0, 0,75, PI/2, PI (vorne, 3/4, Seite, hinten). Blatt 2 Ausdrücke: sechs Büsten (neutral, glücklich, wütend, überrascht, besorgt, selbstgefällig), gezeichnet als Scissor-Tiles, weil das Stapeln von sechs Charakteren in einer 3D-Szene Reihen unter dem Boden und Überlappungen erzeugt. Tile-Muster:
renderer.autoClear=false; renderer.setScissorTest(true);dann pro TilesetViewport(x, H-y-h, w, h); setScissor(...); renderer.clear(); camera.aspect = w/h; camera.updateProjectionMatrix(); renderer.render(scene, camera);und setze am Ende vonframeden Viewport, den Scissor-Test und autoClear zurück. - Augenbrauen-Vorzeichenkonvention:
face.brow = [height, tilt, arch], tilt > 0 = innere Enden nach unten (wütend), tilt < 0 = innere Enden nach oben (besorgt). IndrawFaceist dasang = -s * f.brow[1] * .55. (Die erste Version hatte es invertiert und ein wütender Polizist sah traurig aus.) - Umgebungsblatt 0: vier 760x318 Scissor-Tiles (Helden-Weitwinkel vom Eingang, Endwand mit dem Bogenfenster, die Schlüsselrequisitenecke, eine Vogelperspektive mit
hall.ceiling.visible = false), jede beschriftet; platziere den Cast im Set für den Maßstab und lass sie die Kamera oder einander ansehen (Gierwinkel prüfen: Gier 0 zeigt nach +z zu einer Kamera auf +z; ein Charakter mit dem Rücken zur Kamera ist ein Aufstellungsfehler). Blatt 1: jede Requisite allein auf einer weit entfernten Requisitenbühne (x = 400 + i*20), sodass sie nie in Hallenaufnahmen erscheint, Tiles 486x300. Blatt 2: Draufsicht auf den Grundriss mitup = [-1,0,0](die Längsachse verläuft horizontal), Kamera etwa [-1,120,-13] gerade nach unten schauend, enges fov (etwa 9,3) und Pin-Beschriftungen projiziert aus Weltpunkten. Füge immer einen Farbfeldstreifen (10 Farbfelder) im Header ein. - Die Hallendecke und -balken leben in einer Gruppe, die als
H.ceilingexponiert ist (Schattenwurf ausgeschaltet, damit die Sonne den Flur noch beleuchtet); verstecke sie für Vogelperspektiven und Draufsichten. Beschriftungstext muss auf seine Ebene skaliert sein: ein 1,9 x 0,5 Schild benötigt 800 56px Poppins, nicht 78px (abgeschnitten). - Three.js-Falle:
Object3D.positionund.rotationsind schreibgeschützte Eigenschaften; setze Komponenten (m.rotation.x = ...,m.position.set(...)) und niemalsObject.assign(mesh, { rotation: ... }), was einen Fehler wirft und__readystoppt. - Liefere die PNGs plus die
.js-Dateien mit SendUserFile; rendere jedes Blatt nach jeder Kit-Änderung neu und sende es erneut, damit der Benutzer niemals veraltete Blätter hat.
3. Animationsglätte-Regeln
- Animation ist eine reine Funktion der Zeit:
frame(t)setzt alles abhängig vont. Kein Zustand zwischen den Frames, sodass jeder Frame in beliebiger Reihenfolge oder parallel gerendert werden kann. - Verwende
kf([[t0,v0],[t1,v1]], t, easeFn)für jede Bewegung. VerwendeeaseOutfür Push-Ins und Ankünfte,easeIOfür Kran-/Peitschenbewegungen,easefür kleine Dinge. Niemals linear für eine Kamera oder Gliedmaße. - Kamera ist niemals statisch. Jede Einstellung hat eine erleichterte Position + Blickrichtung + fov + ein paar Grad Roll, plus ein ständiges Handheld-Driftschwanken
[sin(.5t)*.03, sin(.37t)*.02]. - Harte Schnitte nur an Einstellungsgrenzen, auf einem Wort oder Takt. Keine Überblendungen. Durchschnittliche Einstellung 2-3 s.
- Leerlaufebenen auf jedem Charakter, die ganze Zeit: Atemwippen, Schwanken, Kopfbewegen, Blinzeln alle ~3-4 s (0,13 s erleichtert), winzige Augenzuckungen.
K.idle()erledigt dies. - Mundöffnungsgrad kommt von der tatsächlichen Voiceover-Lautstärke (
lipsync.pyschreibt env.json mit 30 fps). Niemals eine sich wiederholende Flatterbewegung. - Gesten: Antizipation (kleines Zurückziehen vor einem Zeigen/Zuschlagen), Überschwingen (
easeBack), Halten, Beruhigen. Köpfe führen, Torso folgt (verwendeheadYawvortwist). - Gehzyklus: Beine gegenläufig, jeder Arm schwingt gegenläufig zu seinem gleichseitigen Bein, Knie beugen nur nach hinten (
Math.max(0, knee)), Wippen 2x pro Zyklus, kleine Torsodrehung und -neigung. Phase ist in Zyklen: verwende etwa 1 Zyklus/s für einen normalen Gang. - Charaktere dürfen sich niemals mit Möbeln überschneiden: platziere sie mit explizitem x/z und überprüfe ein Standbild.
4. Pipeline
- Skript: Ich-Perspektive, Aufhänger in den ersten 3 Sekunden, kurze prägnante Sätze, Steigerung, Wendung, Punchline am Ende. Etwa 2,5 Wörter/s.
- Voiceover: Der Benutzer liefert normalerweise eine ElevenLabs-Datei. Wenn nicht, danach fragen oder einen Platzhalter-Track synthetisieren. Speichern als
voice.wav. - Wort-Zeitsteuerung: Wortgenaue Zeitstempel abrufen (forced alignment gegen den Skripttext, z.B. pocketsphinx oder whisper-Wortzeitstempel). Speichern als
words.jsonals[{"w":"mein","s":0.20,"e":0.41}, ...], wobei die Schreibweise und Zeichensetzung des Skripts verwendet wird (Untertitel verwenden nachgestellte Zeichensetzung, um Gruppen zu trennen). - Lippensynchronisations-Hüllkurve:
python3 lipsync.py voice.wav env.json. - Einstellungsliste: eine Zeile pro Einstellung: Start-/Endzeit, was der Betrachter sieht, Kamerafahrt, wer agiert. Ziel ist ein Schnitt alle ~2,6 s. Variiere die Bildgestaltung: Nahaufnahme Gesicht, Medium-Zweiershot, Tiefwinkel-Push-In, Hochwinkel-Kran über die Menge, Peitschen-Schwenk, Over-the-Shoulder, Detail auf einer Requisite (Telefon, Abzeichen). Schreibe die Einstellungen in
SHOTSinscene.html. - Erstelle den Cast + Set aus
blockkit.js/props.js(siehe Code). Verwenderoom(),chair(),bench(),crowd()wieder; baue neue Requisiten mitbox()in der Palette oben. - Überprüfe zuerst Standbilder (günstig):
node render.cjs scene.html outstills 1,4,7,...; schau dir Nahaufnahmen jedes benannten Charakters an, einen Gang, eine Geste, eine Weite. Korrigiere vor dem Rendern des Videos. - Video rendern (etwa 1-1,7 s/Frame bei 720x1280 auf 2 Kernen, also 40 s entspricht etwa 15-25 min; führe Chunks parallel und im Hintergrund aus):
npm i [email protected] @fontsource/poppins playwright # falls nicht bereits geschehen; playwright global für 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 chunks à 30 s; warten auf beide
printf "file 'part0.mp4'\nfile 'part1.mp4'\n" > list.txt && ffmpeg -y -f concat -safe 0 -i list.txt -c copy silent.mp4
- Audio + Mux (durchgehender, leiser Teppich unter der Stimme, etwa -14 LUFS):
D=130 # >= Videolänge
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
- QC vor der Auslieferung: (a) Kontaktbogen, ein Frame alle ~2,6 s, Charaktere/Untertitel prüfen; (b) Bewegungsprüfung, mittlere Framedifferenz auf einem herunterskalierten Graustrom, erwarte 0 statische Frames:
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),'frames; static:',(d<.02).sum(),'; spikes(cuts):',(d>14).sum())
E
(c) Lautstärke ffmpeg -i final.mp4 -af ebur128 -f null - (etwa -14 LUFS), (d) Untertitel passen zur Sprache.
Ausliefern mit SendUserFile (mp4 + ein Cover-Standbild).
5. Setup
mkdir -p work && cd work && npm init -y >/dev/null && npm i [email protected] @fontsource/poppins
# Erstelle die folgenden Dateien in diesem Ordner: blockkit.js props.js captions.js render.cjs lipsync.py scene.html
# (oder kopiere die fertigen Dateien aus dem kit/-Ordner dieses Repos, der auch Standbilder und Beispiele enthält)