Communitygithub.com

デモビデオスキル

ナレーションと字幕付きの画面録画デモビデオを生成します。スクリプト化されたブラウザ操作をagent-browserで録画し、ローカルのKokoroで音声合成、ffmpegで字幕を焼き付けます。ユーザーがデモビデオ、ウォークスルー、スクリーンキャスト、機能ツアー、説明動画などをリクエストした際に使用します。

デモビデオスキル とは?

このスキルは、Webアプリケーションの機能を短いデモビデオに変換するための包括的なワークフローを提供します。実際のブラウザ操作を録画し、画面内容を説明する音声ナレーションと字幕を自動生成します。すべての処理はローカルで完結し、agent-browserによる録画、Kokoroによる音声合成、ffmpegとPillowによる動画組み立てを行います。 中核となる設計思想は「ナレーションが時間軸の基準となる」ことです。まず音声をレンダリングし、各シーンの長さを計測してから、ブラウザ操作を対応するナレーションの単語に同期させます。これにより、録画の速度変動に影響されない安定したタイミング制御を実現しています。 このスキルは、Claude CodeなどのAIエージェント上で動作し、ユーザーが「デモ動画を見せて」「チームに機能を紹介したい」「画面録画にナレーションを付けたい」などの要求をした際に自動的に呼び出されます。プリフライトチェック、スクリプト作成、ナレーション生成、録画、組み立て、レビューという一連の工程をガイドし、プロダクション品質のデモ動画を効率的に生成します。

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

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

デモビデオ

機能を短いビデオに変換します。実際のブラウザで実際の操作を行い、画面上の内容を説明するナレーションと字幕を付けます。すべてローカルで動作します。agent-browserが録画し、Kokoroが音声を生成し、ffmpegとPillowが動画を組み立てます。

成功の鍵となる考え方は、ナレーションを時計として使うことです。まず音声をレンダリングし、各シーンの長さを計測してから、各ブラウザ操作をそれを説明する単語に同期させます。録画にナレーションを合わせようとしてはなりません。録画のペースは変動しますが、WAVファイルのペースは変動しません。録画自体はナレーションを待ちません。操作は連続して実行され、組み立て時にナレーションが必要な時間だけフレームをフリーズします。

ツール

このスキルのスクリプトは、ベースディレクトリの scripts/ にあります。ベースディレクトリはスキルがロードされたときに「このスキルのベースディレクトリ」として表示されます。サンプルシナリオは同じく references/ にあります。各シェルコマンドは毎回新しく起動されるため、あるコマンドでエクスポートした変数は次のコマンドでは使えません。変数が必要なコマンドはすべて、次のように export SCRIPTS=<base directory>/scripts DEMO=<demo directory>; で始めてください(以下はプリフライトの例)。

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

プリフライトは、agent-browser とそれ用のChrome、libx264対応の ffmpeg/ffprobe、KokoroのPython環境とモデル(DEMO_VIDEO_VENV が設定されている場合はプリインストール済み、そうでなければ uv でPython 3.12上でKokoroとPillowを実行し、Hugging Faceへのアクセスが必要)をチェックします。0以外の終了コードで終了した場合は、処理を停止し、ユーザーに何が不足していて、どうインストールすればよいかを伝えてください。スクリプト、データ、録画の作成を開始しないでください。別のTTSエンジン、レコーダー、サイレントビデオに代用しないでください。結果はユーザーが求めたものになりません。警告(espeak-ng がない、システムフォントがない)は処理をブロックしませんが、言及してください。

$SCRIPTS/python は、スキルの uv.lock で固定された環境で以下のPythonスクリプトを実行します。シナリオも $SCRIPTS を読み取ります。すべての作業ファイルは、スクラッチパッド内の1つのデモディレクトリ(DEMO、絶対パス)に、リポジトリの外部に保持してください。リポジトリ内にのみ書き込める場合、またはそこからプルリクエストにビデオを添付する場合は、代わりにgitが無視するフォルダ(tmp/demo-video/<name>、ただし git check-ignore -q tmp/demo-video が成功する場合。成功しない場合は、確認した別の無視フォルダ)を使用してください。このために .gitignore を聞かずに編集しないでください。

