デモ動画
実際のアプリを動作させたスクリプト化されたウォークスルーを録画します。視聴者は製品が実際に動作している様子を見ることができ、遅い部分(モデルの応答、ジョブなど)は早送りされ、各場面には注目すべき点を説明する短いキャプションが付きます。毎回同じフォーマットを使用するため、セグメントを1つの動画に結合することができます。
ハウスフォーマット
新しいセグメントが以前のものと一致するように、以下の指定を正確に守ってください:
- 動画: 1440×900、30fps、H.264(
libx264 -preset slow -crf 18)、yuv420p、+faststart、音声なし。 - タイトルカードとエンドカード: 全画面白背景、中央揃え:オプションで小さな大文字グレーのブランド行(
kit.startでbrandを設定、デフォルトなし)、46px太字タイトル、20pxグレーサブタイトル。約2.8秒間表示し、フェードアウト。セグメントはタイトルカードで始まり、最後のセグメントは1行のテイクアウェイカードで終了します(例:「アイデア→評価→ライブテスト→出荷」/「その間のアプリデプロイは不要」)。 - キャプション: 濃色のピル型背景(
rgba(17,17,17,.88))、白の17pxセミボールド Latoフォント、下部中央。キャプションごとに1つのアイデア、約60文字以内、文頭のみ大文字、最後のピリオドなし、現在形、視聴者側から見た文章で記述します(「結果がチャットの横にアーティファクトとして開く」)。ビートが変わるときにキャプションを変更し、各キャプションを約2.5〜4秒間表示します。 - クリック: クリックの直前に、ターゲット上に紫のリップル(
rgba(122,71,255,…))が表示されます。 - タイピング: 1キーずつ表示(1キーあたり約25ms、長いテキストでは14ms)。
- 待機: 遅い処理は
ff()内で実行され、約2秒で再生されます。それ以外はリアルタイムで再生されます。 - アーキテクチャシーン: 固定の1440×900の白いページで構成(タイトル→before→問題の強調→after→メトリクスカウントアップ→機能一覧)、Latoフォント、インク色
#111、アクセントカラー紫#6f43d6。「before」タグはグレー、「after」タグは紫。assets/architecture-example.htmlがリファレンスです:そのCSSと構造を再利用し、コンテンツを変更します。
動画の保存場所:デモリポジトリ
すべての動画は、チームのデモリポジトリ(デフォルト ~/src/demos、または $DEMOS_REPO)内のフォルダに保存され、録画スクリプトと demo.json マニフェストが含まれます。製品リポジトリにはデモコードは含まれません。録画ファイルはコミットされません。マニフェストの output にMP4の出力先が指定されます。
~/src/demos/
package.json playwright-core + ffmpeg-static(一度だけnpm install)
<video>/
demo.json マニフェスト
architecture.html 「scenes」セグメント(オプション)
record-<part>.js 「walkthrough」セグメント、demo-kitで作成
.build/ セグメントごとのMP4とフレーム(gitignore対象)
マニフェスト(demo.json):
{
"name": "<video-name>",
"title": "1行:動画が示す内容",
"description": "誰のための動画か、伝えたいポイント",
"owner": "email",
"repos": ["録画した機能が存在するリポジトリ(ブランチ指定)"],
"needs": [{ "what": "サービス名", "check": "起動時に2xx/3xxを返すURL", "start": "起動方法" }],
"reset": "クリーンな状態にする方法と、録画が残すもの",
"segments": [
{ "name": "architecture", "kind": "scenes", "page": "architecture.html", "timeline": "0:3000,1:4500" },
{ "name": "walkthrough", "kind": "walkthrough", "script": "record-walkthrough.js" },
{ "name": "intro", "kind": "video", "file": "~/path/to/existing.mp4" }
],
"output": "~/Downloads/<name>.mp4"
}
このスキルの examples/sift/ はリファレンスフォルダです。同じフォルダに含まれる小さなサンプルアプリの2セグメント動画(シーンページと実際のウォークスルー)が含まれています。
デモリポジトリがまだクローンされていない場合は、作成します(git init、上記の package.json、および node_modules/、.build/、*.mp4、*.jpg、*.png、*-frames/、*-timeline.json を含む .gitignore を作成)。リモートがまだないことをユーザーに伝えます。
ツール(scripts/ 内、このスキルからの相対パス)
| スクリプト | 機能 |
|---|---|
find-demos.js | node find-demos.js [root…] [--json]:デモリポジトリ内のすべての動画を、セグメント、リポジトリ、出力、最終録画日時とコミット日時とともに一覧表示します。 |
record.js | node record.js <video-dir> [--only <segment>]:needs を確認し、各セグメントを録画・エンコードして .build/ に保存し、それらを output に結合します(古い動画は *.previous.mp4 として保持されます)。デモリポジトリのルートから実行します。 |
demo-kit.js | ウォークスルースクリプト用ライブラリ:kit.start() は page、title/untitle、caption、tap、type、ff、scroll/scrollTop、ripple、navigate、startCast、finish を提供します。フレームはディスクにストリーミングされます。 |
encode.js | node encode.js <frames-dir> <timeline.json> <out.mp4>:MP4を構築し、ff() ウィンドウを圧縮します。 |
record-scenes.js | node record-scenes.js <page.html> <out.mp4> "0:3000,1:4500,…":window.step(n) を定義するシーンページを録画します。 |
concat.js | node concat.js <out.mp4> <a.mp4> <b.mp4> …:セグメントを結合し、ハウスフォーマットに再エンコードします。 |
review.js | node review.js <video.mp4> <out-dir> [stills] [at=s,s,…]:2×2のコンタクトシートを作成して確認します。 |
assets/example-walkthrough.js はキットを使用した完全な実例です(examples/sift/record-walkthrough.js は実際の例です)。新しいウォークスルーを作成する前に必ず読んでください。動画内でのアプリ切り替え、早送りジョブ、エンドカードを含む構造が示されています。
ワークフロー
0. 既存のものを確認
まず find-demos.js を実行します。ユーザーが既にフォルダが存在する動画を再録画または拡張したい場合は、そのフォルダで作業します。UI変更後は record.js <folder>(または --only <segment>)で再録画するか、マニフェストにセグメントを追加します。同じ動画に2つ目のフォルダを作成しないでください。本当に新しい動画の場合のみ、新しいフォルダを作成し、動画の内容に応じた名前(ケバブケース)を付けます。
1. まずストーリーボードを作成
コードを作成する前に、ストーリーボードを番号付きセクションのリストとして書き、各セクションのビート(画面で何が起こるか、キャプション)を定義します。事前に以下を決定します:
- 実際の動作が必要なものと、シミュレート可能なもの。 実際の動作(実際のモデル呼び出し、実際の検索)を優先します。データをシミュレートする必要がある場合(例:数週間分のライブトラフィック)は、キャプションとアプリ自体(ラベル付け可能な場合)の両方でその旨を明示します。視聴者はデモを額面通りに受け取るため、シミュレート結果を実際のものとして提示することは誤解を招きます。
- クリックしてはならないもの。 共有環境に書き込む操作(ボードの保存、メール送信、購入)。ボタンを表示しますが、押しません。
- 長さ: セグメントあたり約60〜90秒。3分の結合動画でも問題ありません。各セグメントは1つのポイントを伝える必要があります。
会話からユーザーの目的が明確な場合は、作業中のアップデートでストーリーボードを共有してください。動画のポイントが不明確な場合のみ質問します。
2. 動画のフォルダを設定
<demos repo>/<video>/ を作成し、demo.json を記述します(needs には動画が触れるすべてのサービスの check URL を入力し、reset を記述します)。デモリポジトリのルートで npm install を実行します(node_modules がない場合)。キットとスクリプトは、カレントディレクトリから playwright-core と ffmpeg-static を解決するため、すべてのコマンドをリポジトリルートから実行してください。Chromeがインストールされている必要があります(channel: 'chrome'、Playwrightブラウザのダウンロードは不要)。
3. アプリの事前確認
- 動画が触れるすべてのサービスが起動していることを確認し(各URLをcurl)、ローカルHTTPSが機能していることを確認します(キットは自己署名証明書を無視します)。
- 状態をリセットして、クリーンな状態から開始できるようにします(新しいDB、localStorageのクリア)。状態が汚れている場合はスクリプトが即座にエラーをスローするようにします。3分後に失敗するテイクは、実際のモデル呼び出しと時間を無駄にします。
- 動画がポーリングする遅いエンドポイントの時間を計測します。4秒の結果クエリはカメラ上でローディングスケルトンとして表示されます。クエリを修正するか、待機時間を調整します。
- 小さなPlaywrightスクリプトで各キーステートのスクリーンショットを撮り、確認します:レイアウト、折り返し、壊れた画像、1440×900で表示される範囲。これは録画よりもコストが低いです。
4. 録画スクリプトを作成
assets/example-walkthrough.js の構造をコピーします:
const kit = require(process.env.DEMO_KIT || `${process.env.HOME}/.claude/skills/demo-video/scripts/demo-kit`);
const demo = await kit.start(); // record.jsがフレームとタイムラインパスを提供
await demo.page.goto(URL, { waitUntil: 'networkidle' });
await demo.title('機能名', 'ユーザーができること');
await demo.startCast(); // タイトルがページを覆った後に開始
await demo.sleep(2800); await demo.untitle(); await demo.sleep(900);
await demo.caption('…'); await demo.tap(locator); await demo.type(locator, 'テキスト');
await demo.ff(() => page.waitForFunction(() => /* 応答完了 */ true, null, { timeout: 180000 }));
await demo.finish();
- 安定したセレクタ(
aria-label、id、data-*)を使用し、実際のDOMと照合します。 ff()内では固定スリープではなく、実際の完了条件を待ち、余裕を持ったタイムアウトを設定します。- キャプションが何かを説明する場合、先にそれを画面にスクロールします(コンテンツが要素内でスクロールするアプリの場合は
demo.scroll(px, 'main'))。 demo.navigate(url)でページやアプリを切り替えます。新しいページでスクリーンキャストを再開します。- アーキテクチャセグメントの場合は、サンプルからシーンページを作成し、
record-scenes.jsで録画します。また、矢印キーとスペースキーでのナビゲーション(サンプルにあります)を追加して、ライブプレゼンテーションでも同じページが使用できるようにします。
5. 録画
デモリポジトリのルートから、長時間のタイムアウトを設定して動画全体(または1つのセグメント)をフォアグラウンドで実行し、ログを出力します:
set -o pipefail; node ~/.claude/skills/demo-video/scripts/record.js <video> [--only <segment>] 2>&1 | tee <video>/.build/record.log
tail にパイプしないでください。レコーダーの終了コードが隠され、失敗したテイクが成功したように見えます。バックグラウンドで実行する必要がある場合は、実際のPIDを待機します(while kill -0 $PID; do sleep 5; done)。「完了」通知はプロセスが終了する前に届く可能性があるため、信頼しないでください。
6. レビュー
record.js は既にエンコードと結合を実行し、レビューコマンドを出力します:
node ~/.claude/skills/demo-video/scripts/review.js <output.mp4> <video>/.build/review 16
1つのセグメントを確認するには、<video>/.build/<segment>.mp4 をレビューします。
動画が完成する前に、すべてのコンタクトシートを確認してください。 以下をチェックします:
- 各キャプションがその時点の画面内容と一致していること
- キャプションが参照するものが画面内に表示されており、スクロールしなければ見えない位置にないこと
- 壊れた画像や代替テキスト、埋め込まれたスケルトン、エラートースト、折り返されたりクリップされたヘッダー、デバッグUIがないこと
- 早送りされた待機時間が、プログレスの素早いブラーとして表示され、フリーズしたフレームではないこと
- タイトルカードとエンドカードが存在すること
スクリプトまたはアプリを修正し(例:画像読み込み失敗時のプレースホルダー、より高速なクエリ)、状態をリセットして再録画します。通常、2~3回のテイクが必要です。
7. 納品
- 結合ポイントを確認します:各セグメント境界で
review.js <output> <dir> 0 at=<t-1>,<t+1>を実行します。 record.jsは以前のバージョンを*.previous.mp4として保持します。このフォルダ自身のoutput以外の動画を上書きしないでください。他の人の動画に追加するには、新しいフォルダのvideoセグメントとして追加します。- 動画フォルダがデモリポジトリにコミットできる状態であることをユーザーに伝えます。ユーザーが依頼した場合のみコミットします。
- ユーザーに報告します:ファイルパスと長さ、各セグメントの内容、実際の動作とシミュレートされたもの、録画が残した状態(実行中のサービス、作成したレコード、有効なままのもの)とその元に戻し方。
過去に遭遇した落とし穴
- メモリ: CDPスクリーンキャストは、アニメーション中に毎秒最大60フレームを送信します。数分間のテイクでフレームをメモリにバッファリングすると、レコーダーが強制終了され、出力が得られません。キットはフレームをディスクにストリーミングし、
ff()中は間引きます。この動作を維持してください。 - クロック:
ff()ウィンドウとフレームタイムスタンプは両方とも壁時計の秒数を使用するため、待機全体(ポーリングを含む)を1つのff()呼び出し内で計時します。 - ブロッキング処理(例:データスクリプト用の
execFileSync)をff()内で実行しても問題ありません。フレームはその後再開され、ウィンドウは圧縮されます。 - ポート: デモ用のサービスを起動する前に、ポートが空いていることを確認します。自分が起動していないプロセスを強制終了しないでください。
- 残存状態: 何かを出荷または有効にして終了するデモは、その状態をオンのままにします。その後でオフにするか、ユーザーに伝えます。