AIエージェントで1から作る — 1分AIニュース(YouTube Shorts)
ずんだもんがAIニュースを5本読み上げるShortsシリーズを、AIエージェントと立ち上げました。 ニュース集めから投稿までを6工程に分けた記録です。1本あたり約40分、費用はほぼゼロ。
人間がやるのは工程1だけです。どのニュースを選ぶか、その数字は信用できるか。 ここは任せていません。
以下は、AIエージェント(Claude Code)のチャット欄に実際に入力した文です。 コマンドではなく、この日本語をそのまま渡しています。
この1つで最後まで走りました。途中で止めたのはXの投稿先アカウントを聞かれた1回だけです。
1回で走るようになったのは2本目からです。1本目は作りながら直しました。 下は、AIエージェント(Claude Code)に送ったプロンプトの全部です。 赤い番号が修正の指示にあたります。
修正は6回。うち3回は同じ指摘でした。解決したのは、目で見るのをやめて 測る仕組みを作ったときです。
そしてこの6回が、そのまま検証スクリプトになりました。2本目以降、同じ指摘は出ていません。
エージェントは自分が作った動画を見られません。mp4は再生できず、1,700枚のフレームを 全部は確認できず、音は聞けない。だから「間延びしている」の判断ができません。
そして人間の目も当てになりませんでした。重なりを直したつもりで、実機ではまだ重なっていた。 重なっていた相手は動画の中ではなく、YouTubeのUIだったからです。
そこで、目で見ていた項目を数値に置き換えました。
測ってみると、見えていなかったものが出てきました。
| 測ったら | 原因 |
|---|---|
| 画面の91%が静止していた | 背景の動きを、手前の不透明なパネルが隠していた(→14%) |
| 実機UIとほぼ全編重なっていた | キャラクターを画面の右下に置いていた |
| 最後の声が0.02秒切れていた | 尺を音声の終端ちょうどに合わせていた |
| キャラクターが消えていた | 画像が404。それでもレンダリングは成功扱いで終わる |
数値になれば、エージェントは自分で回して自分で直せます。 「91%」と出れば原因を探し、もう一度測って「14%」を確認する。人間を待ちません。
閾値に根拠は要りません。過去の動画を見比べて「これ以上止まると飽きる」と感じた線を、 そのまま数字にしただけです。
大事なのは、線が引かれていて、機械が毎回同じ厳しさで測れることです。
「中国の7ラボがClaudeを蒸留、1.5億回」と書いたことがあります。出典を確認すると、 1.51億回はそのうち1社の数値でした。嘘ではないが、合計だと誤読される。
これは画面をどれだけ測っても出てきません。別のエージェントに出典URLを渡し、 「この数字は出典のどこに書いてあるか」を1件ずつ答えさせています。
| 工程 | 時間 |
|---|---|
| 1. ニュース収集・選定 ← 人間 | 15〜20分 |
| 2. 台本作成 | 10分 |
| 3. 音声合成〜ミックス | 3分 |
| 4. レンダリング | 2分 |
| 5. 検証 + 事実確認 | 4分 |
| 6. 投稿・再生リスト・SNS | 5分 |
| 合計 | 約40分 |
VOICEVOXもレンダリングも無料なので、費用は実質ゼロです。 X投稿のAPI利用料が1回$0.200かかる程度でした。
動画でなくても、スライドでも記事でもアプリ画面でも同じです。
残るのは、何を作るかとどこに線を引くかだけです。
1本のニュースが持つ情報を全部宣言します。誰に関係あるか、結論、数字、読者がすべきこと、 出典とその種別、そして読み上げ用と表示用の2つの文。
最後の分離は必須です。音声合成には「ペーパーカット」と渡さないと読みを間違えますが、 画面には「PaperCut」と出したい。1つの文では両立しません。
dict(
audience="社内にサーバーがある人へ", # 誰に関係あるか
summary="AIエージェントが395組織を突破", # 3秒で読める結論
figs=[{"n":"395組織","l":"侵害された組織(48カ国)"},
{"n":"7分","l":"侵入から管理者権限まで"}],
impact="PaperCutは今すぐパッチ適用を", # 読者がすべきこと
text="ペーパーカットの脆弱性を突く攻撃で…", # 読み上げ用(カタカナ)
caption="PaperCutの脆弱性を突くAI主導の攻撃で…", # 字幕用(正表記)
source_url="https://…", sourceType="調査",
)
この形にすると、2本目はデータを差し替えるだけです。HTMLには触っていません。
本文のハッシュをファイル名に使い、同じものがあれば再利用します。
th = hashlib.sha1((ln["text"] + str(sid)).encode()).hexdigest()[:10]
f = AUD / f"l_{ln['idx']:03d}_{sp}_{th}.wav"
if f.exists():
sec = dur(f) # 本文が同じなら再利用
else:
vv(ln["text"], sid, f) # 変わった行だけ合成
事実確認で1行直したとき、再合成されたのは7本中1本でした。
CSSアニメーションは使いません。「この時刻の画面」を返す関数を1つ置き、 外から時刻を進めます。
window.__setTime = function(t){
$("pbar").style.width = (Math.min(t/TOTAL,1)*100) + "%";
$("bgfx").style.transform =
"translate("+(Math.sin(t*0.42)*230)+"px,"+(Math.cos(t*0.31)*300)+"px)";
};
for i in range(N):
pg.evaluate(f"window.__setTime({i/FPS:.4f})")
pg.screenshot(path=str(FR/f"f_{i:05d}.jpg"), type="jpeg", quality=88)
実時間に依存しないのでコマ落ちせず、何度実行しても同じ絵になります。 そして検証側が好きな時刻の画面を再現できます。
def rects_overlap(a, b):
ox = min(a["right"], b["right"]) - max(a["left"], b["left"])
oy = min(a["bottom"], b["bottom"]) - max(a["top"], b["top"])
if ox > 0 and oy > 0:
return {"w": round(ox, 1), "h": round(oy, 1)}
return None
0.1秒刻みで全編に回します。500〜600回の判定が数十秒で終わり、出力は1行です。
ep02: 重なりなし(0.1秒刻み, 全587フレーム走査)
動画ではなくHTMLに対して走るので、mp4を作る前に判定できます。
# YouTube Shorts 実機UIの目安(1080x1920基準)
SAFE_RIGHT_W, SAFE_RIGHT_Y0, SAFE_RIGHT_Y1 = 220, 760, 1780 # 右のボタン列
SAFE_BOTTOM_H = 310 # 下のキャプション欄
diffs = [float(np.abs(arrs[i]-arrs[i-1]).mean()/255*100)
for i in range(1, len(arrs))]
still_ratio = sum(1 for d in diffs if d < 0.5) / len(diffs)
requestfailed はネットワークエラーでしか発火しません。404は正常応答なので拾えない。
pg.on("response", lambda r: fails.append(r.url) if r.status >= 400 else None)
フレーム数は整数に丸められます。48.117秒は1,443フレーム=48.10秒。
音声の終端48.12秒に0.02秒足りず、-shortest が短い方に合わせていました。
TAIL = 0.7 # 最後の声にぴったり合わせると末尾が切れる
total += TAIL
$ python qc_video.py ep02
=== 品質ゲート(必須項目)===
尺 58.00s OK (Shorts上限60s)
音圧 LUFS -15.13 OK (-14±1.5)
冒頭0.5秒の輝度 61.0/255 OK
最長の静止 1.0s OK (3秒未満)
静止フレーム比率 6% OK (1/3未満)
判定: 全項目通過 — 投稿可
エージェントが読むのは、最後の1行だけです。
ここまでで同じ構造を組み直すことはできます。ただ、実際に動かすには、 VOICEVOXの読み間違いの潰し方、口パクの閾値、BGMのダッキング、API認証まわりなど、 書ききれない部分が残ります。それを含めた実行可能な一式を別途配布する予定です。
| 含まれるもの | 中身 |
|---|---|
| パイプライン本体 | 台本生成・音声合成・口パク・ミックス・レンダリング(13スクリプト) |
| 画面テンプレート | 本編と図解5種、横/縦サムネ |
| 検証スクリプト | 品質ゲート・重なり判定・セーフゾーン判定・構造図の生成 |
| 配信スクリプト | YouTube投稿・再生リスト・縦サムネ設定・X投稿 |
| Claude Code スキル | この手順をエージェントに実行させる SKILL.md |
| セットアップ手順 | VOICEVOX / Playwright / ffmpeg / API認証 |