ワークフロー

1. 機能を理解し、ストーリーを選ぶ

ブランチの差分、計画、PRの説明、機能のユーザー向けコピー(i18nファイル、ツールチップ、ヘルプテキスト)を読みます。ツールチップは通常、列やオプションを最もよく定義しています。ナレーションは、数値が何を意味するかについて、ツールチップと一字一句一致する必要があります。

主人公(機能を実際に使う架空の企業)と、その機能が彼らに答える質問を選びます。1つの具体的なシナリオを質問から答えまで説明するデモは、すべてのコントロールをツアーするよりも優れています。

ビデオを変更するものについてのみユーザーに質問します。対象ユーザー(顧客かチームか)、目標時間、音声、スクリプトを録画前に確認するかどうか。デフォルト:顧客向け、約3分、af_heart。ユーザーがすでに回答した、または無意味になった質問はスキップします。すべてを一度に完全に依頼した人、または明らかに不在で回答できない人には、デフォルトを使用し、スクリプトレビューは行いません。

2. ストーリーを伝えるデモデータを作成する

空のデータやランダムなデータは、機能が壊れているように見えます。各ポイントが画面上で明白になるデータを作成します。最初の画面で確認できるエッジケース、オプションを切り替えたときに目に見えて変化する合計など。

  • 分離します。 別のデータベースをシードし、アプリを独自のポートで実行します。ユーザーの開発データを再利用しないでください。プロジェクト独自の規約(CLAUDE.md、ワークツリーと開発サーバーのガイド、シードディレクトリ)を最初に探します。プロジェクトにシードフレームワークがある場合は、1回限りのスクリプトではなく、その中に再利用可能な業界別デモシード(例:db/seeds/demos/bike_shop.rb)を記述します。次のデモのコストはゼロになります。コミットする前に確認してください。
  • すべての日付を今日からの相対日付にします。 デモが古くなりません。
  • 履歴にはタイムトラベルが必要です。 アプリは過去の注文や予約の作成を拒否します。各レコードを実際に作成されたであろう時点で作成します(例:Railsの travel_to)。トラベルされた時計を無視する状態(トラベル前にロードされたインスタンスにメモ化された値、作成時に計算されるステータス列)に注意してください。トラベルブロック内でそれらをリロードし、リフレッシュします。
  • 空のスキーマのスナップショットを作成します(例:createdb -T)。 修正後の再シードがマイグレーションではなく、ドロップ&コピーで済むようにします。
  • 次に、ページを開き、言及する予定のすべての数値を確認します。

3. スクリプトを作成する

DEMO/scenes.json を作成します。[{"id": "01_intro", "text": "..."}, ...]。1シーンにつき1つのアイデア、10~15シーン。IDは再生順にソートされます。

  • 長さ: Kokoroは毎分約150語で話し、各シーンに約1~3秒のアクションとポーズが追加されます。3分間で約450語を記述します。420語で2分40秒になりました。
  • 視聴者が見ているものを、見ている順に言います。コントロールは画面上のラベルで名前を指定します。
  • 耳で聞いてわかるように書きます。短い文、数字は不自然に読まれる場合はスペルアウトします(「twenty-five percent」)。
  • コードの場合と同様に、ナレーションでもプロジェクトの語彙ルール(用語集、予約語)に従います。
  • 画面上にあるものを、それが画面上にあるときに名前を指定します。 視聴者が見る前にフィルター値、メニューオプション、レコードの名前を指定しないでください(スタッフリストがまだ閉じているときに「Samが販売したすべて」と言わない)。メニューを開く単語を名前の前に置き、シナリオが最初にメニューを開き、そのオプションが話されているときにポインターをその上に置けるようにします。
  • 機能自身の質問で始めます。 最初の文は、この機能だけが答えられるものでなければなりません。「今月の売上はいくらですか?」は隣接するレポートも説明しますが、「どの店舗が最もスターターキットを販売しましたか?」は説明しません。決定する前に、アプリの兄弟機能(他のレポート、他の設定ページ)を確認してください。
  • 指定された対象ユーザー向けに書きます。 顧客向けデモは彼らが何ができるかを示します。マーケティングやアカウント管理向けの社内デモは、それがどのように決定するか、有効にする方法、制限、および顧客から尋ねられた場合に何を伝えるべきかについてもカバーします。

