Communitygithub.com

GetOccasion/demo-video-skill

Erstelle ein vertontes, untertiteltes Bildschirm-Video einer Web-App-Funktion – gesteuerte Browser-Interaktionen, aufgezeichnet mit agent-browser, einer lokalen Kokoro-Sprachausgabe und mit ffmpeg eingebrannten Untertiteln. Verwende es, wann immer der Benutzer ein Demovideo, einen Walkthrough, einen Screencast, eine Feature-Tour, eine Erklärung oder eine aufgezeichnete Demo von etwas in seiner App wünscht, ein Feature einem Team, Kunden oder Stakeholdern als Video „zeigen“ möchte oder eine Sprachausgabe oder Untertitel für eine Aufnahme verlangt – selbst wenn er nicht „Skill“ sagt oder eines dieser Tools nennt.

Was ist demo-video-skill?

Dieser Skill verwandelt eine Web-App-Funktion in ein kurzes Video mit echten Browser-Interaktionen, einer erläuternden Sprachausgabe und Untertiteln. Alles läuft lokal: agent-browser zeichnet auf, Kokoro spricht, ffmpeg und Pillow setzen es zusammen. Die Kernidee ist, dass die Sprachausgabe den Takt vorgibt: Der Voiceover wird zuerst gerendert, jede Szene vermessen und jede Browser-Aktion auf das Wort getaktet, das sie beschreibt. So wird vermieden, dass die Aufnahme die Sprachausgabe timen muss, und die Montage friert das Bild dort ein, wo die Sprachausgabe mehr Zeit benötigt. Der Skill unterstützt die Erstellung von Demodaten, das Schreiben eines Drehbuchs, das Rendern des Voiceovers, das Aufzeichnen der Browser-Interaktionen und die finale Zusammenstellung – alles über eine Reihe von Skripten und Konfigurationen.

Funktioniert mit✓Claude Code~Codex CLI~Cursor
npx skills add GetOccasion/demo-video-skill

In Ihrer bevorzugten KI fragen

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

Dokumentation

Demo-Video

Verwandelt eine Funktion in ein kurzes Video: echte Interaktionen in einem echten Browser, eine Sprachausgabe, die erklärt, was auf dem Bildschirm zu sehen ist, und Untertitel. Alles läuft lokal: agent-browser zeichnet auf, Kokoro spricht, ffmpeg und Pillow setzen es zusammen.

Die eine Idee, die es zum Laufen bringt: Die Sprachausgabe ist der Taktgeber. Rendere zuerst den Voiceover, vermesse jede Szene und takte jede Browser-Aktion auf das Wort, das darüber spricht. Stimme die Sprachausgabe niemals auf eine Aufnahme ab; die Geschwindigkeit einer Aufnahme driftet, die einer WAV-Datei nicht. Die Aufnahme wartet nicht auf die Sprachausgabe: Aktionen laufen direkt nacheinander ab, und die Montage friert das Bild dort ein, wo die Sprachausgabe mehr Zeit benötigt.

Werkzeuge

Die Skripte dieses Skills befinden sich in scripts/ unter seinem Basisverzeichnis – dem, das beim Laden als „Basisverzeichnis für diesen Skill“ angezeigt wird. Das Beispielszenario liegt in references/ dort. Jeder Shell-Befehl startet neu, daher ist eine in einem Befehl exportierte Variable im nächsten verschwunden: Beginne jeden Befehl, der sie benötigt, mit export SCRIPTS=<Basisverzeichnis>/scripts DEMO=<Demo-Verzeichnis>; (siehe unten), wie hier für die Vorabprüfung:

export SCRIPTS=<Basisverzeichnis>/scripts; bash $SCRIPTS/preflight.sh

Sie prüft auf agent-browser und ein Chrome dafür, ffmpeg/ffprobe mit libx264 sowie Kokoros Python-Umgebung und -Modell: entweder vorinstalliert (DEMO_VIDEO_VENV ist gesetzt) oder uv (das Kokoro und Pillow auf Python 3.12 ausführt, egal welches System-Python installiert ist) plus Zugriff auf Hugging Face. Wenn es mit einem Fehler ungleich Null beendet wird, stoppe: Teile dem Benutzer mit, was fehlt und wie es installiert wird, und beginne nicht mit dem Skript, den Daten oder der Aufnahme. Ersetze keine andere TTS-Engine, keinen anderen Rekorder und kein stummes Video ohne Nachfrage – das Ergebnis wäre nicht das, was der Benutzer erwartet. Ihre Warnungen (kein espeak-ng, keine Systemschriftart) blockieren nicht; erwähne sie.

