デモ動画
実際のアプリケーション(モックや過去の動画は使いません)を新しいヘッドレスブラウザで起動し、Edge TTSでナレーションを付け、字幕を焼き込み、MP4をレンダリングします。デモはそのプロセス用に作成された2つのファイル narration.json と flow.cjs で構成されます。それ以外はすべて、このフォルダ内の demo-video-kit コマンドが担当します。動作例は examples/todomvc/ にあります。
以下 <kit> は node <このフォルダ>/bin/demo-video-kit.js を意味します(このフォルダは、例えば ~/.claude/skills/demo-video です)。ユーザーのプロジェクトにキットがインストールされている場合は npx demo-video-kit でも同様に動作します。<kit> help ですべてのコマンドを表示できます。
リクエストに応じた作業内容
| リクエスト | 作業内容 |
|---|---|
| 「<プロセス>のデモを作成して」 | 以下の全ワークフローを、ユーザーのプロジェクトの demos/<プロセス>/ に実施 |
| 「<プロセス>の画面が変わったのでデモを作り直して」 | 新しい画面でドライラン(手順2)を実行し、flow.cjs を修正。必要に応じて narration.json も調整し、手順4~8を実施 |
| 「既存の<プロセス>動画にボックスを追加して」(新規録画なし) | 実行中のアプリでボックス位置を計測し、annotations.json を作成、<kit> annotate <demo-dir> を実行(ハイライトのセクションを参照) |
| 「ハイライトなしで」 | すべての highlight オプションを除外。それ以外は変更なし |
| 「声/速度/言葉を変えて」 | narration.json の voice、rate、セグメントの text を編集し、手順4~8を実施:クリップの長さが画面表示のタイミングを決めるため、動画は必ず再録画 |
ルール(常に)
- 実際の、新鮮なものを使う。 新しいブラウザセッション。実際のアプリケーション。実際のデータ。結果や数値、ステータスを偽装しない。アプリケーションが成功を表示した後にのみ成功をアナウンスする。
- 安全に。 ローカルまたはテスト環境のみ:実行するたびにフローが作成するものは実際に作成される。認証情報を印刷したり保存したりしない。認証情報は環境変数から取得する(例:
DEMO_EMAIL、DEMO_PASSWORD)。パスワードはドットで表示するだけ。既知のテストアカウントがない場合はユーザーに確認する。 - 画質。 1920x1080、16:9、30fps、H.264 + AAC MP4。ヘッドレスで録画するため何もカットされない。各ターゲットにスムーズに移動する可視カーソルと、軽いクリックリング。ズーム、カメラ移動、凝ったトランジションはなし。何かに作用する前に、完全に画面内にスクロールする。長いページは上から下へゆっくりと読める速度で読む。
- 音声。 Edge TTS、デフォルトの声は
en-IN-NeerjaNeural(他の声は<kit> voices en-または別のフィルターで一覧表示)。ナレーションセグメントごとに1つのクリップ。クリップが重なることはない。ナレーションはそのシーンに現在表示されている内容か、これから起こることだけを説明する。 - 7秒以上の無音は作らない。 ナレーションなしの区間が7秒に達してはいけない。レコーダーが警告を出した場合、または
verify.pyが失敗した場合、そのセグメントのナレーションを長くするか分割し、またはアクションを高速化して再録画する。 - ペース。 急がない。入力した値はすべて、次の操作に進む前に画面に表示されていることを確認する。
- アプリケーションに従う。 ルート、ラベル、ボタンは実行中のアプリから取得し(ドライラン)、記憶に頼らない。ボタンはアプリが表示している名前で呼ぶ。
- 出力はGit管理対象外に。 生成された音声、ビルド、MP4は、特に依頼がない限りコミットしない。
- ハイライト。 すべてのデモにおいて、ナレーションが指す要素に赤いボックスを表示する。ただしリクエストに「ハイライトなし」と明記されている場合は除く。
ハイライト
赤いボックス(4px、#E11D48、要素から6pxのクリアランス)が、録画中にページ上にライブで描画されるため、スクロールに追従し、動画の一部となる。デフォルトでボックスが表示される要素:画面に至るまでにクリックされた各メニュー項目、プロセスを開始するボタン、確認ページで入力されたデータの各ブロック、最終送信ボタン。入力中のすべてのフィールドにボックスを付ける必要はない。カーソルと入力動作で十分示されている。
flow.cjs でのAPI:
goto / click / type / pick (loc, ..., { highlight }):highlight: trueでカーソルが移動する要素にボックスを付ける。highlight: <locator>で別の要素(例:その行全体やブロック)にボックスを付ける。- ボックスはカーソルが次のターゲットに移動するまで保持される。
{ keep: true }で現在のボックスも保持。k.clearHighlights()でボックスを削除。k.highlight(loc)でカーソルを移動せずにボックスを追加。 - 各ボックスは
marksにhighlightエントリーとしてbuild/timeline-raw.jsonに追加される。
ボックスなしで録画された動画に対しては、<kit> annotate <demo-dir> で後から annotations.json に基づいてボックスを描画できる。これはフォールバックであり、通常は再録画の方が簡単で正確。
ワークフロー
- セットアップ。
<kit> doctorを実行し(セットアップ参照)、アプリがそのURLで応答することを確認する。 - ドライラン。 Playwrightブラウザツールを使って、録画なしで実際のフローを最終送信の手前まで実行する。各画面のルート、ラベル、
getByRoleの名前、ドロップダウンオプション、ダイアログ、最終ボタン、その後の動作を記録する。 - デモを記述する:
<kit> init <demo-dir> --url <アプリのURL>で、すぐに実行できるスターターが作成される(またはexamples/todomvc/をコピーする)。その後編集する:narration.json:タイトル、声、速度、pronounce(書き言葉→話し言葉)、およびセグメント{id, scene, text}。アクションに時間がかかるシーンは複数のセグメントに分割し、各セグメントのナレーションの長さがそのアクションの所要時間とほぼ一致するようにする。flow.cjs:runDemo(__dirname, async (k) => { ... })の中に、ナレーションセグメントごとに1つのk.segment(id, async (at, clip) => { ... })を記述する。k.click / k.type / k.pick / k.goto / k.scrollByを使用。at(seconds)でクリップ内の特定の時点(例:clip.cues[0].end)を待つ。要素を待つこと。waitForLoadState('networkidle')は使わない。検証結果を返す。require(process.env.DEMO_KIT || 'demo-video-kit')はそのままにしておく。コマンドがDEMO_KITを設定する。
- 動画を作成する。
<kit> run <demo-dir>(DEMO_BASE_URLと必要な認証情報を環境変数に設定)で、音声生成、録画、レンダリング、検証を実行する。レコーダーからの各WARNING行、および検証からの各FAIL行はルール5に違反しているため、修正して再実行する。flow.cjsのみを変更した場合は--from recordで音声生成をスキップ。字幕やエンコードのみを変更した場合は--from renderで録画をスキップ。 - 確認する。
build/check/contact-sheet.png:すべてのフレームで、全画面、カーソル、正しい手順、読みやすい字幕、ナレーションが指す要素のボックスが表示されていることを確認する。動画は<demo-dir>/<フォルダ名>.mp4にある。 - 引き渡す。 MP4のパス、長さ、フォーマット、最大無音時間、実行によって作成されたもの、およびアプリの動作の違いによりリクエストと異なる点を報告する。
セットアップ
Node 18+ と Google Chrome(または Microsoft Edge:レコーダーはフォールバックとして使用する、または DEMO_BROWSER=msedge を設定)。このフォルダで npm install を一度実行する。音声(msedge-tts)、ffmpeg(ffmpeg-static)、playwright-core が付属し、レコーダーは初回実行時にPlaywright自身の小型ffmpegを取得する。<kit> doctor ですべてをチェックする。これには音声に必要なインターネット接続も含まれる。
既知の落とし穴
| 落とし穴 | 回避方法 |
|---|---|
| グレーの帯、ページ下部が切れる | 可視ブラウザウィンドウでは録画しない。demo-kit.cjs は1920x1080でヘッドレス録画 |
waitForLoadState('networkidle') がポーリングするアプリでハングする | 代わりに要素を待つ |
| ヘッドレスUIダイアログが非表示と報告される | ダイアログ自体ではなく、その中のフィールドを待つ |
| ドロップダウン内でのEscapeがダイアログ全体を閉じる | Escapeの代わりにオプションを選択する |
| 多くのフィールドを入力中に無音時間が発生する | そのセグメントのナレーションを分割する。type() には { delay }(デフォルト32ms)を使用 |
| CSSで大文字表示されているラベル | 表示されている大文字ではなく実際のテキストと一致させる |
| ラベルのみにボックスが付き、そのブロック全体に付かない | ラベルの親(xpath=..)または祖父母(xpath=../..)にボックスを付ける |