Communitygithub.com

demo-reel

スクリプト化された操作をもとに、60fpsの滑らかなデモ動画を生成します。実際のWebアプリをヘッドレスChromiumで駆動し、カーソルハイライト、クリックエフェクト、キャプション、コーナーバッジを合成。PowerPoint、Keynote、Webでループ可能なMP4を出力します。

demo-reel とは?

demo-reelは、Webアプリケーションの操作手順をスクリプト化し、プロフェッショナルな画面収録風のデモ動画を自動生成するスキルです。通常の画面収録ではフレームレートが不安定になりがちですが、demo-reelは仮想クロックを注入し、各フレームを正確に1/fpsずつ進めることで、常にスムーズな60fpsを実現します。主にPlaywright + ffmpegベースで動作し、Node.js 18以降とffmpegが必要です。ユーザーは.scenario.mjsファイルに操作シーケンスを記述し、`node scripts/record.mjs`で動画を生成。キャプション、バッジ、カーソルハロー、クリックリップルなどのオーバーレイを自由にカスタマイズできます。Canvas/WebGLベースの高度なUIにも対応し、Cytoscape.jsなどの座標関数を使った操作も可能。PowerPoint/Keynote/Web向けのMP4出力、GIF変換、複数クリップの結合機能も備えています。

対応~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/hasuwini77/demo-reel/tree/main/skills/demo-reel

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

プレビュー

スキル README より

ドキュメント

demo-reel は何をしますか?

Webアプリのスクリプト化された操作手順を、洗練された画面収録風の動画に変換します:スムーズな60fpsの動き、表示可能なカーソル+ハロー、クリックリップル、キャプション。実際のアプリをヘッドレスChromiumで駆動します — 映像は製品そのものであり、モックアップではありません。

なぜ動画がスムーズなのか

通常の画面収録はブラウザがペイントするタイミングでフレームをキャプチャするため、遅いマシン(またはソフトウェアWebGL)では約10〜15fpsになりカクつきます。demo-reelは代わりに仮想クロック(scripts/inject.js)を注入します:requestAnimationFrame、performance.now、Dateはレコーダーが指示したときだけ進み、CSS/Webアニメーションはdocument.getAnimations()でステップ実行されます。各フレームは正確に1/fpsだけ進められ、スクリーンショットされ、ffmpegにパイプされます。フレームのレンダリングにどれだけ時間がかかっても、結果は完全にスムーズに再生されます。

タイマー(setTimeout/setInterval)は意図的に実際のままにしています — それらを偽装すると、ローダーやスケジューラのゼロ遅延タイマーチェーンが永遠に回り続けるからです(これがPlaywright独自のpage.clockが多くのWebGLページでハングする理由です)。

必要条件

  • Node 18以降、ffmpegがPATHに含まれていること。
  • 一度だけ、このスキルのディレクトリで:npm install && npx playwright install chromium。

ワークフロー

  1. アプリをサーブ — ユーザーが見る方法で。理想的には本番ビルド(vite build && vite preview、next build && next start)をローカルまたはモックデータに対して。開発サーバーでも動作しますが、遅く、開発用オーバーレイが表示される場合があります。実際のデータや機密データを決して録画しないでください — デモデータをシードします。
  2. シナリオを書く(*.scenario.mjs、以下のAPI参照)。examples/tasks.scenario.mjsから始めてください。ストーリーとして計画:20〜60秒、キャプションごとに1つのアイデア、各結果の後に1〜2秒ホールドして視聴者が読めるように。
  3. 録画:node scripts/record.mjs my.scenario.mjs --out demo.mp4 60秒のクリップ(1080p)は約3〜10分かかります(重いWebGLページが最も遅い)。バックグラウンドで実行してください。
  4. 品質確認:scripts/sheet.sh demo.mp4でコンタクトシート(2.5秒ごとに1フレーム)を作成。確認事項:キャプションが画面の内容と一致している、ローディングスピナーがない、キャプションで隠れているものがない、バッジ/カーソルが表示されている。連続するフレームをいくつか抽出してスムーズさをスポットチェック — 動きのあるときは各フレームが少しずつ異なるはず。
  5. 納品(「動画の使い方」参照)。

シナリオAPI