$SCRIPTS/python führt die folgenden Python-Skripte in der Umgebung aus, die die uv.lock des Skills festlegt; Szenarien lesen ebenfalls $SCRIPTS. Behalte alle Arbeitsdateien in einem einzigen Demo-Verzeichnis (DEMO, ein absoluter Pfad) im Scratch-Pad, außerhalb des Repositorys. Wenn du nur innerhalb des Repositorys schreiben kannst oder das Video an einen Pull-Request von dort angehängt werden soll, verwende stattdessen einen Ordner darin, den git ignoriert: tmp/demo-video/<name> wenn git check-ignore -q tmp/demo-video erfolgreich ist, andernfalls einen anderen Ordner, der bestätigt, dass er ignoriert wird, damit nichts davon committed werden kann. Bearbeite .gitignore dafür nicht ohne Nachfrage.

Arbeitsablauf

1. Verstehe die Funktion und wähle die Geschichte

Lese den Branch-Diff, die Plan- oder PR-Beschreibung und die benutzernahen Texte der Funktion (i18n-Dateien, Tooltips, Hilfetexte). Die Tooltips definieren die Spalten und Optionen meist besser als alles andere – die Sprachausgabe sollte mit ihnen wortwörtlich übereinstimmen, was eine Zahl bedeutet.

Wähle einen Protagonisten (ein fiktives Unternehmen, das die Funktion wirklich nutzen würde) und eine Frage, die die Funktion für sie beantwortet. Ein Demo, das ein konkretes Szenario von der Frage zur Antwort durchläuft, ist besser als ein Rundgang durch jedes Steuerelement.

Frage den Benutzer nur nach dem, was das Video verändert: Zielgruppe (Kunden oder das Team), Ziellänge, Stimme und ob er das Drehbuch lesen möchte, bevor etwas aufgenommen wird. Standardeinstellungen: Kunden, ~3 Minuten, af_heart. Überspringe die Fragen, die sie bereits beantwortet oder hinfällig gemacht haben – jemand, der das Ganze von Anfang bis Ende ohne Zwischenfragen angefordert hat oder der offensichtlich nicht da ist, um zu antworten, erhält die Standardeinstellungen und keine Drehbuchprüfung.

2. Erstelle Demodaten, die die Geschichte erzählen

Leere oder zufällige Daten lassen eine Funktion kaputt aussehen. Baue Daten, deren Zahlen jeden Punkt auf dem Bildschirm offensichtlich machen: Grenzfälle, die auf dem ersten Bildschirm sichtbar sind, Summen, die sich sichtbar ändern, wenn eine Option umgeschaltet wird.

  • Isoliere sie. Erstelle eine separate Datenbank und starte die App auf einem eigenen Port; verwende niemals die Entwicklungsdaten des Benutzers wieder. Suche zuerst nach den eigenen Konventionen des Projekts (CLAUDE.md, Anleitungen zu Worktrees und Dev-Servern, Seed-Verzeichnisse). Wenn das Projekt ein Seed-Framework hat, schreibe einen wiederverwendbaren, branchenspezifischen Demo-Seed darin (z. B. db/seeds/demos/fahrradladen.rb) anstelle eines einmaligen Skripts – das nächste Demo kostet dann nichts. Frage vor dem Committen.
  • Datiere alles relativ zu heute, damit das Demo niemals veraltet.
  • Geschichte braucht Zeitreisen. Apps weigern sich, Bestellungen oder Buchungen in der Vergangenheit zu erstellen; erstelle jeden Datensatz zu dem Zeitpunkt, zu dem er wirklich erstellt worden wäre (z. B. Rails travel_to). Achte auf Zustände, die die gereiste Uhr ignorieren: Werte, die in einer Instanz zwischengespeichert wurden, die vor dem Reisen geladen wurde, und Statusspalten, die bei der Erstellung berechnet wurden. Lade sie innerhalb des Reiseblocks neu und aktualisiere sie.
  • Erstelle einen Snapshot des leeren Schemas (z. B. createdb -T), damit ein erneutes Seeds nach einer Korrektur ein Drop-and-Copy ist, keine Migration.
  • Öffne dann die Seite und überprüfe jede Zahl, die du erwähnen möchtest.

