Bビデオ は何をしますか?
その他のトリガー
PRのビデオウォークスルー、デモ録画の作成、音声付きデモ、両スタックの記録、説明ビデオ、drawtextフィルタ不要、Playwright recordVideo、ヘッドレスシェル不足
クレームセットごとに1つのmp4:タイトルカード、Aクリップ、Bクリップ、エビデンスカード、判定カード、それぞれ音声付き。
Apple Silicon上のmacOS向けに構築(say、~/Library/Caches/ms-playwright下のPlaywrightキャッシュ、chrome-mac-arm64)。それ以外の環境ではCHROMIUM_EXEを設定し、音声には--elevenを使用。Bun、ffmpeg、Python 3が必要。
実行手順
S=${SKILL_DIR:-~/.claude/skills/narrated-ab-video}
$S/preflight.sh /tmp/vid # chromium, playwright ffmpeg, ffmpeg, say voice
bun $S/record.ts a.json raw/a-run # サイドごとに1シナリオ(examples/scenario.jsonをコピー)
bun $S/record.ts b.json raw/b-run
bun $S/cards.ts cards.json cards # タイトル/エビデンス/判定PNG + バナーA/B.png
python3 $S/build.py plan.json # [--voice Samantha] [--rate 182]
open -a "QuickTime Player" demo.mp4 # 一時停止状態で開く;自分で再生しないこと
examples/内の例:scenario.json(ステップ: wait, select, click, clickIfPresent, type, send, waitStable, expand, fetchText, waitText, probeEnabled, reload;record.tsのヘッダー参照)、cards.json、plan.json。プランパスはプランファイルからの相対パス。実際の2クリップから4セグメント、40秒のビルドでエンドツーエンド検証済み。
このように構築された理由(摩擦の採掘)
- Playwrightの固定ヘッドレスシェルはダウンロードされていないことが多い。
record.tsとcards.tsは最新のキャッシュされたchromium-*フルビルド(または$CHROMIUM_EXE)を起動し、失敗を回避。 - recordVideoにはPlaywright独自のffmpegビルドが必要。 欠如するとwebmが生成されない。preflightは
browsers.jsonからリビジョンを読み取り、bunx playwright install ffmpegを実行。 - Homebrewのffmpegはdrawtextが欠如していることがある。 バナーは
cards.tsからの透明PNGで、build.pyでオーバーレイ。フォントやフィルタの探索は不要。 - モデルの応答には10〜60秒の無音時間が発生する。
sendは「送信済み」、waitStableは「応答あり」をマーク。build.pyはその区間をspeed(デフォルト10倍速)で再生し、残りは1倍速で再生。 - ナレーションは通常クリップより長い。 build.pyは最終フレーム(
tpad stop_mode=clone)を音声終了まで保持するため、応答が画面に表示されたまま説明が行われる。 - 動的入力(OTPコード、前のターンのIDなど):
typeステップに{{fn:NAME}}を配置し、シナリオと同じディレクトリのhooks.tsからNAME(page, state)をエクスポート。state.lastJsonはcaptureJsonFromに一致する最後のPOST JSONを保持。 - セレクター: ボタンはロールと名前の正規表現でマッチ(
"Tools used"、"^Send$")。CSSは入力とセレクトのみ。スクリプト作成前にクイックスクリーンショットで名前を確認。 - 連結には同一エンコードが必要。 すべてのセグメントは同じコーデック、レート、オーディオレイアウトで再エンコードされ、
-c copyで結合。 - より良い音声:
ELEVENLABS_API_KEY=... build.py plan.json --eleven <voice_id>(モデルeleven_multilingual_v2;クリップは音声+テキストハッシュでキャッシュされるため、再実行では文字数消費なし)。キーは0600のファイルに保管し、プランには決して書かず、使用後は削除。 - 片側デモ(A/Bなし): 1つのシナリオを記録し、
cards.jsonに1つのバナーを与え、同じクリップを複数のプランセグメント(クレームごと)に分割し、待機時間を高速化するspans(例:5倍速)。同じビルドで、Aサイドなし。 - ユーザーが単に
<voice_id> <key>を貼り付ける場合もある。 つまり--eleven <voice_id>で再ビルド。遅い音声はビデオを引き伸ばす(sayで72秒→92秒)。build.pyは音声終了まで最終フレームを保持するため。 - 撮影前に破棄されたデモの再構築: 短期間の認証(ローカルJWT、トンネルURL、一時的なクラウドリソース)は翌日には無効になっていることが多い。録画前に1回のAPI実行で再確認すること;そこで401「JWTトークン期限切れ」が発生した場合、結果が届かないターンを撮影してしまう。
- 生クリップの喪失: 以前の出力mp4から該当サイドの映像を切り出す(バナーコーナーの色でスパンを特定)、
<dir>/a.webmとして-f mp4と空のmarks.jsonで保存し、明示的な1スパンを使用。バナーは焼き込まれているので省略。 - オーディオ:
say -oはファイルのみ書き出し。ユーザーの明示的な許可なしにビデオやオーディオを再生してはならない。一時停止状態で開き、ユーザーに引き渡す。 - 撮影前に証明せよ。 先にA/Bを実行し、結果(APIまたはログ)をアサートすること。ビデオは証明された結果を示すものであり、テストではない。
- カードはクリップサイズと一致しなければならない。 build.pyはスケーリングを行わない。
"viewport": [1440, 900]での録画にはcards.jsonで"size": [1440, 900]が必要。そうしないと連結時にフレームサイズが混在する。 - 非同期UIには固定待機ではなくマークが必要。
waitTextは正規表現が出現した時点でマーク(またはtimeout:<mark>)、probeEnabledはwaitMsでボタンをポーリングしmarks.jsonにprobes[]を記録、tryMsはtype/send/clickで失敗時にskipped:<step>をマークしてアボートしないため、「無効のまま」のサイドでもクリップが完了する。 - クリックする要素と同じ要素をプローブせよ。 CSSプローブ(
button[aria-label=...])がロール名sendとは異なるマッチングボタンにヒットし、sendが通過中にdisabledを読み取った;probeEnabledでは"role"を使用。 - Devモードのオーバーレイ(クエリdevtools、エラートースト)はクリップに写り込む。 最初のwaitの後に
{"clickIfPresent": "^Close .* devtools$"}を追加。 - 「安定化」しない応答を待つ場合: ライブの経過時間バッジが本文テキストを毎秒変更する。ビルド前に
marks.jsonにrepliedマークがあるか確認。 - 証拠が可視テキストにあることを確認せよ。 折りたたまれたツールカードは展開されないことがある(
expandは静かに何もクリックしない)。marks.jsonのbodyでクレーム文字列を確認。欠如している場合はプロンプトを変更するか、代わりにカード上で証明。 - 「ベースが失敗する」クレームを撮影する前にパリティを証明せよ。 ローカルベースが、実際の環境では決して発生しない理由(ローカルではオン、リモートではオフのフラグ)で失敗したことがあり、ナレーションでそれを主張した。まずリモートのフラグに合わせてから撮影。
- タイトルカード:テストされたコミットと各サイドのフラグ。クリップセグメントごとに1クレーム。判定カードで終了。
関連
narrated-screenshot:ビデオではなく、注釈付きの単一静止画。pr-ab-evidence:このスキルが撮影する赤/緑のA/Bメソッド。