ティーザー動画
起動中のアプリに対して、ローンチ品質のティーザー(約 30~45 秒)を生成します。ストーリーボードを作成し、実際の UI を操作して撮影し、ffmpeg で編集して、MP4 と README 用の GIF を納品します。
出力は製品動画のように見える必要があり、単なる画面録画ではいけません。違いは解像度ではなく、演出、フレーミング、リズムといった「演出面」にあります。
参考実装は reference/ にあります。疑似コードではなく、実際に動作する ESM モジュールです。プロジェクト内ではなく、スクラッチディレクトリでセットアップしてください:
cp -r <skill>/reference/* "$SCRATCH/" && cd "$SCRATCH" && npm install
export TEASER_BASE_URL="https://the-app.example.com"
reference/package.json は "type": "module" と 3 つの依存関係を宣言しています。すべてのスクリプトは TEASER_BASE_URL を読み取るため、特定のアプリにハードコードされていません。
何よりも先に:2 つの絶対ルール
1. 現実世界に影響を及ぼす副作用を、明示的一件承認なしに絶対にトリガーしないこと。
実アプリの撮影は、実際のボタンをクリックすることを意味します。その中には、メールを送信する、公開投稿する、課金する、サードパーティにフォームを送信する、といった操作が含まれる可能性があります。アプリに触れる前に:
- ストーリーボード内の、マシンを離れるすべてのアクションを列挙する。
- それぞれのアクションが何をするのか、何回実行されるのかをユーザーに正確に伝え、回数について合意を得る。
- 不可逆的なステップの手前で停止するモード(プレビュー、ドライラン、撮影してから拒否できる確認ゲート)を優先する。
ライブシステムに対する読み取り専用のプローブなど存在しません。既に実行中のセッション内で「診断目的」のクリックが行われれば、実際に発火します。アクションが機能しなかった理由をデバッグしている場合、次のクリックは 機能する と想定し、その準備をしてください。
意図しない発火が発生した場合:プロセスをそのソースで直ちに停止し(UI のボタンだけでなく、セッション/ワーカーを kill する。UI ボタンは効かない可能性がある)、停止した状態を読み戻して確認し、最後のメッセージでユーザーにその回数も含めて明確に伝えてください。
2. すべてのフレームが公開され、PII で満たされていると想定すること。
実際のアカウントには、名前、メールアドレス、住所、書類が含まれています。事前にユーザーと方針を決めてください:
- フレームで回避する(デフォルト) - 個人情報を除外するようにショットを構成する。ffmpeg の段階で、永続的なクローム要素(サイドバー、アバター、アカウントメニュー)をクロップする。
- 撮影前に非表示にする -
page.evaluateで該当するカードにvisibility: hiddenを適用してからカメラを回す。撮影して後でカットで回避しようとしない。 - シードされたデモアカウントを使用する - デモアカウントが存在する場合、最もクリーン。
納品前に、最終カットのすべてのフレームをフル解像度で確認する。コンタクトシートではなく、フレームそのものを確認してください。
ワークフロー
1. スカウトとストーリーボード
最初に、すべての候補画面の認証済みスクリーンショットを取得します(reference/scout.js)。それらを確認します。次に、ストーリーボードテーブルを作成します:ビート、ショット、視聴者が 感じる べきこと、長さ。担当が割り当てられていないショットは、撮影前にカットされます。
効果的なアーク:緊張(問題) → 啓示(製品) → 証明(実際に動作する) → 規模(結果) → 信頼(実行環境) → クロージング。
ストーリーボードに、どの画面に PII が含まれ、どの画面が安全かを記録してください。
2. セットの準備
撮影前に、決して撮影後に、セットをキュレーションします:
- データが最も美しく見えるビューを選択する - 完全なチャート、アクティブなリスト、認識可能な名前、ゼロでないカウント。スピナー、空の状態、描画途中のチャートは絶対に撮影しない。
- UI の状態(パネル幅、折りたたみ/展開、フィルター、スクロール位置)を、init スクリプトまたはプレロールの
page.evaluateで意図的に設定する。 - トースト、バナー、クッキーバーはカメラ外で消す。
- データが落ち着くのを待つ:
networkidleに加えて数秒。
3. 撮影
CDP スクリーンキャスト(Page.startScreencast)を使用します。画面録画ではありません。これはページをオフスクリーンで約 100fps でキャプチャするため、ユーザーの操作がフレームに映り込まず、マシンも使用可能なままです。Playwright が操作を駆動し、CDP が撮影します。reference/rig.js がこれを実装しています。
- ショットごとに 1 つの被写体。「アプリ全体」ではなく、対象をフレーミングする。
- カメラの動きは ページ内でのスクリプト化されたイージングスクロール(
cameraScroll)から行い、フィルターは使用しない。以下の zoompan 警告を参照。 - カーソルは演技者です。注入された SVG カーソル(
cursorClick)を使用します。これはイージングで滑らかに動き、押下時にディップします。または非表示にします。アイドル時のドリフトは避ける。 - 各テイクをリハーサルし、フレームを抽出し、確認し、調整してから本番撮影する。
外部ウィンドウの撮影(アプリ自体が開くブラウザ、デスクトップアプリ)には、OS レベルのキャプチャ(gdigrab/x11grab/avfoundation、rig 内の recordDesktop)が必要です。これは 画面上のすべて、ユーザー自身の作業も含めてキャプチャします。ユーザーに警告し、離れるよう依頼し、結果をフレーム単位で確認し、プライベートな情報が映り込んだ場合は削除します。
4. タイトルカード
静的画像ではなく、ブラウザ内で撮影したアニメーション HTML/CSS。カードのマークアップを アプリ自身のオリジン上の 空白ページに注入し(これにより Web フォントとパレットが解決される)、スクリーンキャストします。実際のページから表示フォントを取得し(getComputedStyle)、既知の良好な等幅スタックを固定します(スニッフィングはしません)。
アプリのモーション言語(独自のイージングカーブと持続時間)に合わせます。プロジェクトに確立されたカードスタイルがない場合は、事前に frontend-design スキルをロードしてください。
5. 編集
reference/build.js は各セグメントを共通のサイズ/フレームレートに正規化し、クロスフェードでチェーンします。カットは約 2.5~4 秒ごとに保ちます。決定的な瞬間は通常速度で余裕を持たせ、その周りのルーチン部分は圧縮します。
6. レビュー、そして納品
- コンタクトシート および 秒ごとのフル解像度フレームを生成する。
- フレームを PII、クリップされたパネル、読みにくいテキストがないか読み取る。
- プローブで長さと寸法を確認する。
- MP4 に加えて、README 用のパレット最適化 GIF を納品する(GitHub はコミットされた MP4 をインラインプレイしません)。GIF は約 10 MB 未満に抑える。
- 指定がない限り無音で納品する。音楽のライセンスは取得できません。そのように伝える。
苦労して得た知見
プッシュインに zoompan を使用しないこと。 フレームごとにズームを再計算し、クロップウィンドウを整数ピクセルに丸めるため、画像が行ったり来たりと震えます。地震のように見えます。ロックされたショットと、実際のページ内スクロールを使用してください。どうしてもプッシュインが必要な場合は、まずソースを 4 倍にプリスケールし、フレームデルタ測定(reference/jitter.js)で確認してください。ロックされた静的ショットは平均デルタが 1.0 未満であるべきです。目に見える揺れは 3 以上になります。
アニメーションが落ち着くと、スクリーンキャストはフレームの発行を停止します。 アニメーションが 1.2 秒で終了するカードは、1.2 秒のクリップになります。tpad=stop_mode=clone:stop_duration=N でパディングし、-t で希望の長さにカットしてください。
固定レートではなく、タイムスタンプ付きフレームをアセンブルすること。 スクリーンキャストのフレームには metadata.timestamp が付属します。実際のフレームごとの持続時間で ffmpeg concat リストを作成し、その後一定の 60fps にリサンプリングします。固定間隔を想定すると、ジッターが発生します。
ffmpeg-static を dev 依存関係として、モジュールパスで解決すること。 ffmpeg が PATH にあることに依存しないでください。(ffprobe はそのパッケージに含まれていません。代わりに ffmpeg -i の stderr を解析してください。)
認証済みキャプチャ: 一度ヘッド付きでログインし、storageState を保存し、すべてのテイクで再利用します(reference/login.js)。セッションは期限切れになります。同じ日に撮影を終えるか、再実行してください。また、UI 設定の localStorage もそこでシードしてください。
デプロイされたオリジンからの localhost へのアクセスは、Chrome のローカルネットワークアクセスポリシーによってブロックされます。 ページがローカルサービスにアクセスする必要がある場合は、--disable-features=LocalNetworkAccessChecks,PrivateNetworkAccessChecks,BlockInsecurePrivateNetworkRequests を指定して起動してください。
永続的なクローム要素は固定オフセットでクロップすること。 ビューポート幅 1920 + railWidth で撮影し、crop=1920:1080:railWidth:0 でサイドバーを除去して正確に 1080p に収めます。
成果物
パス、長さ、寸法を報告し、ユーザーからまだ必要なもの(音楽、GitHub ネイティブ動画アップロード)を明確に述べ、撮影中にユーザーが知るべき出来事があれば開示します。