3. Schreibe das Drehbuch

Schreibe DEMO/scenes.json: [{"id": "01_intro", "text": "..."}, ...]. Eine Szene pro Idee, 10–15 Szenen. IDs sortieren in der Wiedergabereihenfolge.

  • Länge: Kokoro spricht mit ~150 Wörtern pro Minute, und jede Szene fügt ~1–3 s Aktionen und Pausen hinzu. Für 3 Minuten schreibe etwa 450 Wörter; 420 Wörter ergaben 2:40.
  • Sage dem Zuschauer, was er sieht, in der Reihenfolge, in der er es sieht. Nenne Steuerelemente mit ihrer Beschriftung auf dem Bildschirm.
  • Schreibe für das Ohr: kurze Sätze, Zahlen werden ausgeschrieben, wo sie sich seltsam lesen („fünfundzwanzig Prozent“).
  • Befolge die Vokabularregeln des Projekts (Glossare, reservierte Wörter) in der Sprachausgabe wie im Code.
  • Nenne, was auf dem Bildschirm ist, wenn es auf dem Bildschirm ist. Nenne keinen Filterwert, keine Menüoption oder keinen Datensatz, bevor der Zuschauer ihn sehen kann („alles, was Sam verkauft hat“, während die Mitarbeiterliste noch geschlossen ist). Setze die Wörter zum Öffnen des Menüs vor den Namen, damit das Szenario zuerst das Menü öffnen und den Zeiger auf die Option setzen kann, während sie gesprochen wird.
  • Beginne mit der eigenen Frage der Funktion. Der erste Satz muss eine Frage sein, die nur diese Funktion beantwortet. „Wie viel haben wir diesen Monat verkauft?“ beschreibt auch einen benachbarten Bericht; „Welcher Laden hat die meisten Starter-Kits verkauft?“ nicht. Überprüfe die verwandten Funktionen der App (andere Berichte, andere Einstellungsseiten), bevor du dich darauf festlegst.
  • Schreibe für die angegebene Zielgruppe. Ein Kundendemo zeigt, was sie tun können; ein internes Demo für Marketing oder Account Management behandelt auch, wie es Entscheidungen trifft, wie man es einschaltet, seine Grenzen und was man einem Kunden sagt, der fragt.

Wenn der Benutzer gebeten hat, das Drehbuch zu überprüfen, halte hier an und zeige es vor dem Rendern des Audiomaterials: zuerst eine kurze Gliederung (eine Zeile pro Szene: was gezeigt wird und die eine Behauptung, die sie aufstellt), plus alle Behauptungen, bei denen du dir unsicher bist, mit dem vollständigen Text darunter für diejenigen, die ihn sehen möchten. Ein Satz, der später geändert wird, bedeutet, dass diese Szene neu gerendert, das Szenario neu getaktet und der Take neu aufgezeichnet werden muss – dies ist also der günstigste Moment, um die Geschichte zu ändern. Andernfalls fahre fort; erwähne im Abschlussbericht, dass sich das Drehbuch in DEMO/scenes.json befindet.

4. Rendere die Sprachausgabe

export SCRIPTS=… DEMO=…; bash $SCRIPTS/python $SCRIPTS/narrate.py $DEMO

Schreibt tts/<id>.wav und tts/<id>.words.json mit Wort-Zeitstempeln und gibt die Länge jeder Szene aus. Einzelne Szenen mit --only <id> ... neu rendern. Stimmen: af_heart (natürlichstes US-Englisch), am_michael, bf_emma (britisches Englisch). Der erste Buchstabe der Stimme wählt die Sprache.

5. Führe jede Interaktion trocken durch

Führe vor der Aufnahme jeden Schritt einmal mit agent-browser aus und mache einen Screenshot. So findest du, was auf der Kamera kaputtgehen wird: falsche Routen, von Overlays verdeckte Elemente, Menüs, die beim Schließen abschicken, ein Layout, das sich verschiebt, wenn ein Badge erscheint. Schnappschuss-Referenzen (@e12) ändern sich bei jedem Seitenladen, verwende also stabile CSS-Selektoren (IDs, dom_ids, name=) im Szenario. Bevorzuge sie gegenüber demo.click_role: die Helfer gleiten den Zeiger zum Element eines Selektors, bevor sie handeln, aber agent-browser gibt keine Position für eine Rolle, daher springt ein Rollenklick mit dem Zeiger.

