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
recordVideoweg 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 diewindow.__STEPLOG-Variable vonterm-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
- Aufnahme (Capture) — MODUS A:
xvfb-runheaded Chromium starten (WebGL erforderlich), PlaywrightrecordVideoverwendet, um den automatisch abgespielten Höhepunkt aufzuzeichnen; MODUS E läuft headless (siehe GOTCHA 47). MODUS B: Quellenliste abrufen, auswählen, herunterladen. - Sprachausgabe (Narration) (optional) — edge-tts
zh-TW-YunJheNeural --rate=-5%erzeugt das Voice-Over. - Qualitätskontrolle (Verify) (optional) — Gemini Pro hört sich das Transkript an und prüft die Betonung; Gemini Flash wählt die Hintergrundmusik aus.
- Komposition (Compose) —
brand.pyerstellt 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 v2verwenden, 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.mjsgleicht 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:
--kioskfunktioniert bei Playwright nicht; es verwaltet das Fenster selbst. Die Registerkartenleiste + Adressleiste sind fest 88 Pixel hoch. Den Bildschirm 88 Pixel größer öffnen und mitcropabschneiden. Den Cursor mit-draw_mouse 0ausblenden.- 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,querySelectorfindet sie nicht). - 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.
- Segmentiert schreiben (
-f segment). Ein Absturz verliert nur ein Segment. Abschließend mitconcat -c copyverlustfrei verbinden. - 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
-
Seitenverhältnis-Treue: Niemals
scale=W:Hfü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 mitforce_original_aspect_ratio. Vor Abschluss „Original-Seitenverhältnis vs. Anzeige-Seitenverhältnis“ prüfen (Fehler <0,1 % bestehen). -
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. -
Zoompan-Frame-Explosion: Nicht
-loop 1 -t(jeder Eingabe-Frame wird ×d multipliziert). Bei Einzelbild:-frames:v D+zoompan d=D. -
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.
-
file://-Fetch wird blockiert: App verwendet
fetch(?pkg=)/JSON laden →python3 -m http.serverstarten, um http zu verwenden (oder den integrierten Server voncapture-app.mjs). -
Headless WebGL verliert Swiftshader/wird schwarz →
xvfb-run -a --server-args="-screen 0 WxHx24"verwenden, um headed Chromium zu starten. -
ES-Modul-App:
gotoPhaseist nicht im Window-Objekt → DOM-Knoten der Timeline#tlNodes .node[i].click()verwenden, nichtevaluate(gotoPhase). -
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. -
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).
-
Hintergrundmusik: Gemini Flash durchsucht CC0-Musik, auf Stimmigkeit achten (Kriegsmusik nicht für eine Katzen-Anzeige verwenden); CC0-Quelle notieren.
-
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.
-
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.
-
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“.
-
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“. -
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.
-
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.
-
Interaktives Spiel ohne Auto-Play → Tastatureingaben selbst senden (Untermodus von MODUS A): Katamari/Action-Spiele mit Playwright
keyboard.down/upsteuern (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. -
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). -
„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).
-
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.
-
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).
-
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→ErweitertSchritt 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__STEPLOGvon 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). -
Zuerst das gesamte README/
--helpscannen, 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 übersahdashboard.pyvisuelles 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.yazelinist 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. -
MODUS D Musikvideo: Ganzer Song + Interaktion, nicht aufnehmen, später mit Originaldatei muxen. Playwrights
recordVideoenthä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 anaudio.currentTimegebunden sein (PollingwaitForFunction(t => au.currentTime >= X)), nichtwaitForTimeoutverwenden – die Wanduhr driftet, wenn der Aufnahmerekorder Frames fallen lässt, die Audio-Uhr jedoch nicht. -
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 undnewContext(), 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 vonBildschirmzeit → Audiozeitdurch, 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. -
Hochformat (9:16) nicht durch Browser-Skalierung auf die Auflösung stützen.
recordVideolegt den „CSS-großen Bildschirm“ in die gewünschte Frame-Größe: Viewport 405×720 mitrecordVideo.size1080×1920 ergibt: der 405×720-Inhalt wird oben links platziert, der Rest mit Grau gefüllt. Versuch, mitdeviceScaleFactordie 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 wieHIDEinjiziert. Schärfe der Schrift vergrößert prüfen (GOTCHA 16): 405 auf 1080 skalierte Schrift hat weiche Kanten, nativ gerenderte ist scharf. -
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.
-
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.
-
metadata=printschreibt standardmäßig nach stderr. Die Ausgabe vonffmpeg -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. -
Der integrierte HTTP-Server muss zuerst die Datei lesen, dann den Header senden.
res.writeHead(200)vorawait readFilegeschrieben: Wenn die Datei nicht existiert, wird der 200-Header bereits gesendet, und der Catch, der 404 setzen möchte, kollidiert mitERR_HTTP_HEADERS_SENT– die eigentliche Ursache (falscher Pfad) wird durch diesen falschen Fehler verdeckt. Diese Fallgrube war ursprünglich incapture-app.mjsdieses Repositorys vorhanden und wurde behoben. -
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. -
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:meanundactive_ratiosind 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 dermotion.slide.per_s ≥ 5,5vonpunch-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. -
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. -
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 Zitatsund danndrawImage–seekedgarantiert 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. -
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.mjsimplementiert und wird nach der Aufnahme ausgegeben. -
Der
eq-Filter von ffmpeg mit brightness-Ausdruck berechnet nur einmal (eval=init). Bei der negativen Kontrolle für „absichtliches Flackern“ wurde:eval=framevergessen, 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. -
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.
-
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 Zeichenserzwingt einen neuen Block. Nach der Korrektur: 3 leuchtende Blöcke, maximaler Block von 11 auf 6 Zeichen reduziert. Achtung: Inpunch-score.pygibt es eine identische Aufteilungslogik, die ebenfalls angepasst werden muss. -
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,087verwenden und jeden Frame erneut begrenzen (z ≤ Breitentoleranz,cxso 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“. -
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).
-
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.
-
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.jsongebunden; wenn die md5 vonwords.jsonunverä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.mjsgibt jetzt bei jeder AufnahmeVorlage short-9x16.html md5 …aus. Die md5 muss bei der Angabe von Zahlen mitkopiert werden. -
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