video-sync
Aus shots.json + Originalvideo wird ein Video erstellt: Auf einer Seite das Bild, auf der anderen Seite die Shot-Informationen des aktuellen Shots. Beim Szenenwechsel aktualisiert sich die Tabelle automatisch und die aktuelle Zeile wird hervorgehoben.
Das Layout richtet sich ausschließlich nach dem Seitenverhältnis:
| Originalvideo | Layout | Zusammensetzung |
|---|---|---|
| Horizontal (breit > hoch), Quadratisch | Bild oben, Informationen unten | vstack |
| Vertikal (hoch > breit) | Bild links, Informationen rechts | hstack |
In beiden Fällen wird das Bild unbeschnitten skaliert. Das Panel füllt den verbleibenden Platz.
Es werden nur drei Screenshots erstellt; die beweglichen Teile übernimmt FFmpeg. Auf dem Panel ändern sich nur zwei Dinge: die Scroll-Position und die hervorgehobene Zeile. Daher werden die Zeilenpositionen einmal gemessen, ein Hintergrundbild sowie zwei vollständig aufgeklappte Tabellenbilder erstellt (eines abgedunkelt, eines vollständig hell). Scrollen = ein Ausschnitt aus dem dunklen Hintergrundbild, Hervorhebung = eine Zeile aus dem hellen Tabellenbild wird darüber gelegt. Die Positionen werden über sendcmd an jedem Schnittpunkt mit einer kleinen Expression gesteuert. Bei 53 Shots sinkt die Anzahl der Screenshots von 53 auf 3 (12 Sekunden).
Der aktuelle Shot wird in der zweiten Zeile fixiert: Die ersten beiden Shots scrollen nicht (die Hervorhebung bewegt sich nach unten in die nächste Zeile), ab dem dritten Shot wird bei jedem Schnitt eine Zeile nach oben gescrollt. Die Bewegung erfolgt am Schnittpunkt, der Rest ist statisch – am Schnittpunkt wird innerhalb von --ease (Standard: 0,45) Sekunden an die Zielposition gescrollt und bleibt dann stehen. Bei einem 16-Sekunden-Langshot würde die Liste hingegen kontinuierlich kriechen, was den Zuschauer 16 Sekunden lang ablenken würde.
Die Zeilenhöhe wird durch den Inhalt bestimmt: Je länger die Beschreibung, desto höher die Zeile. Die Hervorhebungsleiste ist nach Zeilenhöhe gestaffelt (die Höhe von crop lässt sich zur Laufzeit nicht ändern).
{baseDir} = Verzeichnis, in dem sich diese Datei befindet. Das Skript {baseDir}/scripts/video-sync.mjs hat keine Abhängigkeiten und wird direkt mit node ausgeführt.
Abgrenzungen (was nicht gemacht wird): Keine Shot-Analyse (das ist Aufgabe von video-shots, dieses Skill verwendet dessen shots.json), kein Schneiden, keine Überblendungen, keine Musik, keine Untertitel im Bild, keine Frame-für-Frame-Rendering (Animationen werden von FFmpeg zeitgesteuert erstellt).
Schritt 0 – Shot-Daten müssen vorhanden sein
Eine shots.json (Ausgabe von video-shots) und das zugehörige Originalvideo sind erforderlich. Wenn frames/ (ein Schlüsselbild pro Shot) vorhanden ist, wird dieses als Thumbnail in der Tabelle verwendet – andernfalls bleibt die Stelle leer.
Die shots.json muss mit dem Video übereinstimmen: Wenn meta.durationSeconds nicht mit der Videodauer übereinstimmt, wird die Synthese abgebrochen; korrigieren Sie zuerst die Shots.
Schritt 1 – Geometrie prüfen, dann Video erstellen
node {baseDir}/scripts/video-sync.mjs plan shots.json --video <Video>
Nur berechnen, nicht synthetisieren – dauert nur Sekunden: Layout, Bildbereich, Panelbereich, Ausgabeabmessungen. Prüfen Sie zuerst diese drei Zahlen, bevor Sie fortfahren – die Synthese eines 3-minütigen Videos dauert ein bis zwei Minuten; ein Fehler wäre dann ärgerlich.
Anpassungen: --panel <Verhältnis> (horizontal: Panelhöhe / Bildhöhe, Standard 0,8; vertikal: Panelbreite / Bildbreite, Standard 1,6), --width / --height (Obergrenze des Bildbereichs, Standard 1920 / 1080).
Ist das Originalvideo zu klein, skaliert den Bildbereich hoch: Das Panel wird relativ zum Bildbereich berechnet. Bei einem 640x360-Video wäre das Panel nur 640x288 groß – vier Spalten wären zu eng. --scale 2 skaliert den Bildbereich auf 1280x720 und das Panel auf 1280x576, das Seitenverhältnis bleibt erhalten. Standard ist 1 (keine Skalierung). plan / panels / compose / export müssen alle denselben --scale-Wert verwenden.
Schritt 2 – Panel rendern
node {baseDir}/scripts/video-sync.mjs panels shots.json --video <Video> \
--frames frames --lang zh|en [--out panels] [--chrome <Pfad>]
Erzeugt static.png (Hintergrund), list-dim.png / list-lit.png (vollständig aufgeklappte Tabelle, einmal abgedunkelt, einmal hell), layout.json (Zeilenpositionen, die für das Scrollen bei der Synthese verwendet werden), sowie panels/panel.html – im Browser öffnen, um das Panel zu sehen. Mit #S07 kann der Shot gewechselt werden (die Scroll-Animation im Preview entspricht der Exportversion).
Das Layout wird über {baseDir}/scripts/panel.css angepasst: Nach einer Änderung wird panels erneut ausgeführt, um das neue Layout zu übernehmen – ohne Änderungen am Skript oder an der Screenshot-Logik. Stilkonventionen in {baseDir}/references/panel-style.md.
Schritt 3 – Synthese
node {baseDir}/scripts/video-sync.mjs compose shots.json --video <Video> \
[--panels panels] [-o out.mp4] [--crf 20] [--ease 0.45]
Die Audiospur wird unverändert übernommen (bei stummen Videos wird automatisch keine Tonspur hinzugefügt). Nach der Synthese werden in stderr die Ausgabemaße, Dauer und das Layout ausgegeben – überprüfen Sie diese Angaben.
Die beiden Schritte können auch in einem Befehl kombiniert werden:
node {baseDir}/scripts/video-sync.mjs export shots.json --video <Video> -o out.mp4 --frames frames
Schritt 4 – Stichprobenprüfung ⛔ Nicht überspringen
Prüfen Sie nicht nur, ob die Synthese ohne Fehler abgeschlossen wurde. Nehmen Sie mindestens drei Stichproben, um zu sehen, ob die Informationen mit dem Bild übereinstimmen:
for t in 5 30 60; do ffmpeg -v error -y -ss $t -i out.mp4 -frames:v 1 -q:v 3 /tmp/check-$t.jpg; done
Überprüfen Sie drei Dinge: Zu welchem Shot gehört das Bild in diesem Frame? Ist die Shots-Nummer im Panel dieselbe? Wird die hervorgehobene Zeile korrekt aktualisiert? Abweichungen deuten in der Regel darauf hin, dass shots.json und das Originalvideo nicht zusammengehören oder dass --video falsch angegeben wurde.
Melden Sie das Ergebnis in einem Satz: Ausgabepfad, Größe, Dauer, Layout, Anzahl der Shots und welche Panels nicht gerendert wurden (falls vorhanden).
Abgrenzungen
- Das Scrollen erfolgt nur am Schnittpunkt: Innerhalb von
--easeSekunden wird an die Zielposition gescrollt und bleibt dann stehen. Ein „gleichmäßiges Durchlaufen“ ist theoretisch möglich (durch Anpassung dermotionPlan-Segmente), würde aber bei langen Shots zu störendem Flimmern führen. - Ein Headless-Browser ist erforderlich (Chrome/Chromium/Edge). Der Preis für „das Layout kann frei angepasst werden“: Mit FFmpegs
drawtextmüsste man Zeilenumbrüche, Thumbnails und Hervorhebungen selbst implementieren, und jede Layoutänderung würde eine Überarbeitung des Filtergraphen erfordern. - Das Vokabular (Einstellungsgröße/Kategorie/Kamerabewegung/Schnitt) ist in diesem Skill eigenständig enthalten – es wird nicht aus anderen Verzeichnissen importiert, damit das Skill vollständig kopiert werden kann. Wenn in
video-shotsneue Begriffe hinzugefügt werden, müssen sie auch hier ergänzt werden, da sie sonst als Enum-Schlüssel angezeigt werden. - Das Rendern des Panels erzeugt immer drei Screenshots, unabhängig von der Anzahl der Shots: 53 Shots dauern etwa 12 Sekunden. Die Synthese skaliert mit der Videolänge; ein 203-Sekunden-Video benötigt etwa 75 Sekunden.
Selbsttest
node {baseDir}/scripts/selftest.mjs
122 Assertions, ohne FFmpeg oder Browser: Geometrie (horizontal/quadratisch/vertikal, gerade Kantenlängen, Ober-/Untergrenzen, Drehknöpfe), Datenvertrag der Panelseite (Vokabularverteilung, Rolleninformationen, Thumbnails nur vorhanden/fehlend, Escape-Sequenzen), Animationsbefehle (jeder Befehl muss kurz sein – zu lange Expressions führen zu FFmpeg-Konfigurationsfehlern), FFmpeg-Parameter. Nach Änderungen am Skript zuerst diesen Test ausführen.