Verstecke nur für Entwickler gedachte Overlays (Profiler, Debug-Leisten) und blende einmalige Einblendungen wie „Angemeldet als…“ aus, indem du die Seite vor dem Take zweimal lädst (demo.prime(url)).

6. Schreibe das Szenario und nimm auf

Ein Szenario ist ein kurzes Python-Skript in DEMO, das SCRIPTS/recorder.py verwendet. Beginne mit references/example_scenario.py. Takte Aktionen auf Wörter mit s.at("wort"). Satzzeichen sind eigene Tokens, verwende also "Oktober" und nicht "Oktober,", und verwende nth=, wenn ein Wort zweimal vorkommt.

Bewege dich nur durch die Helfer. Rohe agent-browser-Zeigerbefehle teleportieren den Zeiger und ein Mausrad springt auf der Seite, was auf der Kamera wie ein Schnitt wirkt. demo.move, demo.hover und demo.click gleiten den Zeiger dorthin und kehren zurück, sobald er angekommen ist; demo.scroll animiert das Scrollen und kehrt zurück, sobald die Seite stillsteht. So ist jede Bewegung in voller Länge auf der Aufnahme zu sehen, bevor die nächste Halteposition ein Bild einfriert.

Lass den Zuschauer jedes Menü lesen. Eine Liste, die innerhalb einer Sekunde geöffnet und geschlossen wird, zeigt nichts. Verwende die Helfer, die auf der geöffneten Liste verweilen und den Zeiger auf der Option ruhen lassen, bevor sie sie auswählen:

  • demo.pick(auslöser_zum_öffnen, option_selector) für benutzerdefinierte HTML-Menüs.
  • Übergib scene=s, cue="<wort>" an einen der Helfer, um den Zeiger genau dann auf der Option zu platzieren, wenn der Sprecher sie nennt; das Menü öffnet sich etwa zwei Sekunden vorher. Das erfordert zwei Sekunden Sprachausgabe vor dem Hinweiswort in derselben Szene, ohne langsame Aktion dazwischen – die Montage gibt warning: … runs …s late aus, wenn sie es nicht kann, und die Lösung liegt im Drehbuch („Die dritte Wahl in diesem Menü teilt es…“ anstatt „Oder teile es…“) oder darin, die langsame Aktion in die vorherige Szene zu verschieben.
  • demo.pick_native(select_selektor, label) für ein natives <select>. Eine Headless-Aufnahme zeichnet das Popup eines nativen Selects nie, daher zeichnet dies eine Ersatzliste aus den echten Optionen des Selects, zeigt auf die Auswahl und wählt sie dann aus.

Ein Viewport pro Take. Ein Take, dessen Viewport sich während der Aufnahme ändert, kommt verzerrt heraus. Nimm ein Telefon-Segment als eigenen Take auf (demo.viewport(390, 844)); die Montage fügt es auf eine Leinwand ein. Ein kurzer Abschluss-Take danach kehrt zum Desktop zurück.

Nimm jeden Take auf: export SCRIPTS=… DEMO=…; python3 -I $DEMO/szenario.py desktop (und phone, outro, …). Die Aufnahme ist nicht zeitgesteuert: s.at() und s.until() schlafen nicht, sondern protokollieren einen Haltepunkt, sodass der Take nur so lange läuft wie seine Aktionen (ein 2-minütiges Video wird in unter einer Minute aufgenommen) und ein defekter Selektor oder ein Hinweiswort, das in der Sprachausgabe fehlt, innerhalb von Sekunden fehlschlägt. Das macht einen separaten Trockendurchlauf überflüssig; iteriere direkt an den Takes. Jeder Take schreibt <take>.mp4 und <take>.holds.json; die Montage friert das Bild an jedem Haltepunkt ein, bis die Sprachausgabe ihr Hinweiswort erreicht. Wenn ein Schritt mitten im Take fehlschlägt, korrigiere das Szenario und führe den gesamten Take erneut aus; die Haltepunkte müssen von einem sauberen Durchlauf stammen.

