film-engine: Filme aus Code, Frame für Frame
Wo das Kit lebt: Führe jeden Befehl dieses Skills aus dem Ordner
reels-makeraus (dem Klon von github.com/thativanng/reels-maker, dertemplate/undtools/enthält). Falls das Projekt keinen hat, richte es zuerst gemäß der SETUP.md dieses Repos ein.
Die Idee in einem Absatz
Ein Film ist hier eine HTML-Seite mit einer Regel: window.seek(t) muss genau den Frame für die Zeit t zeichnen, jedes Mal, allein aus t. Nichts im Bild darf eine echte Uhr lesen. Ein Renderer durchläuft dann t mit 30 fps, macht Screenshots jedes Frames in 2x und leitet sie an ffmpeg weiter. Da die Seite deterministisch ist, stimmt ein erneutes Rendern überein (im Durchschnitt auf einen Bruchteil einer Stufe; GPU-Rauschen kann einzelne Pixel um bis zu etwa 60 Stufen verschieben), ein Standbild bei 17,3 s ist der Frame, den das Video zeigen wird, und jede Prüfung (film-audit) kann jeden Moment betrachten, ohne den Film abzuspielen.
Der Weg (keine Station überspringen)
- Briefing und Look zuerst. reel-intake endet mit einem unterschriebenen Briefing und Storyboard (Format, Länge, Ton, was nie erscheint); reel-style endet mit einem ausgewählten Look. Öffne die Build-Schritte dieses Skills nicht ohne beides.
- Storyboard. reel-intake schreibt es (Zeit · Text auf dem Bildschirm · Ton und Untertitel · Begründung; references/brief.md), und die Person unterzeichnet es, bevor du baust. Ein Stil mit einem Banner fügt eine Banner-Spalte hinzu.
- Teststreifen. Baue etwa die ersten 10 s im gewählten Look (ein Walk-Stil-Film braucht vielleicht 15–30 s, um ein Kapitel zu zeigen), erstelle Standbilder, bekomme ein Ja. Ein vollständiger Build mit einem nicht unterschriebenen Look ist verschwendete Arbeit.
- Baue den vollständigen Film (das Style-Pack sagt, wie Szenen gebaut werden).
- Tore (film-audit): die Kollisionsprüfung bei 40 fps, die Pixelproben, dann ein unabhängiger Auditor. Nichts geht zur Person vorher.
- Render:
node tools/render.mjs --page reels/<name>/page.html --out reels/<name>/out/film-silent.mp4(2x Frames, mit lanczos herunterskaliert, keine Farb-Tags). - Audio:
python3 template/audio.py out/film.wav(eine Cue-Liste auf dem gleichen Takt wie der Film). - Fertigstellung:
zsh tools/mux.sh out/film-silent.mp4 out/film.wav out/film.mp4. Das ergibt −14 LUFS, einen Limiter, eine ungetaggte H.264-Datei und eine Telefonvorschau in derselben Form wie der Film (kurze Seite 720 px). - Cover:
node tools/cover.mjs --page reels/<name>/page.html --out reels/<name>/out/cover.jpg. Eine Seite mit einem Cover-Modus (der Starter?cover, aus configcover) zeichnet das entworfene Standbild; jede andere Seite gibt den Frame beiT=. Es wird in der eigenen Form des Films gespeichert und unter 2 MB gehalten, plus der mittlere 3:4-Zuschnitt für einen 9:16-Film (das Instagram-Grid, gemeldet). LinkedIn akzeptiert ein Cover nur zum Zeitpunkt des Postens: danach kann nur noch der Text eines Beitrags bearbeitet werden (LinkedIn Hilfe, „Geteilten Beitrag bearbeiten“, https://www.linkedin.com/help/linkedin/answer/a522811). Die Thumbnail-Spezifikation, JPG oder PNG in der Form des Videos und maximal 2 MB, stammt aus LinkedIn-Hilfe, zitiert von einer Suchzusammenfassung; die Seite selbst würde nicht laden, also behandle sie als unbestätigt. - Sende zuerst die Telefonvorschau, dann die vollständige Datei und das Cover.
Der Seitenvertrag
window.seek(T): zeichnet Frame T. Es ist der einzige Ort, der irgendetwas animiert.window.ready: ein Promise, das nach Schriftarten, Texturen und dem ersten Frame erfüllt wird.window.DUR: die Länge des Films in Sekunden.window.FILM = { w, h, format, safe }: die Framegröße und die freizuhaltende Zone in Pixeln. Jedes Werkzeug dimensioniert sein Fenster daraus, also besitzt die Seite ihr Format.- Optional:
window.cover()zeichnet das Cover anstelle eines Frames, undFILM.coverAtbenennt den Moment, auf dem es sitzt (tools/cover.mjs verwendet beides; ohne sie nimmt es den Frame beiT=). window.collide(T)undwindow._dbg: Hooks für film-audit (siehe dieses Skill).- Die Tools bedienen den Kit-Ordner über http selbst (ES-Module laden nicht von
file://). Um eine Seite live im Browser zu betrachten,npm run serveausführen und http://127.0.0.1:8765/template/starter.html öffnen.
Formate, der Starter und andere Sprachen
template/formats.jsonenthält die vier Größen (9:16 1080×1920 · 4:5 1080×1350 · 1:1 1080×1080 · 16:9 1920×1080), die Plattform→Format-Zuordnung und die freizuhaltende Zone jeder Plattform mit ihrer Quelle. Die Formatzeile des Briefings wählt daraus.- Jeder Reel lebt in seinem eigenen Ordner,
reels/<name>/(gitignored): sein Briefing, Storyboard,page.html(eine Kopie des Starters oder von film.html), seinereel.jsonodercareer.jsonund seinout/. Seiten laden Schriftarten und formats.json aus dem Kit-Stamm, also funktioniert eine Kopie von dort. Weise die Tools darauf hin:PAGE=reels/<name>/page.html. template/starter.html+template/reel.jsonist das Grundgerüst für jeden Look außer dem Voxel-Walk. Seine Wörter und Zeitangaben leben in der Konfiguration, es dimensioniert sich selbst über?fmt=oder die Konfiguration und hält jedes Wort innerhalb der freizuhaltenden Zone der Plattform.?guideszeigt die abgedeckten Zonen in Rot an, nur zur Überprüfung; niemals damit rendern. Es ist ein Grundgerüst, kein Look: reel-style schreibt seine Tokens und sein Layout neu.template/film.html(die Voxel-Vorlage) ist nur 4:5, und sie verzichtet auf freizuhaltende Zonen (safeist 0): Ihr Banner und Untertitel sitzen absichtlich nahe den Rändern, also besteht diesafe-Prüfung dort immer und das Auge des Auditors ist die Kontrolle. Ihre Kameras sind auf 4:5 abgestimmt; ein anderes Verhältnis bedeutet, sie neu abzustimmen und jedes Tor erneut durchzulaufen.- Chinesisch, Japanisch, Koreanisch: Der Starter lädt Noto Sans TC von Google Fonts zur Renderzeit (nur die Zeichen, die die Konfiguration verwendet) und setzt CJK-Schrift auf etwa 0,72× der lateinischen Größe (ihre Zeichen sind volle em breit). Schreibe
\n, wo eine Zeile umbrechen soll: Chinesisch bricht zwischen beliebigen Zeichen, also braucht eine lange Zeile einen von dir gewählten Umbruch.template/reel.zh.jsonist ein ausgearbeitetes Beispiel. Jede andere Schrift braucht ihre eigene Schriftart im Stack.
Takt und Timing
- Ein Film-Takt steuert die Szenen, die Kamera, das Banner, die Untertitel und den Ton. Wenn du später einen Beat einfügst, ohne alles andere neu zu takten, verwende eine Neuzuordnung: Der Szenen-Takt pausiert an der Einfügestelle, während die Overlays den echten Takt behalten:
Das Audio-Skript braucht dieselbe Tabelle: Schreibe einconst INS = [[24.0, 5.0, 'detail']]; // bei Szenenzeit 24.0 einen 5 s langen eingefügten Beat abspielen function remap(T) { let sh = 0; for (const [a, len, id] of INS) { if (T < a + sh) return { t: T - sh, ins: null }; if (T < a + sh + len) return { t: a, ins: id, u: T - sh - a, len }; sh += len; } return { t: T - sh, ins: null }; }RT(t), das die eingefügten Längen zu einer Szenenzeit addiert, und platziere jeden Cue beiRT(t), sonst driftet jeder Cue nach dem Einfügen. (Die Vorlage hat keine Einfügungen, also braucht ihre audio.py keine.) - Easing:
eiofür Kamerabewegungen,eofür Dinge, die ankommen,trap(Trapezgeschwindigkeit) für Gehen und Fahren, damit ein Beweger nicht an den Enden „einschnappt“. - Kamera-Keyframes sind absolut
A(pos, target)oder folgenF(offset, targetOffset)vom Bodenpunkt des Helden aus. Mische zwischen Keyframes miteio. Halte auf einem wichtigen Beat; ein Shot, der sich ständig bewegt, wirkt nervös.
Die Overlays (im DOM, über der Leinwand)
- Banner: Jahr, dann Firma, dann die Rollen-Pille, plus eine Zeitleiste, deren Punkt über dem aktuellen Jahr atmet. Das Banner ändert sich, wenn die neue Szene AUF DEM BILDSCHIRM ist (ihr Set ist erschienen, das Outfit hat sich geändert, der Held ist angekommen), nie, wenn der Code beginnt, sie zu bauen. Überprüfe dies auf einem Frame-Blatt bei ±1 s um jede Änderung.
- Untertitel: eine Zeile Geschichte pro Beat, mit der Schlüsselphrase in fettem Akzent. Verwende
text-wrap: balance. Gib einem Leser etwa 3 Wörter pro Sekunde und blende einen Untertitel aus, bevor eine schnelle Kamerabewegung das Subjekt dahinter freigibt. - Eine Sprechblase, die einer Figur folgt: Projiziere den Kopfpunkt und halte die Blase ≥24 px innerhalb des Frames.
- Die Endkarte: Name, ein Slogan, worin sie gut sind, ein Zitat. Die Szene wird dahinter unscharf.
Audio
- Cues werden auf dem Film-Takt geschrieben, neben dem Moment, zu dem sie gehören (Schritte pro Gehbein, ein Glockenton, wenn ein Ding erscheint, ein dumpfer Schlag, wenn es landet).
- Keine Wisch-Sounds bei Übergängen; sie wirken billig. Verwende Glockentöne, Ticks und weiche Einschläge.
- Das Standard-Bett in
template/audio.pywird aus Sinuswellen erzeugt, also ist es überall kostenlos nutzbar. Wenn jemand einen KI-generierten Track mitbringt, überprüfe seine Lizenz: Die Gewichte einiger Modelle erlauben nur nicht-kommerzielle Nutzung. - Lautstärke: Two-Pass-Loudnorm auf −14 LUFS (messen, dann linear anwenden), dann ein Limiter. Ein Durchlauf überschießt.
Render-Fakten, die uns Tage gekostet haben (mehr in references/gotchas.md)
- In 2x rendern und herunterskalieren. EffectComposer entfernt das Canvas-Anti-Aliasing, also hat ein 1x-Render überall gezackte Kanten.
- Keine Farb-Tags auf dem MP4. Eine JPEG-Pipe taggt es als Vollbereich, und einige Player verschieben dann die Farben.
setparams=...=unknownim Filter. - Zwei Gesichter in einer Ebene flackern (Z-Fighting). Gib dem vorderen einen Tiefenoffset.
- Schriftarten müssen geladen werden (
document.fonts.load), bevor Canvas-Text gezeichnet wird, sonst fällt es lautlos zurück. - GPU an im Headless-Chromium (
--use-angle=metalauf macOS). Ohne sie ist ein Render etwa 10x langsamer.
Arbeiten ohne Aufforderung bei jedem Schritt
Die Person hat den Erlaubnisregeln des Kits einmal zugestimmt (SETUP.md Schritt 4); bleibe innerhalb dieser, sonst werden sie immer wieder gefragt:
- Schreibe und ändere Dateien mit den Schreib- und Bearbeitungswerkzeugen: Seiten, reel.json, Briefing, Storyboard, AUDIT.md. Erzeuge sie nicht mit einem Python- oder Shell-Skript, einem Heredoc oder
sed -i; jedes davon ist eine Genehmigungsaufforderung. - Messe mit den Werkzeugen des Kits (Stills, Probe, Sheet, Collide, Render, Cover), nicht mit einmaligen
python3 -c-Schnipseln. - Kein
mkdir, Schleifen oder Ketten nötig: Jedes Werkzeug erstellt seinen eigenen Ausgabeordner und nimmt eine Liste von Zeiten in einem Aufruf entgegen (stills.mjs … 1,5,6,12). - Eigene Skripte eines Reels (sein Tonskript, ein Helfer) leben in
reels/<name>/und laufen alspython3 reels/<name>/audio.pyodernode reels/<name>/x.mjs, was die Regeln abdecken.
Befehle
Führe jedes Werkzeug als einen einfachen Befehl aus (Flags, kein führendes VAR=…, keine Schleifen, keine &&-Ketten). Dann decken die Erlaubnisregeln der Person (node tools/*) es ab und sie werden nicht gebeten, jeden Schritt zu genehmigen. Die Umgebungsvariablen funktionieren weiterhin für Skripte.
npm run doctor # prüft diesen Rechner zuerst
npm run demo # der 12-Sekunden-Beweis: out/demo/
node tools/stills.mjs "2,8.5,15" # 2x Standbilder → out/stills (die Voxel-Vorlage)
node tools/stills.mjs --page template/starter.html --format 16:9 1.6 # der Starter mit einem anderen Verhältnis
node tools/stills.mjs --page template/starter.html --cfg reel.zh.json 1.6 # eine andere Konfiguration (hier: Chinesisch)
node tools/stills.mjs --dsf 1 --out out/chk 2,8.5 # schnelle 1x-Frames für ein Blatt
python3 tools/sheet.py out/chk/sheet.jpg 6 300 out/chk/t*.jpg
node tools/render.mjs --page reels/<name>/page.html --out reels/<name>/out/film-silent.mp4
python3 template/audio.py out/film.wav
zsh tools/mux.sh out/film-silent.mp4 out/film.wav out/film.mp4