Low-Poly PS1-Kurzfilme
Alles ist Code: Claude schreibt ein Blender-Skript pro Kurzfilm, rendert es auf dem Rechner des Benutzers, betrachtet die Frames, korrigiert sie und erstellt dann Sound und Untertitel. Kein Videogenerator.
Bewährt auf examples/lift-rocket/ ("Was wäre, wenn Aufzüge so schnell wie Raketen wären?", 34 s).
Lies dieses Beispiel, bevor du einen neuen Kurzfilm schreibst: Es ist die Vorlage.
Voraussetzungen (zuerst prüfen, ggf. installieren)
- Blender 4.2+ (getestet mit 5.3). Finde es:
blenderim PATH, sonstC:\Programme\Blender Foundation\<ver>\blender.exe//Applications/Blender.app/Contents/MacOS/Blender. - Python 3.10+ mit
numpy pillow soundfile faster-whisper;ffmpegim PATH. - Charaktere: Quaternius "Ultimate Modular Men" (CC0, kostenlos, quaternius.com). Entpacke sie in
assets/quaternius-modular-men/oder setzeLOWPOLY_PACKauf den OrdnerIndividual Characters/Blends. - Eine TTS-Stimme (ElevenLabs oder ein anderes Tool des Benutzers). Optional: das
earshotMCP zur Soundkontrolle.
Die Pipeline (ein Ordner pro Kurzfilm, kopiere examples/lift-rocket/ als Start)
| # | Schritt | Datei / Befehl | Fertig, wenn |
|---|---|---|---|
| 1 | Skript | script.md | Hakenfrage + Kette von Konsequenzen, jede schlimmer und lustiger + echte Zahlen + trockener Pointen. 60-110 Wörter für 30-40 s. Jede Zahl mit einer Quelle überprüft und in die Faktenliste geschrieben. |
| 2 | Stimme | vo/takeA.mp3 | 2 Takes, wähle den mit sauberen Wortzeitangaben. Energetische Creator-Stimme, keine ruhige (ruhig = Feedback "langweilig"). |
| 3 | Wortzeiten | python ../../scripts/words.py vo/takeA.mp3 | takeA_words.json hat keine Wörter mit Länge null. |
| 4 | Shot-Liste | Tabelle in script.md | Ein Shot pro VO-Phrase, alle 1-3 s ein neues Bild, jeder Shot an eine Wortzeit gebunden. |
| 5 | Szene | build.py (Blender) | blender -b -P build.py -- frames --still 30,120,... rendert Standbilder; betrachte jedes Standbild vor dem vollständigen Rendern. |
| 6 | Vorschau + Notizen | python ../../scripts/preview.py . render dann serve | Benutzer hat keine offenen Notizen (siehe Vorschau-Schleife). |
| 7 | Rendern | blender -b -P build.py -- frames | 360×640 EEVEE PNGs, f_0001.png... |
| 8 | Sound | python build_mix.py | SFX auf Bildframes platziert, VO darüber, Lo-Fi-Bett darunter, Ducking. Schreibt mix_raw.wav + cues.json. |
| 9 | Nachbearbeitung | python post.py | PS1-Dither, Effekte, Wort-Highlight-Untertitel, Statistik-Callouts -> out/ mit 1080×1920. |
| 10 | Muxen | siehe unten | short.mp4, dann messen und Synchronisation prüfen. |
Frame-Regel überall: 24 fps, Frame 1 = t 0, F = lambda t: int(round(t * 24)) + 1.
Muxen:
ffmpeg -y -framerate 24 -i out/f_%04d.png -i mix_raw.wav -c:v libx264 -crf 16 -pix_fmt yuv420p \
-af "loudnorm=I=-15:TP=-1.5:LRA=11,aresample=48000" -c:a aac -b:a 192k -shortest short.mp4
Stimme später austauschen, ohne das Bild neu zu schneiden:
python ../../scripts/words.py vo/takeB.mp3 dann python ../../scripts/align.py vo/takeA_words.json vo/takeB.mp3 --end 34 (--end = wo das VO-Bett endet; Ausgabe ist end + 2 s lang. Ohne: letztes Wort + 1,5 s, was eine kürzere Datei als das Bild ergibt)
-> takeB_aligned.wav + takeB_aligned_words.json. Weise build_mix.py (VO-Umgebungsvariable) und post.py darauf.
Vorschau-Schleife (vor dem vollständigen Rendern)
Schnelle Low-Res-Vorschau im Browser, der Benutzer hinterlässt Notizen auf der Timeline, Claude korrigiert
und rendert nur die Shots neu, deren Inhalt sich geändert hat. Shots = die Kameramarker in build.py.
| Schritt | Befehl (vom Kurzfilm-Ordner aus) |
|---|---|
| Vorschau rendern | python ../../scripts/preview.py . render (erster Durchlauf: jeder Shot; später: nur geänderte Shots) |
| Notizenseite öffnen | python ../../scripts/preview.py . serve -> http://127.0.0.1:8765 |
| Notizen lesen | python ../../scripts/preview.py . notes (gruppiert nach Shot, mit Framenummern) |
| Nach Korrektur | build.py bearbeiten, render erneut ausführen, dann python ../../scripts/preview.py . done 3 5 --reply "was du geändert hast" |
| Shots erzwingen | render --shots c_press,c_slam oder render --all |
build.pymuss--blend-onlyakzeptieren (Szene erstellen, speichern, nicht rendern). Siehe das Beispiel.- Änderungserkennung: Jeder Vorschau-Frame erhält einen Fingerabdruck (aktive Kamera, jede sichtbare Objekt- Transformation, Armature-Pose, Gesichtsaustausch, Materialien, Lichter, Mesh-Daten). Ein Shot wird nur neu gerendert, wenn sich einer seiner Frames geändert hat. Unveränderte Shots behalten ihre alten Frames.
- Einstellungen in
preview.json(optional):scale50 (%),step2 (jeder 2. Frame),samples1,audio(Standardmix_raw.wav, sonst die VO),final= die Befehle der vollständigen Pipeline. Der Button "Genehmigen & Final rendern" der Seite führt sie aus;notesgibt GENEHMIGT aus, wenn der Benutzer ihn gedrückt hat. - Korrigiere die Notizen eine nach der anderen, antworte auf jede, rendere neu und sage dem Benutzer, dass er nicht aktualisieren muss: Die Seite lädt das Video von selbst neu. Wiederholte Notizen derselben Art werden zu einer Regel in dieser Datei.
- Gemessen am Lift-Raketen-Beispiel (RTX 3070 Ti Laptop, 820 Frames): erste Vorschau 76 s, ein Shot geändert 13 s, nichts geändert 12 s. Der Großteil der Renderzeit eines Frames ist fixer Overhead, daher hilft eine niedrigere Auflösung weniger als das Rendern weniger Frames.
Kit-API (scripts/lowpoly_kit.py, innerhalb von Blender importieren)
load_character(scn, "Suit")->(armature, meshes, actions). Charaktere: Adventurer, Beach, Casual_2, Casual_Hoodie, Farmer, King, Punk, Spacesuit, Suit, Swat, Worker.- Aktionen (am Platz, 24): Death, HitRecieve, HitRecieve_2, Idle, Idle_Neutral, Interact, Kick_Left/Right, Punch_Left/Right, Roll, Run, Run_Back/Left/Right, Walk, Wave, Gun_Shoot, Idle_Gun*, Idle_Sword, Sword_Slash, Run_Shoot.
add_faces(scn, arm, head, tag, kinds, spec, drop)vor dem Bewegen der Armature. Kinds: neutral, blink, side, shock, grimace, squint, dazed, xeyes.droplöscht die 3D-Augen/Brauen des Packs.show_faces(faces, [(frame, kind), ...]): harte Schnitte, jedes Gesicht hält bis zum nächsten.nla(arm, [(action, start, end, blend[, influence]), ...]): spätere Einträge auf höheren Tracks.drive_root(arm, [(frame, x, y, yaw, ease), ...], last): yaw 0 zeigt in -Y-Richtung (die Standardkamera).new_cam,aim,yaw_to,ps1_render_settings(scn, outdir).- Sets:
scripts/set_lift_lobby.py(ausführen mitBUILD_ONLY=True, es gibtbox,uv_world,matzurück). Neue Sets folgen dem gleichen Muster: Boxen mit Pixeltexturen (interpolation = "Closest").
Look-Regeln
- Rendern mit 360×640,
filter_size 0, AgX Punchy, flat shading; Nachbearbeitung: 15-Bit-Farbe + Bayer-Dither, ×3 nächster Nachbar. - Gesichter sind flache 32×28 Pixel große Decals, die mit harten Schnitten ausgetauscht werden; animiere ein Gesicht niemals weich.
- Quetschen unter Kraft durch Keying der Armature-Skalierung (z runter, xy hoch). Comedy-Schläge: schnell rein (2-3 Frames), halten.
- Untertitel: 1-3 Wörter, schwere Sans, weiß mit schwarzer Kontur, aktuelles Wort gelb, unteres Drittel (y 1300 von 1920).
- Statistik-Callouts: rote abgerundete Box, oberes Drittel, Einblendung über 4 Frames, eine pro echter Zahl.
Sound-Regeln
- Jeder SFX wird so platziert, dass sein Peak auf dem Bildframe landet (
at(name, frame, db, peak=...)). - Die Stimme muss gewinnen: VO auf 0 dBFS normalisiert, SFX-Bus -11 dB, Musik -7 dB, plus Sidechain-Ducking während der Stimme (SFX -5 dB, Musik -9 dB). v1 des Lift-Kurzfilms hatte SFX bei -3 und Musik bei 0: "Ich kann die Voiceover nicht hören".
- Schneide die Musik auf dem großen Stopp-Beat ab, bringe sie danach zurück. Stille verkauft den Gag.
- Master: loudnorm -15 LUFS, True Peak -1,5 dBTP.
- Prüfe: earshot
timelineauf der mp4 vscues.json(Ziel: jeder Cue innerhalb von 100 ms),measurefür LUFS.
Bereits gefundene Bugs (nicht wiederholen)
- Pack-Meshes werden render-versteckt ausgeliefert:
hide_render = False(das Kit macht das). - Tür-Meshes sitzen bereits an ihrem geschlossenen Platz: animiere nur den Offset, sonst schließen sie mit 0,7 m Lücke.
- Eine Decke unter ~2,75 m verdeckt Wanddisplays vor der Kamera in einem Aufzugs-großen Fahrzeug.
- Ein Charakter, der noch nicht gesehen werden darf: keye
location.z = -50bis zu seinem Shot. - NLA-Einzelaktion, die in eine Halteaktion übergeht: setze den Clip
blend_out = 0, sonst hüpft die Pose einen Frame lang. - Rückwärtsshots werden schwarz (unbeleuchtete Decke): füge ein vorderes Licht und etwas Deckenemission hinzu. Rendere den Rückwärtsshot früh.
- faster-whisper auf mp3 kann mit einem PyAV-Konflikt brechen:
words.pydekodiert zuerst mit ffmpeg. - Ein Satz, der bei -0,04 s beginnt, ergibt einen leeren Slice in der Ausrichtung: abfangen mit
max(0.0, ...)(erledigt).
Kosten (Lift-Kurzfilm, RTX 3070 Ti Laptop)
Rendern 820 Frames in 92 s, Nachbearbeitung 17 s, $0. Charaktere, SFX, Musik: $0 (CC0 + synthetisch im Code). Stimme: ElevenLabs etwa $0,10 pro Take, 2 Takes pro Kurzfilm.
Bevor du übergibst
- Betrachte ein Kontaktblatt der finalen Frames (alle ~2 s), nicht nur die Standbilder.
- Messe die Lautstärke, prüfe die Synchronisation, prüfe die Untertitel gegen den VO-Text (Rechtschreibkorrekturen kommen in post.py
FIX). - Teile dem Benutzer mit, was gemessen wurde und was nicht von einem Menschen gehört wurde.