
Remotion Lambda と ElevenLabs の音声合成 API で PR の受け入れを動画化してみた
リテールアプリ共創部のるおんです。
最近は、Claude Code などの AI エージェントを何本か並べて開発することが増えました。実装のスピードは上がった一方で、 プルリクエストが自分の確認待ちで溜まっていく ようになりました。差分を読み、ブランチを手元に落として起動し、画面を触って「仕様どおりか」を確かめる。この確かめが1本ごとに数分〜十数分かかり、しかも並列で走っている別の作業の文脈を頭から追い出してからでないとできません。地味ですが、 認知負荷 がかなり高い作業です。
そこで最近は、プルリクエストを出したエージェントに 受け入れテストの動画 まで作らせるようにしています。エージェントが自分で画面を操作して撮り、ナレーションと字幕を付けて、最後に「この形で合格としてよいか」を聞いてくる2分弱の動画です。私はそれを見て、合格かどうかだけを判断します。
この記事では、題材として小さな TODO アプリに機能を足すプルリクエストを用意し、その受け入れテストの動画を Remotion ・ Remotion Lambda(AWS Lambda) ・ ElevenLabs で作ってみた流れと、実際にかかった時間・費用を紹介します。
先に結論
- プルリクエストの受け入れテストを、 エージェントに動画で報告させる と、こちらは「見て、合格かどうかを決める」だけになり、確認の負担がかなり下がりました
- 動画は 画面の静止画をつないで、ズーム・赤枠・クリックの印で動きを付ける 作りにしています。画面録画よりも撮り直しが軽く、文字も読める絵になります
- ナレーションは ElevenLabs の時刻つき読み上げ(with-timestamps) で作ります。文字ごとの発話時刻が返ってくるので、「追加を押します」と読んだ瞬間にクリックの印を出す、といった音と絵の同期を機械で取れます
- 書き出しは Remotion Lambda で、手元の PC では書き出しません。約1分50秒・1080p の動画が Lambda 89台・46秒・約0.07ドル(Remotion の見積もり) でできました
- 動画の型は「 変わること → 実際の画面 → 確かめていないこと → 合格としてよいか 」に固定しています。 確かめていないことを必ず言わせる のが一番効きました
| 工程 | 使ったもの | 今回の実測 |
|---|---|---|
| 実装・プルリクエスト | Claude Code | TODO アプリに「期限」と「今日まで」の絞り込みを追加(6ファイル・自動テスト7件) |
| 画面の撮影 | Claude in Chrome | 静止画6枚 |
| 台本 | Claude Code が script.json を書く |
9場面(題名・図解・画面5つ・確かめていないこと・締め) |
| ナレーション | ElevenLabs(Eleven v4・with-timestamps) | 584文字・約1分50秒 |
| 書き出し | Remotion Lambda | 89台・46秒・約0.07ドル・4.3MB(1920x1080) |
できた動画
今回作った受け入れテストの動画です。
全体の流れ
プルリクエストを出してから、私が合格を出すまでの流れはこうです。
ポイントは、 私が手を動かすのは最初の依頼と最後の判断だけ という点です。差分を読む・ブランチを落とす・起動する・触る、はエージェント側に寄せています。
使った道具
Remotion と Remotion Lambda
Remotion は、React で動画を作るためのフレームワークです。動画の1コマ1コマを React のコンポーネント(今回は canvas に描く処理)として書き、それを mp4 に書き出します。
書き出しは手元でもできますが、今回は Remotion Lambda を使いました。公式ドキュメントでは、次のように説明されています。
a lot of Lambda functions are created in parallel which each render a small part of the video
動画を細かい区間に分けて、たくさんの Lambda で同時に書き出し、最後に1本につなぐ仕組みです。Remotion のプロジェクトは S3 にサイトとして置かれ、書き出した動画も S3 に置かれます。
手元で書き出さない理由は単純で、 エージェントを並べて開発している最中の PC で動画を書き出すと、ほかの作業が全部重くなる からです。実際に一度、手元で書き出して PC が重くなり、止めたことがあります。
ElevenLabs の時刻つき読み上げ
ナレーションは ElevenLabs の Text to Speech で作っています。使っているのは、読み上げと一緒に 文字ごとの発話時刻 を返してくれるエンドポイントです。
POST https://api.elevenlabs.io/v1/text-to-speech/{voice_id}/with-timestamps
レスポンスの alignment に、文字の一覧(characters)と、それぞれの開始・終了時刻(character_start_times_seconds / character_end_times_seconds)が入っています。
これがあると、台本の中の「追加を押します」という語が 何秒目に読まれたか が分かります。その時刻に合わせてクリックの印を出したり、赤枠を出したりできるので、音と絵のずれを人が調整する必要がなくなります。
Claude in Chrome(画面の撮影)
画面の撮影は、Claude Code から Chrome を操作できる Claude in Chrome で行っています。エージェントが自分専用のタブを開き、 localhost のアプリを操作しながら、1操作ごとに静止画を撮ります。
やってみた
1. 題材のプルリクエストを用意する
題材は、React + TypeScript(Vite)の小さな TODO アプリです。もともとある「追加・完了・削除・すべて/未完了/完了の絞り込み」に、次の機能を足すプルリクエストを Claude Code に作ってもらいました。
- やることに 期限 を付けられる
- 期限が今日なら 黄色で「今日」 、過ぎていれば 赤で「期限切れ」 と出る
- 「今日まで」 を押すと、未完了で期限が今日か過ぎているものだけに絞り込める
期限の判定は、テストしやすいように純粋な関数に切り出しています。
export type DueStatus = "overdue" | "today" | "upcoming" | "none";
/** 手元の時刻での「今日」を YYYY-MM-DD で返す(toISOString は UTC になるので使わない) */
export const toLocalDateString = (date: Date): string => {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, "0");
const d = String(date.getDate()).padStart(2, "0");
return `${y}-${m}-${d}`;
};
export const dueStatus = (dueDate: string | undefined, today: string): DueStatus => {
if (!dueDate) return "none";
if (dueDate < today) return "overdue";
if (dueDate === today) return "today";
return "upcoming";
};
/** 「今日まで」= 未完了で、期限が今日か過ぎているもの */
export const isDueByToday = (todo: Todo, today: string): boolean => {
const status = dueStatus(todo.dueDate, today);
return !todo.done && (status === "today" || status === "overdue");
};
画面側は、絞り込みの条件に1行足すだけです。
- const visible = todos.filter((t) =>
- filter === "all" ? true : filter === "active" ? !t.done : t.done,
- );
+ const today = toLocalDateString(new Date());
+ const visible = todos.filter((t) => {
+ if (filter === "active") return !t.done;
+ if (filter === "done") return t.done;
+ if (filter === "dueToday") return isDueByToday(t, today);
+ return true;
+ });
自動テスト(Vitest)は7件で、すべて通っています。
Test Files 1 passed (1)
Tests 7 passed (7)
ここまでは普段どおりです。テストは通っていますが、 「画面で実際にどう見えるか」「操作の流れとして自然か」 はテストでは分かりません。ここを人が確かめる代わりに、動画にしてもらいます。
2. エージェントに頼む
依頼は、例えばこんな形です(実際には、撮り方や動画の型の決まりをひな形のドキュメントにまとめてあり、そちらを読ませています)。
期限と「今日まで」の絞り込みを実装して、プルリクエストを出してください。
出したら、受け入れテストの動画も作ってください。
- 手元で起動して、練習用のデータで、追加 → 絞り込み → 完了 の流れを撮る
- 最後に「確かめていないこと」と「この形で合格としてよいか」を入れる
- 書き出しは Lambda で(手元で書き出さない)
3. 画面を撮る
エージェントは、自分専用のタブで localhost のアプリを開き、練習用のやることを3件入れてから、1操作ごとに静止画を撮ります。今回は次の6枚でした。
| 画像 | 撮った場面 |
|---|---|
| s01 | 最初の一覧(期限切れ・あさって・期限なしの3件) |
| s02 | 「牛乳を買う」と今日の日付を入力したところ |
| s03 | 追加した直後(黄色の「今日」が付いている) |
| s04 | 「今日まで」を押したところ(2件だけになる) |
| s05 | 牛乳を完了にしたところ(「今日まで」から外れる) |
| s06 | 「すべて」に戻したところ(取り消し線で残っている) |
撮りながら、 画面の文言を本文のテキストでも確かめています 。例えば「今日まで」を押したあとの画面からは、次のテキストが取れました。期待どおり、期限切れの1件と今日の1件だけです。
議事録を共有する
10/20 期限切れ
牛乳を買う
10/21 今日
4. 台本を書く
台本は script.json に、場面(beat)を並べて書きます。画面の場面はこんな形です。
{
"id": "v3",
"fn": "shot",
"text": "次に、今日まで、を押します。期限を過ぎたものと、今日が期限のものの、二件だけになりました。あさってが期限の請求書と、期限のない本は、出てきません。",
"marks": ["今日まで、を押します", "二件だけ", "出てきません"],
"shot": {
"steps": [{ "img": "s03", "t": 0 }, { "img": "s04", "t": "今日まで、を押します+0.5" }],
"cams": [
{ "t": 0, "cam": { "rect": [300, 40, 1270, 640], "zmax": 1.6 } },
{ "t": "二件だけ-0.6", "cam": { "rect": [380, 240, 1190, 470], "zmax": 2.2 } }
],
"boxes": [
{ "r": [403, 314, 1165, 450], "t": "二件だけ-0.2", "end": "end", "label": "期限切れと今日の2件", "pos": "below" }
],
"clicks": [{ "x": 746, "y": 285, "t": "今日まで、を押します+0.2" }]
}
}
ここで効いてくるのが marks です。"t": "今日まで、を押します+0.2" は、「 『今日まで、を押します』と読み始めた0.2秒後 」という意味です。秒数を直接書かず、読み上げの中の語を基準にして、クリックの印・画像の切り替え・ズーム・赤枠のタイミングを決めています。
座標(rect や r)は、撮った画像のピクセル座標です。エージェントは撮った画面を見ているので、「今日までのボタンは (746, 285)」といった座標をそのまま書けます。
5. 読み上げる
台本ができたら、ElevenLabs で読み上げます。各場面の文章を with-timestamps のエンドポイントに送り、返ってきた文字ごとの時刻から、 marks の語が何秒目に読まれたかを求めます。
TOTAL 109.622
t0 0.0 7.899 {'受け入れテスト': 5.569}
n1 7.899 17.354 {'期限を付けられる': 4.9, '赤で目立つ': 9.82, '絞り込める': 15.66}
v1 25.253 13.281 {'いまの一覧': 1.168, '期限切れ': 6.768, '何も出ません': 11.968}
v2 38.534 13.343 {'牛乳を買う': 1.615, '期限に今日': 3.815, '追加を押します': 6.095, '一番下に': 8.535}
v3 51.877 13.849 {'今日まで、を押します': 1.221, '二件だけ': 7.021, '出てきません': 12.661}
v4 65.727 7.572 {'完了にします': 1.903, '外れます': 6.383}
v5 73.298 10.377 {'すべて、に戻す': 0.4, '取り消し線': 3.432, '灰色': 8.872}
n2 83.676 16.285 {'零時をまたいだ': 6.06, 'パソコンの Chrome だけ': 12.32}
e1 99.961 9.661 {'合格': 5.141}
読み上げたのは9場面・584文字で、合計約1分50秒になりました。「TODO」を「トゥードゥー」と読ませるなど、表記と読みが違う語は台本の read で置き換えています(字幕は表記のまま出ます)。
6. Remotion Lambda で書き出す
画面写真・声・台本を Remotion Lambda に渡して書き出します。画面写真はレンダー時の入力(inputProps)として渡しているので、Lambda 側のサイトに置くのは映像エンジンのコードだけです。台本や画面を差し替えても、サイトを上げ直す必要はありません。
Invoking lambdas ━━━━━━━━━━━━━━━━━━ 89/89
Rendered frames ━━━━━━━━━━━━━━━━━━ 28376ms
Encoded frames ━━━━━━━━━━━━━━━━━━ 24936ms
+ S3 .../renders/<render-id>/todo-acceptance-1080p.mp4 4.3 MB
90 λ's used, 38.03sec, Estimated cost $0.068
完了 46秒 → projects/todo-acceptance/out/todo-acceptance-1080p.mp4
約1分50秒・1920x1080 の動画が、 46秒・約0.07ドル(Remotion の見積もり) でできました。画面の文字を読める画質にしたいので、品質固定(CRF 32)で書き出しています。それでも2分弱で4.3MBに収まりました。
7. コマを並べて検品する
書き出した動画は、そのまま渡さずに 場面が変わるコマを切り出して並べ、1枚ずつ見ます 。