ユーザーがスクリプトのレビューを希望した場合は、ここで停止し、音声をレンダリングする前にスクリプトを表示します。最初に短い概要(シーンごとに1行:表示内容と1つの主張)、次に確信が持てない主張、さらに全文を必要に応じて表示します。後で文を変更すると、そのシーンの再レンダリング、シナリオの再キューイング、テイクの再録画が必要になるため、これはストーリーを変更するのに最も安価な瞬間です。それ以外の場合は続行し、最終レポートでスクリプトが DEMO/scenes.json にあることを言及します。

4. ナレーションをレンダリングする

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

tts/<id>.wav と tts/<id>.words.json(単語ごとのタイムスタンプ付き)を書き込み、各シーンの長さを出力します。単一のシーンを再レンダリングするには、--only <id> ... を使用します。音声:af_heart(最も自然な米語)、am_michael、bf_emma(英国)。音声の最初の文字が言語を選択します。

5. すべてのインタラクションをドライランする

録画前に、agent-browser を使用して各ステップを1回実行し、スクリーンショットを撮ります。これにより、カメラで壊れるもの(間違ったルート、オーバーレイで覆われた要素、閉じると送信されるメニュー、バッジが表示されたときにレイアウトがシフトするなど)を発見できます。スナップショット参照(@e12)はページを読み込むたびに変更されるため、シナリオでは安定したCSSセレクター(id、dom_id、name=)を使用します。demo.click_role よりもこれらを優先します。ヘルパーはアクションの前にポインターをセレクターの要素に移動しますが、agent-browserはロールの位置を与えないため、ロールクリックではポインターがジャンプします。

開発専用のオーバーレイ(プロファイラー、デバッグバー)を非表示にし、「Logged in as…」などの1回限りのフラッシュは、テイク開始前にページを2回読み込むことでクリアします(demo.prime(url))。

6. シナリオを作成し、録画する

シナリオは、SCRIPTS/recorder.py を使用する DEMO 内の短いPythonスクリプトです。references/example_scenario.py から始めます。アクションを単語に s.at("word") でキューイングします。句読点はそれ自体がトークンであるため、"October" をキューイングし、"October," ではありません。単語が2回出現する場合は nth= を使用します。

ヘルパーのみを介して移動します。 生の agent-browser ポインターコマンドはポインターをテレポートさせ、マウスホイールはページをジャンプさせるため、カメラ上ではカットとして映ります。demo.move、demo.hover、demo.click はポインターをそこに移動させ、到着したら戻ります。demo.scroll はスクロールをアニメーションさせ、ページが静止するまで戻りません。したがって、すべての動きが完全に録画に含まれ、次のホールドがフレームをフリーズする前に表示されます。

視聴者がすべてのメニューを読めるようにします。 1秒以内に開いて閉じるリストは何も表示しません。ヘルパーを使用します。ヘルパーは開いたリストに留まり、選択する前にポインターをオプションの上で休ませます。

  • demo.pick(open_trigger, option_selector) はカスタムHTMLメニュー用です。
  • いずれかのヘルパーに scene=s, cue="<word>" を渡すと、ナレーターがその単語を発音したときにポインターがオプションの上に正確に配置されます。メニューは約2秒前に開きます。これには、同じシーン内でキュー単語の前に2秒間のナレーションが必要であり、その間に遅いアクションがあってはなりません。組み立て時に warning: … runs …s late と表示された場合は、スクリプトの修正(「そのメニューの3番目の選択肢は…を分割します」ではなく「またはそれを分割します」)または遅いアクションを前のシーンに移動することで対応します。
  • demo.pick_native(select_selector, label) はネイティブの <select> 用です。ヘッドレス録画ではネイティブセレクトのポップアップは描画されないため、これはセレクトの実際のオプションから代わりのリストを描画し、選択肢を指してから選択します。

1テイクにつき1つのビューポート。 録画中にビューポートが変更されるテイクは歪んで出力されます。電話セグメントは独自のテイクとして録画します(demo.viewport(390, 844))。組み立て時にキャンバスに収めます。その後の短いクロージングテイクでデスクトップに戻します。