export default {
  size: "1920x1080",          // ビューポート = 動画サイズ
  fps: 60,
  theme: { captionPosition: "bottom", haloFill: "rgba(250,204,21,.30)" }, // オプション
  async run(d) {
    await d.open("http://localhost:4173/");     // ナビゲート+安定化(録画されない)
    d.badge("NEW", "#15803d");                  // コーナーバッジ(nullで非表示)
    d.caption("新しいダッシュボード");             // キャプションピル(nullで非表示)
    await d.hold(1500);                          // 1.5秒そのまま録画
    await d.click(d.page.getByRole("button", { name: "Filters" }));
    d.caption("フィルターは1つのパネルに");     // アクションの後にキャプションを設定
    await d.hold(1200);
    await d.type("Stockholm");                  // 人間的な速度でタイピング
    await d.press("Enter");
    await d.scroll(600);                        // スムーズなホイールスクロール
    await d.step("オプショナルパート", async () => { /* エラーはログに記録され、録画は継続 */ });
  },
};
コール動作
d.open(url, { settle })ナビゲート、ページが安定するまで待機(録画されない、デフォルト2500ms)。
d.hold(ms)ページをそのまま録画。
d.settle(ms)時間を録画せずに進める — 表示したくないローディング状態、遅延チャンク、レイアウトアニメーションをスキップ。
d.move(target, { ms }) / d.hoverカーソルを(イージング付きで)ターゲットにグライド。
d.click(target, { ms, button })移動、リップル付きでクリック。button: "right"でコンテキストメニュー。
d.doubleClick(target)ダブルクリック。
d.type(text, { delay })一度に1キーずつ入力(デフォルト90ms/キー)。
d.press(key)キーボードショートカット、例:"Escape"、"Control+K"。
d.scroll(dy, { ms })スムーズなホイールスクロール。
d.caption(text) / d.badge(text, color) / d.cursor(bool)オーバーレイの状態;フルページナビゲーション後も持続。
d.step(name, fn)名前付きステップ;エラーはログに記録され、致命的ではない。
d.page、d.point(target)、d.width、d.heightエスケープハッチ。

ターゲットはPlaywrightのLocator、CSSセレクター文字列、{ x, y }、または非同期関数(page) => ({ x, y })です。

CanvasレンダリングUI(グラフ、地図、チャート)にはアイテムごとのDOM要素がありません — ライブラリに画面座標を尋ねる関数を渡してください。Cytoscape.jsの例:

const node = (label) => (page) => page.evaluate((label) => {
  const el = [...document.querySelectorAll("div")].find((e) => e._cyreg?.cy);
  const n = el._cyreg.cy.nodes().filter((n) => n.data("label") === label)[0];
  const r = el.getBoundingClientRect(), p = n.renderedPosition();
  return { x: r.left + p.x, y: r.top + p.y };
}, label);
await d.click(node("Server A"));

良いデモを書くために

  • キャプションは真実を伝える。 映像に映っていることだけを主張してください。取り直しで再現しないバグを見せたい場合、キャプションを変更してください — 保持しないで。
  • アクションの後にキャプションを設定し、前には設定しない — そうしないと次の画面のキャプションが前の画面に重なります。
  • クリックではなく結果を見せる。 各結果で1〜2秒ホールド;d.settleでローディングをカット。
  • Before/After:同じストーリーとステップで2つのシナリオを録画し、バッジBEFORE/AFTER(赤/緑)を付け、別々のファイルとオプションの連結を保持します。
  • Heroモーメント(ランディングページ、アニメーション):5〜10秒与える — 視聴者は理解する時間が必要。
  • カーソルは落ち着かせる:移動あたり600〜900ms、ホールド中はコンテンツから離れた場所に置く。
  • 実際のタイマーはフレームレンダリング中も動き続ける(フレームあたり約50〜300msの実時間)ため、タイマー駆動のUI(トースト、デバウンス検索、自動消滅ツールチップ)は現実よりも早く動画から消える可能性があります。画面上に表示したい直前にトリガーするか、ホールド時間を短くしてください。

動画の使い方

  • PowerPoint:挿入 → ビデオ → このデバイス。再生タブ:開始:自動的 および 停止するまでループ。H.264 yuv420p MP4(demo-reelが書き出すもの)はWindowsとmacOSで再生できます。
  • Keynote:ドラッグ&ドロップ;ムービー → 繰り返し:ループ。
  • Web:<video src="demo.mp4" autoplay loop muted playsinline></video>。
  • GIF(README、チャット):ffmpeg -i demo.mp4 -vf "fps=20,scale=960:-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse" demo.gif
  • 同じサイズ/fpsのクリップを連結:printf "file 'a.mp4'\nfile 'b.mp4'\n" > l.txt && ffmpeg -f concat -safe 0 -i l.txt -c copy both.mp4

トラブルシューティング

症状修正方法
空白 / 「Loading…」フレームd.openでsettleを長くする、またはナビゲーションクリック後にawait d.settle(1500)。
ターゲットが表示されない画面外または閉じたメニュー内 — 先に開く/スクロール;Canvasアイテムにはポイント関数を使用。
WebGL/3Dが黒くレンダリングされるデフォルトの起動フラグ(--use-angle=swiftshader)を維持;遅い(約0.25秒/フレーム)が正しく動作。
動画が計画より短い/長い時間は正確:hold + 移動の合計。settle時間は決して録画されない。
PowerPointで色が薄くなる-pix_fmt yuv420pなしで再エンコードしない;demo-reelの出力は既に正しい。
テキストがわずかにぼやける--pngでロスレスフレームをキャプチャ(遅い、ファイルサイズ大)。

関連スキル