Communitygithub.com

promo-video – Zero-Cost Promo Pipeline (Werbevideo-Pipeline ohn…

Erstelle aus jedem Repository (Web-App, Bildergalerie oder CLI-Tool) mit null Kosten ein Werbevideo, einen Trailer oder eine Social-Media-Anzeige – inklusive Screen-Recording, Ken-Burns-Montage oder terminalbasierter Erklärvideos mit Sprachausgabe, Untertiteln und Handlungsaufforderung (CTA). Für Claude Code entwickelt.

Was ist promo-video – Zero-Cost Promo Pipeline (Werbevideo-Pipeline ohn…?

Dieses Skill (promo-video) verwandelt jedes Repository in ein professionelles Werbevideo – ohne einen Cent für APIs auszugeben. Es unterstützt drei Aufnahmemodi: Screen-Recording für bewegte Web-Apps/Spiele (Modus A), Ken-Burns-Montage für Bildergalerien (Modus B) und eine Terminal-Erklärung für CLI-Bibliotheken/Backends (Modus C). Die Pipeline ist in vier Phasen unterteilt: Aufnahme (Capture) mit Playwright/xvfb, optionale Sprachausgabe mit edge-tts, Qualitätsprüfung durch Gemini (z. B. Betonung/Stimmlage) und finale Komposition mit Branding-Elementen (Logo, Schrifttafel, CTA, QR-Code) in verschiedene Seitenverhältnisse (16:9, 9:16, 1:1, 4:5). Die Besonderheit liegt in der Sammlung von Erfahrungswerten („Gotchas“), die das Tool extrem robust gegen typische Fallstricke macht – wie falsche Skalierung, flackernde Übergänge oder asynchrone Audio-Video-Synchronisation. Ursprünglich für Claude Code entwickelt, ist es auch mit anderen KI-gestützten DevOps-Tools kompatibel.

Funktioniert mit✓Claude Code✓Codex CLI~Cursor✓Gemini CLI
npx skills add yazelin/promo-video-skill

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

promo-video – Kostenlose Werbevideo-/Anzeigen-Pipeline

Verwandle jedes Repository in einen Trailer oder eine Social-Media-Anzeige. Die Kernidee: Wenn die App selbst eine filmische Kamerafahrt macht, zeichne sie auf – generiere kein „Video“. Wenn es sich um ein Bildprojekt handelt, lade ausgewählte Bilder herunter und verwende Ken Burns. Der Rest (Sprachausgabe, Qualitätskontrolle, Komposition, Branding, CTA) ist für beide Fälle gleich. In der Praxis getestet: battlefield-editor/tools/trailer (Screen-Recording) + catime-Anzeige „Katze, die die Welt liest“ (Ken Burns + Nachrichten→Bild-Enthüllung) + „Grülich und Herr Schwarzenloch“ – komplettes Durchspielen aller sieben Kapitel (vertonte Variante, 60 Minuten, 700 Sprachausgaben automatisch abgespielt: https://youtu.be/J9OMebCjr9Y).

Entscheidungsbaum: Wähle zuerst den Aufnahme-Modus

  • Bewegte App (3D/Canvas/automatische Kamerafahrt, mit Auto-Play) → MODUS A Bildschirmaufnahme (capture-app.mjs).
  • Bildergalerie / Bilder-Feed (KI-generierte Bilder, Galerie) → MODUS B Ken-Burns-Montage (ausgewählte Bilder herunterladen + kenburns.sh).
  • App, die eigene Töne ausgibt (Vertonte Variante von Modus A; was und wie lange abgespielt wird, wird von der App selbst bestimmt, es liegt keine vollständige Tonspur vor) → Lasse recordVideo weg und verwende stattdessen ffmpeg, um gleichzeitig Bildschirm und Audio aufzuzeichnen. Siehe Abschnitt „Wenn die App eigene Töne ausgibt: Echte Aufnahme“. Wenn die gesamte Tonspur vorliegt, nicht diesen Weg gehen; das spätere Muxen liefert bessere Audioqualität (GOTCHA 24).
  • Nicht-visuelles Repository (CLI / Bibliothek / API / Backend / Daten-Pipeline, kein Bildschirm zum Aufnehmen) → MODUS C Terminal/Erklärung (term-player.html + capture-term.mjs): Erstelle aus „Befehl + tatsächliche Ausgabe“ eine Terminal-Aufnahme mit Schreibanimation. Für Entwicklungstools ist eine „Voice-Over-Lektion“ besser als ein „Quick-Cut-Trailer“ (siehe GOTCHA 22): Struktur = Schmerzpunkt-Hook → Schritt für Schritt (Installation → Ausführung → Ausgabe verstehen → Erweiterte Befehle) + edge-tts Voice-Over, das jeden Schritt erklärt + Schritt-für-Schritt-Untertitel → CTA. Die Synchronisation des Voice-Over erfolgt über die window.__STEPLOG-Variable von term-player.html (Sekunden, in denen die Ausgabe jedes Schritts erscheint). Die Komposition fügt jeden Voice-Over-Satz mit einer Verzögerung (adelay) zum entsprechenden Schritt hinzu. Praxis: WIP-Repository-übergreifendes Status-CLI-Lehrvideo (~60s, Hintergrundmusik auf 0,13 reduziert, um das Voice-Over in den Vordergrund zu stellen).
  • Best-of-Kurzvideo aus Live-Mitschnitt (9:16 Reels/Shorts) → MODUS E Vorlagen-Aufnahme (short-9x16.html + capture-short.mjs): Die course-pipeline extrahiert zuerst die besten Zitate aus dem Transkript und schneidet 16:9-Clips. Hier wird sie in eine 9:16-HTML-Vorlage eingebettet und dann aufgezeichnet. Warum HTML und nicht ffmpeg-Filter: Zuschneiden funktioniert auch mit ffmpeg, aber Schlüsselwort-Animationen sind in CSS mit zehn Zeilen erledigt, während es mit dem ASS-Untertitelformat die Hölle ist. Das Layout anzupassen erfordert eine Änderung des CSS und einen Blick darauf, ohne blind einen filtergraph ändern zu müssen. Abgrenzung: die course-pipeline schneidet, promo-video verpackt. Die Schnittstelle ist „ein fertig geschnittener mp4 + Zeitstempel auf der finalen Timeline“.
  • Hybrid: Screen-Recording als Hauptteil, Ken Burns / Terminal als Ergänzung – alles möglich.

Vier Phasen

  1. Aufnahme (Capture) — MODUS A: xvfb-run headed Chromium starten (WebGL erforderlich), Playwright recordVideo verwendet, um den automatisch abgespielten Höhepunkt aufzuzeichnen; MODUS E läuft headless (siehe GOTCHA 47). MODUS B: Quellenliste abrufen, auswählen, herunterladen.
  2. Sprachausgabe (Narration) (optional) — edge-tts zh-TW-YunJheNeural --rate=-5% erzeugt das Voice-Over.
  3. Qualitätskontrolle (Verify) (optional) — Gemini Pro hört sich das Transkript an und prüft die Betonung; Gemini Flash wählt die Hintergrundmusik aus.
  4. Komposition (Compose) — brand.py erstellt Branding-Materialien (Schrifttafeln, Ecklogos, CTAs, QR-Codes). ffmpeg überlagert Untertitel, verbindet Clips, mischt Audio und erstellt mehrere Seitenverhältnisse sowie eine komprimierte Version.

Hier die sechs Vorlagen (einfach die Konfiguration oben / Parameter ändern)

  • brand.py — PIL: Cover/Contain-aspekt-treuer Kompositor, Schrifttafel, dauerhaftes Ecklogo, CTA-Karte, Mini-Galerie, QR-Code (über API).
  • kenburns.sh — Ruckelfreier Ken-Burns-Clip + Text an Bildkanten + concat + Audiomischung (Hauptwerkzeug für MODUS B).
  • capture-app.mjs — Playwright+xvfb Screen-Recording (MODUS A); mit integriertem HTTP-Server, ausgeblendetem HUD, Sprung zu Knotenpunkten per Klick / Tastatur-gesteuerten Spielen.
  • term-player.html + capture-term.mjs — MODUS C Terminal/Erklärung: HTML-Terminal-Player (Schreibanimation + farbige Ausgabe) + xvfb-Aufnahme. Kein asciinema/xterm nötig.
  • short-9x16.html + capture-short.mjs — MODUS E: 9:16-Vorlage (Folien zentriert, große Untertitel oben/unten, Zitate vergrößert und hüpfend, dauerhaftes Ecklogo, Fortschrittsbalken) + xvfb-Aufnahme + nachträgliches Muxen mit der Original-Audio-Spur. Die Folie hat automatische Kamerafahrt: Sie misst die Position des Textes auf dem Bildschirm, ändert alle 2–3 Sekunden die Komposition, zoomt beim Aussprechen eines Schlüsselworts heran und hellt auf (GOTCHAS 32–37). Für erneute Aufnahmen --tag v2 verwenden, um den Dateinamen zu ändern – die alte Version nicht überschreiben; sie dient als Vergleichsbasis. Nach der Aufnahme werden automatisch „Helligkeitsereignisse pro Sekunde“ und „Lautstärke des fertigen Produkts“ ausgegeben. Keine separate Überprüfung erforderlich.
  • relevel.py — Für bereits ausgelieferte Videos, um die Lautstärke nachträglich anzupassen: Nur Audio ändern, -c:v copy. Die Videostream-MD5 ist vorher und nachher identisch. Ein Clip dauert eine Sekunde. Keine erneute Aufnahme erforderlich. Bei neu aufgenommenen Videos nicht nötig (capture-short.mjs gleicht die Lautstärke bereits beim Muxen an).
  • audition.py — Gemini hört zu: music "<Zweck>" *.mp3 (durchsucht CC0-Musik und bewertet Eignung 0-10), tone "<Text>" v.mp3 (prüft Betonung des Voice-Over).

Wenn die App eigene Töne ausgibt: Echte Aufnahme

GOTCHA 24 beschreibt den Fall, dass die gesamte Tonspur vorliegt (Musikvideo, edge-tts Voice-Over): Das spätere Muxen mit der Originaldatei liefert die beste Audioqualität. Es gibt jedoch einen Fall, in dem dies nicht möglich ist – was und wie lange abgespielt wird, wird von der App selbst bestimmt. Praxis: Das vollständige Durchspielen eines Visual Novels, bei dem 700 Sprachdateien in chronologischer Reihenfolge der Handlung abgespielt werden und keine „vollständige Tonspur“ zum Synchronisieren vorliegt.

Dann die Playwright-recordVideo-Funktion komplett weglassen und stattdessen ffmpeg verwenden, um gleichzeitig Bildschirm und Audio aufzuzeichnen:

# 1. Einen separaten Audio-Empfänger öffnen, den Browser-Ton hineinleiten, andere Desktop-Töne nicht mischen
MOD=$(pactl load-module module-null-sink sink_name=cap)
# 2. Eigener X-Bildschirm
Xvfb :77 -screen 0 1280x808x24 -nolisten tcp &
# 3. Bildschirm + Ton gemeinsam aufnehmen, segmentiert schreiben (Absturz verliert nur ein Segment)
ffmpeg -f x11grab -draw_mouse 0 -framerate 25 -video_size 1280x808 -i :77.0 \
       -f pulse -i cap.monitor \
       -vf crop=1280:720:0:88 \
       -c:v libx264 -preset veryfast -crf 24 -pix_fmt yuv420p \
       -c:a aac -b:a 160k \
       -f segment -segment_time 600 -reset_timestamps 1 seg/part-%03d.mp4 &
# 4. Browser starten (DISPLAY=:77 PULSE_SINK=cap), nach Beenden concat der Segmente

Fünf in der Praxis aufgetretene Probleme:

  1. --kiosk funktioniert bei Playwright nicht; es verwaltet das Fenster selbst. Die Registerkartenleiste + Adressleiste sind fest 88 Pixel hoch. Den Bildschirm 88 Pixel größer öffnen und mit crop abschneiden. Den Cursor mit -draw_mouse 0 ausblenden.
  2. Nicht mit festen Sekunden umblättern. Jede Sprachaufnahme ist unterschiedlich lang. Zu kurzes Warten schneidet das Ende der Sprachausgabe ab, zu langes Warten verdoppelt die Länge des gesamten Videos. new Audio() abfangen, erst umblättern, wenn die Sprachausgabe beendet ist (diese Elemente sind nicht im DOM, querySelector findet sie nicht).
  3. Es muss eine Prüfung geben, ob „Inhalt voranschreitet“. Ohne diese wird eine nicht klickbare Karte bis zum Limit durchgeklickt, und ffmpeg meldet keinen Fehler: In der Praxis 58 Minuten aufgenommen, Datei normal, aber nur ein Viertel des Buchs durchgespielt. Das Kriterium ist „ob sich der Text auf dem Bildschirm ändert“, und leerer Text zählt ebenfalls als kein Fortschritt – der Endbildschirm enthält keinen Text, was zu weiteren 33 Minuten Aufnahme führte.
  4. Segmentiert schreiben (-f segment). Ein Absturz verliert nur ein Segment. Abschließend mit concat -c copy verlustfrei verbinden.
  5. Nach Abschluss vier Dinge prüfen, nicht nur, ob die Datei erstellt wurde: Gesamtlänge, Anzahl der besuchten Karten, Standardabweichung von Stichproben-Frames (nahe 0 = leerer Bildschirm), durchschnittliche Lautstärke mit volumedetect (Stille bedeutet, dass der Null-Sink nicht angeschlossen ist, wiederum ohne Fehlermeldung).

Praxis: glitch-vn/tools/capture/ (record.sh + play_through.mjs). Das Ergebnis ist das vollständige Durchspielen aller sieben Kapitel von „Glitch & Mr. Black Hole“, 60 Minuten, 700 Sprachdateien, einmal durchgelaufen ohne jemanden, der drückt: https://youtu.be/J9OMebCjr9Y

GOTCHAS – Dies ist das wertvollste Asset, vor jeder Ausführung prüfen

  1. Seitenverhältnis-Treue: Niemals scale=W:H für Bilder mit gemischtem Seitenverhältnis verwenden (führt zu Verzerrung). Hintergrund mit Cover (proportional vergrößern und zuschneiden), Hauptobjekt mit Contain (proportional verkleinert einfügen). ffmpeg-Skalierung nur als ganzzahliges Vielfaches (2x) oder mit force_original_aspect_ratio. Vor Abschluss „Original-Seitenverhältnis vs. Anzeige-Seitenverhältnis“ prüfen (Fehler <0,1 % bestehen).

  2. Ken-Burns-Ruckeln: Quelle zuerst 2x vergrößern, dann verkleinern (ganzzahlige Pixel-Sprünge werden zu Subpixel-Sprüngen) + Skalierung mit linearer Frame-Nummer z='1+K*on/(D-1)', niemals das akkumulierende Rundungs-z='min(zoom+inc,...)' verwenden.

  3. Zoompan-Frame-Explosion: Nicht -loop 1 -t (jeder Eingabe-Frame wird ×d multipliziert). Bei Einzelbild: -frames:v D + zoompan d=D.

  4. Text an Bildkanten: Bild zuerst vertikal zentrieren. Textposition = Mitte ± (Bildhöhe × zmax)/2 ± Abstand, für jedes Bild einzeln berechnen (PIL gibt die Höhe jedes Bildes als Sidecar aus). So sind horizontale Bilder nicht zu weit entfernt und Quetschungen werden vermieden.

  5. file://-Fetch wird blockiert: App verwendet fetch(?pkg=)/JSON laden → python3 -m http.server starten, um http zu verwenden (oder den integrierten Server von capture-app.mjs).

  6. Headless WebGL verliert Swiftshader/wird schwarz → xvfb-run -a --server-args="-screen 0 WxHx24" verwenden, um headed Chromium zu starten.

  7. ES-Modul-App: gotoPhase ist nicht im Window-Objekt → DOM-Knoten der Timeline #tlNodes .node[i].click() verwenden, nicht evaluate(gotoPhase).

  8. QR-Code: PEP668 verhindert Installation von qrcode → api.qrserver.com/v1/create-qr-code/?data=<öffentlicher-Link> (nur öffentliche Links); vor der Veröffentlichung mit dem Smartphone scannen und prüfen.

  9. edge-tts Homonyme: Zuerst umgehen („没 mò“ → Wort durch ein anderes ersetzen); Betonungsprüfung mit Gemini Pro nach Tonhöhenverlauf fragen, Whisper STT kann Betonung nicht prüfen (falsch ausgesprochen, aber gleiches Zeichen transkribiert).

  10. Hintergrundmusik: Gemini Flash durchsucht CC0-Musik, auf Stimmigkeit achten (Kriegsmusik nicht für eine Katzen-Anzeige verwenden); CC0-Quelle notieren.

  11. Mehrere Seitenverhältnisse: 16:9 = YouTube, 9:16 = Reels/Shorts/LINE, 1:1 & 4:5 = FB/IG-Newsfeed. MODUS A: jedes Seitenverhältnis nativ neu aufnehmen (Hochformat-Viewport); MODUS B: jedes Seitenverhältnis neu komponieren.

  12. Dauerhaftes Ecklogo (Senderlogo): Marke + URL die ganze Zeit in der Ecke des Bildschirms (nicht nur auf der Endkarte), ein transparentes PNG über das gesamte Video legen.

  13. Werbung muss zum Handeln anregen: Erste 2 Sekunden Hook (stumm automatisch abspielen, erstes Bild/großer Text sofort sichtbar), eingebrannte Untertitel (FB stumm), CTA mit Grund „jetzt zu handeln“.

  14. Plattform-Details: FB-Video in einen Beitrag einbetten, Link in den ersten Kommentar setzen (um Reichweiten-Strafen zu vermeiden); +movflags +faststart; LINE-Inline-Wiedergabe über Album/Video-Button senden, nicht als „Datei“.

  15. Screenshots nach Ladeabschluss + jeden auf Leere prüfen: MODUS A/Galerie: Screenshots einzeln nicht mit fester Verzögerung erstellen (der langsame Screenshot erfasst den leeren „Ladevorgang“). Abfragen, bis der Lade-Indikator verschwindet; nach dem Screenshot jeden Zielbereich auf stdev prüfen (≈0 = leer/nicht gerendert), automatisch fehlende Screenshots erneut erstellen. Praxis: roll-formosa Miaoli hatte nach 1,8 s einen leeren Screenshot. Durch Wait-for-Load + stdev-Scan wurde der Fehler behoben.

  16. Tatsächlich vergrößern, nicht nur auf die Miniaturansicht vertrauen: Zur Überprüfung unbedingt Auflösung einzelner Bilder im Detail anzeigen (Zuschnitt / Unschärferänder / zugeschnittene Bereiche sind in der Miniaturansicht nicht sichtbar). Beim Messen von Koordinaten y/x-Gitter auf das Originalbild zeichnen. Praxis: Die Unterkante eines Wahrzeichens war um 55 Pixel abgeschnitten, die untere Hälfte war verschwommen – beides war in der Miniaturansicht nicht zu sehen, erst bei Vergrößerung und Gitterlinien wurde es erkannt.

  17. Interaktives Spiel ohne Auto-Play → Tastatureingaben selbst senden (Untermodus von MODUS A): Katamari/Action-Spiele mit Playwright keyboard.down/up steuern (Richtungstasten). Das Spiel beginnt oft erst ~10s nach dem Laden (precache/PREPARING): zuerst auf START warten (Button-Bereitschaft abfragen) → START klicken → dann Tastatureingaben senden. Ein blinder Bot bekommt möglicherweise nichts oder wächst nicht (roll-formosa horizontales Layout blieb bei 2 cm hängen) – das „optisch ergiebigste“ Fenster nehmen (Ball groß genug/viele Objekte) oder stattdessen das nächste Showcase verwenden.

  18. Gameplay schwer aufzunehmen → Showcase/Galerie als Material verwenden: Viele Apps haben eine „Objektsammlung/Vorschau“-Autoplay-Seite (z. B. preview.html?city=X), die den gesamten Inhalt ohne Spiel rendert – eine zuverlässige Montage-Quelle (roll-formosa 20 Städte). Diese Objekte auf dunklem Hintergrund, Hintergrund nicht unscharf machen (sonst entstehen Geisterbilder).

  19. „Bereit“-Bildschirm am Ende der Aufnahme: recordVideo zeichnet ab dem Laden auf. PREPARING→START ready erscheint im späteren Teil. Lang genug aufnehmen (bis START + noch 3 s warten), trimmen vom Ende den „ready“-Teil nehmen, nicht von Anfang (sonst wird der Ladevorgang erfasst).

  20. Mobiles Hochformat-Viewport ist oft besser: Das mobile Layout eines Spiels ist manchmal vollflächiger oder verhält sich sogar anders (roll-formosa Hochformat-Gameplay: Ball wächst, horizontales Layout bleibt hängen). Bei der nativen Neuaufnahme jedes Seitenverhältnisses prüfen, welches Viewport die beste Leistung zeigt.

  21. Präzise Texte: Vorstellung des Protagonisten/Objekts mit Eigenschaften/Eigenheiten, nicht mit dem Aussehen (catime-Cast). Die „Nachrichten→Bild“-Zeile in der Anzeige muss aus der Idee/Geschichte der Quelle abgeleitet werden, nicht erfunden (eine falsch zugeordnete Nachricht wäre peinlich).

  22. Trailer ≠ Tutorial, nicht das Format verwechseln: Visuelle Projekte (Spiele/Apps) sind für 15-20 s Quick-Cut-Trailer geeignet (teasen, Aufmerksamkeit erregen). Aber CLI-/Dev-Tools mit einem Quick-Cut-Trailer zu bewerben ist langweilig und fühlt sich nicht nach einem Tutorial an – stattdessen ~60-90 s Voice-Over-Tutorial: Schmerzpunkt-Hook → Installation→Ausführung→Verstehen der Ausgabe→Erweitert Schritt für Schritt + edge-tts Schritt für Schritt erklären (langsam, jeden Schritt lange genug anhalten) + Schritt-für-Schritt-Untertitel → CTA. Die Hintergrundmusik sehr leise stellen (~0,13), um das Voice-Over in den Vordergrund zu stellen. Praxis: Die erste Version des WIP-Trailers war langweilig, erst die Voice-Over-Tutorial-Version vermittelte das Gefühl „verstanden, ich kann es nutzen“. Die Synchronisation des Voice-Over erfolgt über die __STEPLOG von term-player. term-player muss bei jedem Schritt den Bildschirm leeren (GOTCHA 16 verwandtes Problem: Akkumulierter Inhalt läuft über den sichtbaren Bereich hinaus, der erklärte Befehl wird weggeschoben).

  23. Zuerst das gesamte README/--help scannen, um eine Funktionsliste zu erstellen, dann das Skript schreiben: Bei der Erstellung von Tool-Tutorials/Erklärungen nicht nur den Anfang lesen und dann losdrehen – sonst werden die Vorzeigefunktionen übersehen (WIP übersah dashboard.py visuelles Dashboard, wip done, --root, install-skill, Codex-Unterstützung, see: Planungsdatei-Erkennung). Zuerst alle Funktionen auflisten, dann auswählen, welche gezeigt werden sollen. TTS nicht zwingen, Kontonamen/Handles/seltene Schreibweisen auszusprechen (z. B. yazelin ist eigentlich der Pinyin des chinesischen Namens „Yazelin“, Buchstabe für Buchstabe klingt es seltsam) – wenn die URL bereits auf dem Bildschirm ist, das Voice-Over darüber hinweggehen lassen („Link unten“). Wortanfänge vermeiden, die ins Stocken geraten („zuerst…“ → „…ist ganz einfach“). edge-tts spricht allgemeine englische Fachbegriffe (cargo/markdown/Claude Code) problemlos aus, mit Gemini Flash einen Satz anhören, um sicherzugehen.

  24. MODUS D Musikvideo: Ganzer Song + Interaktion, nicht aufnehmen, später mit Originaldatei muxen. Playwrights recordVideo enthält keine Tonspur und keinen Mauszeiger (wenn keine vollständige Tonspur vorliegt, siehe Abschnitt „Echte Aufnahme“). Daher: (a) Ton mit Original-MP3 später muxen (beste Audioqualität, wird nicht vom Null-Sink der xvfb beeinflusst); (b) einen falschen Mauszeiger einfügen (ein Bild, das dem pointermove folgt, nur für die Aufnahme, nicht ins Repository), sonst sieht die „Mausinteraktion“ im Video nur wie eine automatische Animation aus. Die Zeitleiste der Aktionen muss an audio.currentTime gebunden sein (Polling waitForFunction(t => au.currentTime >= X)), nicht waitForTimeout verwenden – die Wanduhr driftet, wenn der Aufnahmerekorder Frames fallen lässt, die Audio-Uhr jedoch nicht.

  25. Die Synchronisation an der Uhr auf dem Bildschirm ausrichten, nicht an Date.now() des Nodes. Es gibt eine unbestimmte Abweichung zwischen dem ersten Frame des webm und newContext(), und Playwright fügt am Ende einen großen Block eingefrorener Frames hinzu (in der Praxis: 160 s Inhalt als 239 s Datei geschrieben). ffprobe-Dauer kann nicht als Länge verwendet werden. Lösung: Den Fortschrittsbalken zuschneiden, Frame für Frame die x-Position des Schiebereglers messen (die Spalte mit den meisten rosa Pixeln als Spitzenwert nehmen, dann den Schwerpunkt berechnen – vorbeiziehende Partikel haben die gleiche Farbe, der Durchschnitt wäre verfälscht). Führe eine lineare Regression von Bildschirmzeit → Audiozeit durch, um den Audio-Startpunkt zu lösen. Drei Schätzmethoden gegenseitig validieren (Schieberegler-Regression, Liedtextwechsel-Moment, Wanduhr beim Klicken) und den Median nehmen. Die Liedtext-Methode hat eine CSS-bedingte Verzögerung von ~0,1 s, die abgezogen werden muss. Vor Abschluss mit Kreuzkorrelation die Verzögerung des fertigen Audios gegenüber der Originaldatei prüfen (AAC-Priming erzeugt feste ~40 ms, normal). Danach drei Frames weit entfernt von Textwechseln überprüfen, ob der richtige Text leuchtet.

  26. Hochformat (9:16) nicht durch Browser-Skalierung auf die Auflösung stützen. recordVideo legt den „CSS-großen Bildschirm“ in die gewünschte Frame-Größe: Viewport 405×720 mit recordVideo.size 1080×1920 ergibt: der 405×720-Inhalt wird oben links platziert, der Rest mit Grau gefüllt. Versuch, mit deviceScaleFactor die Geräte-Pixel zu erhöhen: Playwrights Viewport-Emulation setzt DSF auf 1 zurück. Verwendung von --force-device-scale-factor + --window-size, xvfb hat keinen Fenstermanager, --window-size, Browser.setWindowBounds, --kiosk ändern alle das Fenster nicht (gemessene innerWidth bleibt bei 501). html{zoom:...} funktioniert auch nicht, Media Queries lesen die ursprüngliche Breite. Lösbare Lösung: Viewport direkt auf nativen 1080×1920 öffnen (DSF 1), dann ein „vergrößertes Hochformat-Stylesheet“ injizieren (mobile Anordnung + Schriftgröße/Steuerelemente proportional vergrößert, !important überschreibt das Desktop-Grid). Das Aufnahme-Stylesheet kommt nicht ins Repository, es wird an der gleichen Stelle wie HIDE injiziert. Schärfe der Schrift vergrößert prüfen (GOTCHA 16): 405 auf 1080 skalierte Schrift hat weiche Kanten, nativ gerenderte ist scharf.

  27. Wenn möglich, „der Bildschirm spricht für sich“: Bei interaktiven Aufnahmen die gewünschten Aktionen als Zeitleiste programmieren (Klicken, Ziehen, Anhalten). Dabei bewusst einige Abschnitte völlig unbewegt lassen, damit automatisch ausgeblendete UI-Elemente verschwinden – ständige Bewegung lenkt ab. Bei interaktiven Aufnahmen daran denken, dass recordVideo keinen Mauszeiger enthält. Einen falschen Mauszeiger injizieren (ein Bild, das dem pointermove folgt), sonst sehen die Zuschauer nur automatische Animationen.

  28. Der Synchronisations-Ankerpunkt sollte ein „weißer Blitz“ sein, kein „schwarzer Bildschirm“. Die Idee: „Der Bildschirm wird zuerst schwarz, das Video beginnt erst, wenn der Inhalt erscheint, dann den ersten hellen Frame als t=0 scannen“ – in der Praxis nicht möglich: Playwrights recordVideo beginnt nicht mit dem ersten Frame, der dunkelste Frame der gesamten Aufnahme ist 24,4/255, es gibt keinen schwarzen Frame, der erkannt werden kann. Stattdessen 400 ms vollständiges Weiß vor dem Start einblenden und beim Start ausblenden. Dann „den nächsten Frame nach dem Ende des weißen Blitzes“ scannen. Weiß 250 gegen Inhalt 24-37 ist nicht zu verwechseln. Der Offset wurde über die gesamte Aufnahme mit ±0,05 s validiert, ohne Drift.

  29. metadata=print schreibt standardmäßig nach stderr. Die Ausgabe von ffmpeg -vf "...,metadata=print:key=X" erscheint nicht in stdout. Ein Skript, das nur stdout liest, erhält immer eine leere Ausgabe und interpretiert dies fälschlicherweise als „schwarzes Bild“. :file=- hinzufügen.

  30. Der integrierte HTTP-Server muss zuerst die Datei lesen, dann den Header senden. res.writeHead(200) vor await readFile geschrieben: Wenn die Datei nicht existiert, wird der 200-Header bereits gesendet, und der Catch, der 404 setzen möchte, kollidiert mit ERR_HTTP_HEADERS_SENT – die eigentliche Ursache (falscher Pfad) wird durch diesen falschen Fehler verdeckt. Diese Fallgrube war ursprünglich in capture-app.mjs dieses Repositorys vorhanden und wurde behoben.

  31. Die A/V-Synchronisation muss mit einer unterscheidbaren Methode geprüft werden. „Einen Frame zur gleichen Zeit nehmen und die Korrelation vergleichen“ klingt sinnvoll, aber bei Folien-Präsentationen, die Dutzende von Sekunden kein Bild wechseln, ergibt auch eine negative Kontrolle (absichtlich 6 s versetzt) 0,996 – die Methode ist wertlos. Ein praktikabler Ansatz: In der Vorlage einen Fortschrittsbalken platzieren (Breite = currentTime / duration). Aus dem fertigen Video die Balkenbreite messen, um die interne Zeit des Players abzulesen und direkt mit der Ausgabezeit zu subtrahieren. Dieser Balken dient auch als visuelles Element des fertigen Produkts, nicht nur zum Testen.

  32. Standbild-Folien im 9:16-Kurzvideo: Das Bild ist tot – und messbar. Der Grauwert-Unterschied zwischen aufeinanderfolgenden Frames der Folie betrug in der Praxis nur 0,072 / 0,092 (eingefrorener Vergleich: 0,000). Das Nutzerfeedback war „zu langweilig, es fehlt an Punch“. Die Schwelle nicht nach Gefühl festlegen, sondern bereits ausgelieferte 9:16-Werke mit dem gleichen Maßstab messen, um einen brauchbaren Bereich zu ermitteln: neko-tensei Musikvideo 0,233, k-rider Trailer 1,045, battlefield-editor 1,293, roll-formosa 1,912, catime Anzeige 3,965, catime Trailer 6,168 (active_ratio 0,636 = Quick-Cut). Erzählende Zitat-Kurzvideos sollten keine Quick-Cuts sein. Zielbereich 0,30 bis 1,05, active_ratio 0,10 bis 0,40 – Diese Schwelle ist ungültig: mean und active_ratio sind nicht für die Bildrate normalisiert, und die eigene Arbeit als Maßstab für die eigene Arbeit zu verwenden, ist ein Zirkelschluss. Das aktuelle Kriterium ist der motion.slide.per_s ≥ 5,5 von punch-score.py (Grauwert-Änderung pro Sekunde, mean × fps, die einzige Zahl, die nicht mit der Bildrate variiert). Keine Obergrenze, das gefährliche „Flackern“ wird durch GOTCHA 35 (Helligkeitsereignisse) abgefangen. Begründung: Die vom Nutzer als „tot“ beschriebene v1 hatte 1,79 und 2,30, während das ruhigste ausgelieferte Werk neko-tensei 5,82 beträgt und bestehen muss.

  33. Bei der Kamerafahrt für Standbild-Folien muss die Kameraposition aus dem Bild selbst gemessen werden, nicht als festes Verhältnis festgelegt werden. 960×720 Folien hatten in der Praxis nur 250 Scanlines mit Text, der Rest war leer. Feste obere/mittlere/untere Zuschnitte schneiden die erste kleine Zeile oder einen ganzen leeren Bereich ab. Vorgehen: Canvas einen Frame extrahieren → Tintenmenge pro Zeile (Differenz zum Hintergrund-Median) → zu Bändern verbinden → Bänder mit weniger als 12 % Tinte verwerfen → die linken und rechten Extremwerte der verbleibenden Bänder sind der Textbereich SP. Die Kamera darf SP nie abschneiden. Beim Messen nicht verkleinern: 8-fach verkleinert (120×90) gemessen, drawImage-Miniaturansicht ist nicht der Flächendurchschnitt, dünne Striche verschwinden (die „的路。“ am rechten Ende des Textes wurde nicht gemessen, SP war 96 Pixel zu kurz), und der Hintergrundverlauf wurde als durchgehende Breite interpretiert. Egal, wie die Schwelle angepasst wurde, sie half nicht. Die Verwendung der Originalauflösung löste das Problem, und „Tinte“ kann mit der gleichen Definition wie die Abnahme verwendet werden (28 über dem Hintergrund-Median). 960×720 einmal zu scannen dauert in headless Chromium nur wenige zehn Millisekunden.

  34. Die Kameraposition muss am „Frame, der gerade abgespielt wird“, gemessen werden, nicht, indem man zuerst dorthin sucht. Um die Seite zu messen, auf der das Zitat gesprochen wird: V.currentTime = Mitte des Zitats und dann drawImage – seeked garantiert nur, dass die Dekodierung abgeschlossen ist, das Bild hat sich noch nicht geändert. In der Praxis verwendete das gesamte quote-06 den Layout von Frame 0 (vorherige Seite) für die Kameraposition: SP wurde als 596 statt 696 gemessen, sodass der Rahmen, der „kein Abschneiden des Textes“ garantieren sollte, von Anfang an falsch war. In den 2,8 Sekunden des wichtigsten Schlüsselworts fehlte bei der Hälfte der Frames ein Teil des Textes. Ein zuverlässiger Ansatz ist während der Wiedergabe zu messen: Verwende den ersten Frame (das war der aktuelle Bildschirminhalt), dann nach jedem Kamerawechsel erneut messen. Dies löst auch das Problem „Ein Satz erstreckt sich über zwei Seiten“ – der nächste Kamerawechsel nach dem Seitenwechsel folgt.

  35. Helligkeitsereignisse müssen eine messbare Obergrenze haben, nicht nur „ich mache es langsam“. Miss den YAVG Frame für Frame der Folie. Die Schwelle ist relative 10 % plus eine absolute Untergrenze von 4/255 – der Hintergrund-Y der Folie liegt bei etwa 20; nur mit absolutem Wert würde eine Aufhellung von 9 % nicht erkannt; nur mit relativem Wert würde das Rauschen in dunklen Bereichen die Auswertung überfluten. Zähle, wie oft in der dichtesten Sekunde, und halte 3 (Risikoschwelle für fotosensitive Epilepsie) ein. In der Praxis: fertiges Werk 2 Ereignisse/s; negative Kontrolle 5 Hz Flimmern 26 Ereignisse/s, 1 Hz ±6 % langsames Pulsieren 6 Ereignisse/s – beide werden abgefangen. Diese Prüfung ist in capture-short.mjs implementiert und wird nach der Aufnahme ausgegeben.

  36. Der eq-Filter von ffmpeg mit brightness-Ausdruck berechnet nur einmal (eval=init). Bei der negativen Kontrolle für „absichtliches Flackern“ wurde :eval=frame vergessen, der Filter blieb wirkungslos, die gemessenen Werte waren identisch mit dem fertigen Werk – fast wäre ein nützlicher Indikator als „nicht unterscheidbar“ verworfen worden. Die negative Kontrolle muss auch selbst überprüft werden, ob sie wirklich kaputt ist, sonst beweist sie nur, dass dein Werkzeug zwei identische Dateien nicht unterscheiden kann.

  37. Frame-Differenzen sind für die Synchronisation immer nicht unterscheidbar. GOTCHA 31 in einer anderen Vorlage erneut getestet: words.json absichtlich um 6 s verschoben, die Frame-Differenz des Folienbands war identisch 0,718 (es beachtet words.json gar nicht). Was auffällt, ist die Differenz „Moment des Untertitelwechsels vs. Moment der maximalen Frame-Differenz im Untertitelbereich“ (0,091 → 0,567). Dynamische Indikatoren können nur Dynamik behaupten, für die Synchronisation muss anders gemessen werden.

  38. Die Untertitelaufteilung klebt zwei benachbarte Schlüsselwörter zusammen, wenn sie in denselben Block passen. Die Aufteilung wechselt den Block nur, wenn er nicht mehr passt. Wenn das nächste Schlüsselwort noch passt, wird es mit dem vorherigen zusammengeklebt – in der Praxis: „很大的問號“ und „沒有被驗證過“ ohne Zeichen dazwischen, 11 Zeichen in einem Block, alle golden, zwei Höhepunkte zu einem reduziert (2 leuchtende Blöcke). Lösung: Eine Regel hinzufügen: k >= 0 && k !== k des vorherigen Zeichens erzwingt einen neuen Block. Nach der Korrektur: 3 leuchtende Blöcke, maximaler Block von 11 auf 6 Zeichen reduziert. Achtung: In punch-score.py gibt es eine identische Aufteilungslogik, die ebenfalls angepasst werden muss.

  39. Zur Laufzeit wird der Zoom heimlich vergrößert. Beim Berechnen der Breite den Spielraum abziehen. Langsamer Zoom (+3,5 %) und Schlüsselwort-Zoom (+5 %) ergeben 8,7 %: ein „gerade richtig“ berechneter Kamerarahmen wird durch den Zoom sofort den Text aus dem Bild drücken, und ausgerechnet in den Sekunden des Schlüsselworts – der problematischste Moment. Zweierlei ist zu tun: Bei der Kameraposition z = Breitentoleranz / 1,087 verwenden und jeden Frame erneut begrenzen (z ≤ Breitentoleranz, cx so begrenzen, dass beide Enden von SP im Rahmen liegen). Die Invariante muss in dem Moment gelten, in dem das Bild ausgegeben wird, nicht nur „als die Kameraposition berechnet wurde“.

  40. Sobald der Kopf oder Fuß einer Folie in die Bildmitte gerät, werden die Seiten abgeschnitten und als Text-Abschneiden gezählt. Die kleine Kopfzeile (x 24 bis 932) und der Fußzeilen-Fortschrittsbalken (x 0 bis 759) ragen über den Textbereich hinaus. Sobald die Kamera zoomt, ragen sie über die Bildränder. Die Abnahme schneidet oben und unten 10 % der Folie als Kopf/Fuß ab. Sobald die Kamerafahrt geändert wird, liegen die Möbelstücke innerhalb dieser Linie und werden als abgeschnittener Text gezählt. Lösung: Alle Zeilen, deren Tinte über SP hinausragt, als Möbelstücke markieren (Kriterium ist nicht „durchgehende Breite“ – der Fußzeilenbalken hat unter der Tintenschwelle nur 79 % Breite, mit voller Breite würde er durchrutschen). Dann das Kamera-Zentrum so beschränken, dass Möbelstücke nur in den oberen/unteren 10 % des Bildschirms bleiben oder ganz aus dem Bild fallen. Die Grenze um einige Pixel erweitern: Knapp bei 10 % lassen Rundungsfehler die letzten ein bis zwei Zeilen in den Textbereich fallen (in der Praxis noch 15 Frames, alle in den Zeilen 81 und 82).

  41. Um Kamerafahrt-Probleme zu diagnostizieren, den Algorithmus offline erneut ausführen, nicht durch Parametervariation herumprobieren. Ein 40-Sekunden-Video. Blindes Herumprobieren führt in eine „je mehr, desto schlimmer“-Situation: In der Praxis wurden der langsame Zoom und der Schlüsselwort-Zoom deaktiviert (als Hauptursache vermutet), aber der Textabschnitt stieg von 102 auf 189 Frames. Stattdessen denselben Test-Frame mit ffmpeg extrahieren, im Python die gleiche Band-/SP-Logik ausführen – in zehn Sekunden sieht man, dass „der vom Browser gemessene SP identisch mit der Seite bei Frame 0 ist“. Die Ursache wird sofort sichtbar. Außerdem die Vorbedingungen der Kamerafahrt ins Log schreiben (SP, z der Breitentoleranz, Position der Möbelstücke) – wenn die Kamerafahrt zusammenbricht, zeigen diese drei Zahlen sofort, ob die Breite falsch gemessen wurde oder der Korridor eingeklemmt ist.

  42. Zahlen des fertigen Produkts in „aus dem Inhalt stammend“ und „aus dem Bildschirm stammend“ aufteilen. Für Letzteres muss die md5 der Vorlage angegeben werden. Die aus dem Inhalt stammenden Zahlen (Hook, Färbung, leuchtende Blöcke, Untertitelblöcke, Stumm, Sprechgeschwindigkeit) sind an words.json gebunden; wenn die md5 von words.json unverändert bleibt, ändert sich kein einziges Bit. Die aus dem Bildschirm stammenden Zahlen (Frame-Differenz, Textabschnitt) gelten nur für die bei der Aufnahme verwendete Version der Vorlage. Wenn beide vermischt werden, erhält die nächste Person, die das Dokument nachstellt, nicht übereinstimmende Zahlen: In der Praxis hatte derselbe Clip in der alten Vorlage eine Änderungsrate pro Sekunde von 14,80, in der neuen von 27,19 – eine Differenz um den Faktor 1,8, während die sechs inhaltsbezogenen Zahlen exakt gleich blieben. Die Person verbrachte eine Iteration mit der Suche nach einer nicht existierenden Regression. capture-short.mjs gibt jetzt bei jeder Aufnahme Vorlage short-9x16.html md5 … aus. Die md5 muss bei der Angabe von Zahlen mitkopiert werden.

  43. Die Aufnahme selbst hat ein Zittern. Das Kriterium muss dieses Zittern tolerieren. Vorlage und Eingabe mit der md5 fixiert, viermal hintereinander aufgenommen. Die Änderungsrate pro Sekunde des Folienbands betrug 21,27 / 21,33 / 20,82 / 21,3

Verwandte Skills