今回は1回目の書き出しで、赤枠のラベルが上の「すべて」のボタンや「本を返す」の行に重なって、文字が隠れるコマが見つかりました。ラベルを枠の下に出すよう台本を4か所直し("pos": "below")、書き直しました。2回目は 37秒・約0.06ドル でした。読み上げはキャッシュを使い回すので、ElevenLabs の API は呼び直していません。
この検品は、手間に見えて外せない工程です。以前、社内向けの手順動画を作ったときに、撮影に使った別のワークスペースの名前が 切り替えの途中の数コマにだけ 写っていて、配る直前に気づいたことがありました。目立つ場面だけを見ていると、こういうものを見落とします。
動画の型:最後に「確かめていないこと」を言わせる
受け入れテストの動画は、毎回この順番に固定しています。
| 順番 | 場面 | 目的 |
|---|---|---|
| 1 | 題名 | 何のプルリクエストの動画かを最初に言う |
| 2 | 変わること | 見る人が「何を確かめればよいか」を先に知る |
| 3 | 実際の画面 | 操作と結果を、赤枠とクリックの印つきで見せる |
| 4 | 確かめていないこと | 動画に映っていない範囲を、正直に言う |
| 5 | 合格としてよいか | 確かめたことの一覧と、判断の依頼 |
一番効いたのは、4の 「確かめていないこと」 です。動画は「映っているものは確かに動いた」ことの証拠にはなりますが、映っていないことについては何も言えません。そこを黙っていると、見る側は「全部確かめた」と受け取ってしまいます。
今回は、エージェントが次の2つを挙げてきました。

