frame-capture について
frame-capture は、仮想クロックとフレームごとのスクリーンショットでウェブページを動画に記録するスキルです。スクロール演出を制御したい開発者や、重いシーンでも安定した出力を求める場面に適しています。
主な機能
- 01URLをMP4として記録
- 02スクロールとポインター動作の制御
- 03フレームごとの仮想クロック進行
- 04ChromiumまたはWebKitでの撮影
- 05ラベル付き動画比較の作成
活用シーン
- 01ページのスクロール演出の記録
- 02重いアニメーションシーンの録画
- 032つのページやモデル出力の比較
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ moh4696/ open-3d-skills --skill frame-captureインストール後、Claude Code で/frame-captureを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlscripts/compare.pyrecord.pyserve.py詳細
フレームキャプチャ
画面レコーダーは、GPU がリアルタイムで描画できたものをそのままキャプチャするため、重いシーンではカクつき、スクロールの映像は手持ち撮影のようになります。このスキルでは別の方法で録画します。ページ自身のスクリプトが実行される前に、ページの時計を置き換えます。performance.now、Date.now、requestAnimationFrame は仮想時刻を読み取り、CSS アニメーションとトランジションもその時刻に固定されます。また、すべての <video> を一時停止し、その時刻にシークします。出力する各フレームで時計を正確に 1/60 秒進め、1 フレームを実行してスクリーンショットを撮ります。フレームのレンダリングに 2 秒かかっても、動画は常に完璧な 60 fps で再生されます。
ツール
| スクリプト | 内容 |
|---|---|
scripts/record.py | スクロールキー、ポインターの動き、準備完了条件を指定して URL を MP4 に録画します |
scripts/compare.py | 2 つのクリップをラベルと角丸パネル付きで上下に重ねる(または横に並べる)スクリプトです |
scripts/serve.py | 動画のシークに対応する HTTP Range リクエストを処理する静的サーバーです |
Python 3.9 以降、pip install playwright pillow、python3 -m playwright install chromium webkit、および PATH 上の ffmpeg が必要です。
実行
-
scripts/serve.pyでページを配信します。python3 -m http.serverは使わないでください。 組み込みサーバーは Range リクエストを無視します。そのため、ブラウザーは配信された動画をシークできず(video.seekableが空になります)、録画した動画はすべて最初のフレームで止まります。record.pyはこれを検出すると警告します。python3 scripts/serve.py path/to/site --port 8000 -
まず高速なタイミング確認を行います。 1 秒あたり 10 フレームなら、数分ではなく数秒で動きの流れを確認できます。
python3 scripts/record.py http://127.0.0.1:8000/ /tmp/check.mp4 --seconds 20 --fps 8 \ --scroll "0:0 2.5:0 9:40% 12:40% 19:100%" --pointer drift
映像を確認し、キーを調整して、繰り返します。その後で初めて、クリップ全体をレンダリングします。
-
最終版をレンダリングします。
--sizeは CSS ビューポートのサイズで、--dprはそれに乗算する値です。--size 1920x1080 --dpr 1では 1080p になり、--size 1280x720 --dpr 1.5では、より大きなレイアウトで同じ 1920x1080 になります。出力形式は H.264、CRF 16、yuv420p、BT.709 タグ、faststart で、音声はありません。python3 scripts/record.py http://127.0.0.1:8000/ out.mp4 --seconds 20 --fps 60 \ --scroll "0:0 2.5:0 9:40% 12:40% 19:100%" --pointer drift --ready "window.__ready === true" -
結果を確認してから引き渡します。重要なタイミングのフレームを取り出し(
ffmpeg -ss 9 -i out.mp4 -frames:v 1 f.png)、確認してください。最初のフレーム、各スクロール停止時、最後のフレームを確認します。
動きの指定
--scrollのキーはseconds:position形式です。位置にはピクセル数(1200)、スクロール範囲に対する割合(40%)、またはビューポートの高さに対する倍率(2.5vh)を指定します。区間はイージングされます(半分は線形、半分は smootherstep なので、長い移動でも中間で急加速しません)。線形の区間にするには、時刻の後に~を付けます(例:9~:60%)。- 視聴者に読ませたい内容があるときは、そこで止めます。固定表示されるセクションやカウンターには、位置が同じキーのペアを指定します。
--pointer driftは、実際のマウスを中心の周りでゆっくり円運動させるため、ホバー状態、視差効果、カーソル追従型の仕組みが動き出します。--pointer 0.7,0.3ではその位置に留めます。- カメラ移動やツアーなど、ページ側が最適な動きを把握している場合は、ページに動きを制御させます。
?record=1フラグのもとでwindow.__recordFrame = (t) => { ... }を定義してください。各フレームの前に、仮想時刻が秒単位で渡されて呼び出されます。録画中はwindow.__recordingがtrueになります。 - スムーズスクロールライブラリは、スクロール位置を設定するレコーダーと競合します。
--kill-lenisを指定すると、Lenis の読み込みを停止します。それ以外のライブラリは、?record=1のときに無効にしてください。
ページを録画可能にする
-
時刻は
performance.now()または rAF のタイムスタンプから取得してください。setTimeoutやsetIntervalは使わないでください。 時計が仮想時刻になっていても、タイマーは実時間で動きます。そのため、setTimeout(next, 3000)は実時間で 3 秒後に実行されますが、それが 20 フレーム目の場合も 400 フレーム目の場合もあります。ページの時計に基づいてスケジュールしてください。const pending = []; const later = (secs, fn) => pending.push({ at: performance.now() / 1000 + secs, fn }); // in the frame loop: const now = performance.now() / 1000; for (let i = pending.length - 1; i >= 0; i--) if (now >= pending[i].at) pending.splice(i, 1)[0].fn(); -
準備完了フラグ(最初のフレームのレンダリング、テクスチャのデコード、フォントの読み込みが済んだ後に
window.__ready = trueを設定)を公開し、--readyに渡してください。アセットの読み込みが完了する前に録画すると、フレーム 0 に読み込み中のちらつきが焼き付いてしまいます。 -
録画モードでは、動画に映さない UI(ヒント、カーソル、ローダー、Cookie バー)を非表示にします。
-
WebGL のフレームが黒くなったり古いままになったりする場合は、録画時に
preserveDrawingBuffer: trueを指定してレンダラーを作成してください。
ブラウザー
- **Chromium(デフォルト)**は、ヘッドレスで実際の GPU 上で WebGL を実行します。macOS ではスクリプトが
--use-angle=metalを追加するため、フルスピードでレンダリングされます。これがないと Chromium は SwiftShader にフォールバックします。正しく動作しますが、何倍も遅くなります。 - WebKit(
--browser webkit)では、Safari のレンダリングを確認できます。フォントのスムージング、色の処理、動画のデコードが異なります。1080p ではスクリーンショットの取得がまれに停止します(約100回に1回)。スクリプトは再試行し、ステップ間で時計が一時停止しているため、再試行でも同じフレームが返されます。 - Playwright 独自の Chromium が見つからない場合(アップグレード後のバージョン不一致など)、スクリプトは Playwright のキャッシュ内から最新の Chromium を探します。
CHROME_PATHを設定すると、そちらが優先されます。 - 表示中でも見ていないブラウザータブや、非表示の埋め込みプレビューペインでは、録画しないでください。バックグラウンドタブでは
requestAnimationFrameが制限されたり、停止したりします。ヘッドレスではそのようなことはありません。
比較
python3 scripts/compare.py a.mp4 b.mp4 versus.mp4 --labels "Model A" "Model B" # stacked, for phones
python3 scripts/compare.py a.mp4 b.mp4 versus.mp4 --labels "Before" "After" --layout side # side by side
公平に比較するには、比較対象以外の条件をすべて同じにします。
- ビューポート、長さ、動きを同じにします(
--scrollのキーをそのまま再利用します)。 - 各映像のコンテンツ部分を先に切り抜きます。比較するもう一方の映像にない透かし、ウェブカメラのオーバーレイ、UI の装飾を取り除きます。
- 音声なしで出力します。2つのサウンドトラックがぶつかり、音声が映像への注意をそらします。
- 短い方の映像は最後のフレームを保持し、両方が同時に終わるようにします。
- ラベルには各映像を正直に記載します。視聴者に知られている方を上に配置します。
納品
- ソーシャルプラットフォームではすべて再エンコードされます。1920x1080 または 1080x1920、60 fps、H.264、CRF 14~18 が最も画質を保てます。高解像度でも、多くの場合ダウンスケールされます。
- マスターは保管しておきます。6秒のループや縦向きの切り抜きなどは、再録画せず、マスターから ffmpeg で作成します。
- 録画後に音声を追加し、録画時に表示されたフレーム番号に合わせます。仮想時計を使って音声を録音しないでください。