Jede Aktion muss die Seite in einem stabilen Zustand hinterlassen, bevor der nächste Hinweis kommt, da dieser letzte Bildausschnitt eingefroren wird: Lasse das time.sleep nach einem Neuladen oder einem Menü, das abschickt, da sonst ein eingefrorener Bildausschnitt eine halb geladene Seite zeigt. Alles, was nach einer eigenen Uhr läuft (ein Toast, der verblasst, ein Spinner, ein Countdown), schreitet nur fort, während das Szenario aktiv ist, daher friert ein Haltepunkt es mitten in der Bewegung ein. Um etwas aus dem Bild zu halten, warte, bis es beendet ist, bevor du zum nächsten Hinweis übergehst, wie es prime für Anmeldehinweise tut. Um etwas zu zeigen, wie einen „Gespeichert“-Toast, der erscheint und verblasst, setze ein time.sleep nach der Aktion, das den gesamten Effekt abdeckt, damit er vor dem nächsten Haltepunkt abläuft. Für eine Seite, die im Leerlauf kontinuierlich animiert, nimm diesen Take zeitgesteuert mit DEMO_TIMED=1 auf, was während der Aufnahme bis zu jedem Hinweiswort schläft und stattdessen <take>.timeline.json schreibt.

7. Zusammenstellung

Schließe zuerst den Browser (agent-browser close): Chrome und ffmpegs Encoder können zusammen den Speicher einer kleinen Maschine oder eines Containers sprengen.

export SCRIPTS=… DEMO=…; bash $SCRIPTS/python $SCRIPTS/assemble.py $DEMO --takes desktop phone outro --out <name>

Erzeugt DEMO/build/<name>.mp4 mit eingebrannten Untertiteln, einer weichen Untertitelspur und Sprachausgabe, plus <name>.srt.

8. Überprüfung vor der Übergabe

Du kannst das Video sehen, aber nicht hören. Überprüfe, was du kannst:

export DEMO=…; ffmpeg -v error -y -i $DEMO/build/<name>.mp4 -vf "fps=1/6,scale=480:-1,tile=4x8" -frames:v 1 $DEMO/build/grid.png   # lies es
export DEMO=…; ffmpeg -i $DEMO/build/<name>.mp4 -map 0:a -af volumedetect -f null - 2>&1 | grep volume                              # nicht stumm

Bestätige im Raster, dass jede Szene zeigt, was ihre Sprachausgabe beschreibt, die Untertitel niemals das verdecken, worauf gezeigt wird, und kein Bild eine halb geladene Seite oder einen Fehler einfängt. Greife einzelne Bilder zu den Hinweiszeiten (-ss <t>) für die wichtigen Momente.

Stoppe dann den Entwicklungsserver und übergebe das Video: auf dem eigenen Rechner des Benutzers kopiere es an einen Ort, den er finden kann, und opene es; woanders, gib seinen Pfad an oder hänge es an den Pull-Request an, wenn eines deiner Werkzeuge das tut. Wenn du Bericht erstattest, sage, was du nicht überprüfen konntest (Audio mit dem Ohr) und liste alle Abkürzungen auf, die genommen wurden, wie ein Ersatz-Menü.

Bereits bezahlte Fallstricke

  • agent-browser click schlägt mit „covered by“ fehl, während eine Seite noch lädt. recorder.ab wiederholt; ein Fehler, der die Wiederholungen überlebt, ist echt, also korrigiere den Selektor.
  • Menüs, die abschicken, wenn sie geschlossen werden, wie Checklisten, die bei Escape abschicken, brauchen ein paar Sekunden zum Neuladen. Warte darauf vor dem nächsten Klick, sonst landet dieser auf einem verschobenen Button.
  • Overmind weigert sich, über einem veralteten .overmind.sock zu starten. Entferne den Socket nur, nachdem du bestätigt hast, dass nichts darauf antwortet (overmind status).
  • Lange Scratchpad-Pfade überlaufen die Grenzen von Unix-Sockets; leite Sockets nicht dorthin.
  • Kokoro teilt lange Texte in Blöcke auf; narrate.py versetzt die Wort-Zeitstempel jedes Blocks, also takte nach Wort, niemals nach geschätzten Sekunden.

Verwandte Skills