1つ目の「画面を開いたまま0時をまたいだとき」は、実装を読むとそのとおりです。「今日」の日付は画面を描き直すときに計算しているので、開いたまま日付が変わると、次に何か操作するまで前の日の判定のままになります。 差分を読んでいるだけでは私は気づかなかった と思います。これを仕様として受け入れるか、直すかを決めるのは人の仕事です。動画は、その判断の材料をそろえてくれます。
最後は、確かめたことの一覧と一緒に「この形で、合格としてよいか、ご確認をお願いします。」と判断を依頼して終わります。今回の一覧はこの4つでした。
- 期限を付けて追加でき、今日は黄色・期限切れは赤で出た
- 「今日まで」で、期限切れと今日の 2 件だけに絞り込めた
- 完了にすると「今日まで」から外れ、灰色の取り消し線になった
- 期限の判定は自動テスト 7 件が通っている
注意点・Tips
- Remotion のライセンスを確認する 。Remotion は、個人・従業員3人までの営利企業・非営利団体なら無料で使えますが、それ以外の会社は Company License が必要です。業務で使う前に、ライセンスを確認してください
- ElevenLabs の利用条件も確認する 。作った音声を社外に出す場合は、使っているプランで商用利用ができるかを確認してください
- 動画はテストの代わりではない 。動画が示すのは「映っている操作がそう動いた」ことだけです。ロジックは自動テストで押さえ、動画は「画面で見て自然か」を人が判断するための材料、と役割を分けています
- 写ってはいけないものに気をつける 。今回は練習用の TODO アプリなので問題ありませんが、実案件の画面では、組織名・ワークスペース名・アカウント名・顔アイコン・ID が写っていないかを、全コマで確かめてからぼかします。ぼかしは書き出した動画ではなく、 元の画面写真 にかけます(ズームでずれるため)
- 画質は撮る段階と書き出す段階の両方で決まる 。以前、720p・低いビットレートで書き出し、縮めて撮った画面も重なって、文字が読めない動画になりました。今は「等倍に近い大きさで撮る」「1080p・品質固定で書き出す」をひな形の既定にしています
- 書き出しの AWS アカウントを分ける 。Remotion Lambda は、関数やサイトを AWS アカウントに作ります。業務やお客様のアカウントを使わないよう、プロファイルを明示し、許可したプロファイル以外では動かないようにしています
- 鍵は台本やリポジトリに入れない 。ElevenLabs の API キーは、git の対象外の
.envに置き、表示もしないようにしています
おわりに
AI エージェントで並列に開発していると、実装の速さに対して、 人の確認がボトルネック になってきます。差分を読んで、手元で起動して、触って確かめる、を毎回やっていると、並列にした意味が薄れてしまいます。
受け入れテストを動画にしてもらうと、私の仕事は「2分弱の動画を見て、合格かどうかを決める」まで小さくなりました。しかも動画の最後で「確かめていないこと」を言わせることで、 何を確かめていないか まで分かった状態で判断できます。
Remotion Lambda なら1本あたり1分弱・数セントで書き出せるので、プルリクエストごとに作っても負担になりません。AI に任せる範囲が広がるほど、 人が判断しやすい形で報告させる仕組み が効いてくると感じています。
以上、どなたかの参考になれば幸いです。
参考