各テイクを録画します:export SCRIPTS=… DEMO=…; python3 -I $DEMO/scenario.py desktop(および phone、outro、…)。録画は時間指定されていません。s.at() と s.until() はスリープせず、ホールドポイントをログに記録するだけです。そのため、テイクはアクションの実行時間と同じだけの長さで実行され(2分のビデオは1分未満で録画されます)、壊れたセレクターやナレーションにないキュー単語は数秒以内に失敗します。これにより、個別のドライランは不要になります。テイクを直接反復して改善します。各テイクは <take>.mp4 と <take>.holds.json を書き込みます。組み立て時に各ホールドポイントでフレームがフリーズされ、ナレーションがそのキュー単語に達するまで保持されます。テイクの途中でステップが失敗した場合は、シナリオを修正してテイク全体を再実行します。ホールドはクリーンランから取得する必要があります。

すべてのアクションは、次のキューの前にページが安定した状態になるようにする必要があります。最後のフレームがフリーズされるためです。リロードや送信するメニューの後には time.sleep を残します。そうしないと、ホールドされたフレームに半ロードのページが表示されます。独自のクロックで実行されるもの(フェードするトースト、スピナー、カウントダウン)は、シナリオが動作している間だけ進行するため、ホールドは途中でフリーズします。これらをカメラに映さないようにするには、次のキューの前にそれが終了するまで安定させます(prime がログイン通知で行うように)。「Saved」トーストが表示されてフェードするなど、表示したい場合は、アクションの後にエフェクト全体をカバーする time.sleep を配置し、次のホールドの前に再生されるようにします。アイドル中に継続的にアニメーションするページの場合は、そのテイクを DEMO_TIMED=1 で時間指定録画します。これは録画中に各キュー単語までスリープし、代わりに <take>.timeline.json を書き込みます。

7. 組み立てる

最初にブラウザを閉じます(agent-browser close)。Chromeとffmpegのエンコーダーが一緒に動作すると、小型マシンやコンテナのメモリを超える可能性があります。

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

焼き込み字幕、ソフト字幕トラック、ナレーションを含む DEMO/build/<name>.mp4 を生成します。さらに <name>.srt も生成します。

8. 引き渡す前にレビューする

ビデオは見えますが、音声は聞こえません。確認できることを確認します。

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   # 読む
export DEMO=…; ffmpeg -i $DEMO/build/<name>.mp4 -map 0:a -af volumedetect -f null - 2>&1 | grep volume                              # 無音でない

グリッドで、各シーンがそのナレーションで説明されている内容を表示していること、字幕が指しているものを決して覆っていないこと、フレームが半ロードのページやエラーを捉えていないことを確認します。重要な瞬間については、キュータイムで単一フレームを取得します(-ss <t>)。

その後、開発サーバーを停止し、ビデオを引き渡します。ユーザーの自分のマシン上では、ユーザーが見つけられる場所にコピーして open します。それ以外の場所では、パスを指定するか、ツールがプルリクエストに添付できる場合は添付します。報告する際には、確認できなかったこと(実際の音声)と、代用メニューなどの取られたショートカットをリストアップします。

すでに対処済みの落とし穴

  • agent-browser click は、ページがまだ安定していない間に「covered by」で失敗します。recorder.ab が再試行します。再試行後も失敗する場合は実際の問題であるため、セレクターを修正します。
  • 閉じるときに送信するメニュー(Escapeで送信するチェックリストなど)は、リロードに数秒かかります。次のクリックの前にこれを待つ必要があります。そうしないと、移動したボタンにクリックが当たります。
  • Overmindは古い .overmind.sock があると起動を拒否します。ソケットを削除する前に、そのソケットで応答するものがないことを確認します(overmind status)。
  • 長いスクラッチパッドパスはUnixソケットの制限を超えます。ソケットをそこに向けないでください。
  • Kokoroは長いテキストをチャンクに分割します。narrate.py は各チャンクの単語タイムスタンプをオフセットするため、推測された秒数ではなく、単語でキューイングします。

関連スキル