0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

1本のShortsができるまでの全部 — AIエージェントと毎日更新のニュース動画を作る

0
Last updated at Posted at 2026-09-12

ずんだもんがAIニュースを5本読み上げるYouTube Shortsシリーズを、AIエージェント(Claude Code)と立ち上げました。ニュース集めから投稿までを6工程に分けた記録です。1本あたり約40分、費用はほぼゼロ。

できたもの: https://www.youtube.com/playlist?list=PLT9RqB3kcKYQ

コードは末尾の付録にまとめたので、まず全体像だけ読んでもらえれば十分です。


6工程を、上から下へ流す

1本のShortsができるまでの6工程

人間がやるのは工程1だけです。どのニュースを選ぶか、その数字は信用できるか。ここは任せていません。

2本目に打ったプロンプト(Claude Code)

入力 — Claude Code に送ったプロンプト

02の制作に進んでほしい。説明文にサイトとXの紹介も入れて、Xにも動画付きで投稿してください。

出力 — エージェントが実行したこと

ニュース収集 → 5本選定 → 台本生成 → 音声7本 → 口パク → ミックス → 1,763枚レンダリング → 品質ゲート8項目 → 事実確認(誤り1件を修正して再レンダリング) → サムネ2種 → YouTube公開 → 縦サムネ設定 → 再生リスト追加 → X投稿

この1つで最後まで走りました。途中で止めたのはXの投稿先アカウントを聞かれた1回だけです。


1本目は8回、2本目は1回

1回で走るようになったのは2本目からです。1本目は作りながら直しました。

下の表は、AIエージェント(Claude Code)のチャット欄に実際に入力したプロンプトの全部です。コマンドではなく、この日本語をそのまま渡しています。★が修正の指示にあたります。

Claude Codeに送ったプロンプト8回と、その結果

修正は6回。うち3回は同じ指摘でした。解決したのは、目で見るのをやめて測る仕組みを作ったときです。

そしてこの6回が、そのまま検証スクリプトになりました。2本目以降、同じ指摘は出ていません。


目視をやめる

エージェントは自分が作った動画を見られません。mp4は再生できず、1,700枚のフレームを全部は確認できず、音は聞けない。だから「間延びしている」の判断ができません。

そして人間の目も当てになりませんでした。重なりを直したつもりで、実機ではまだ重なっていた。重なっていた相手は動画の中ではなく、YouTubeのUI(右のボタン列、下のタイトル欄)だったからです。

実機UIのセーフゾーンを重ねた検証画像

赤い部分が、実機でボタンやタイトルに覆われる場所です。ここを自分で定義して、要素がはみ出していないかを機械に測らせます。

そこで、目で見ていた項目を数値に置き換えました。

品質ゲートの8項目

測ってみると、見えていなかったものが出てきました。

測って分かった4件

数値になれば、エージェントは自分で回して自分で直せます。「91%」と出れば原因を探し、もう一度測って「14%」を確認する。人間を待ちません。

閾値に根拠は要りません。過去の動画を見比べて「これ以上止まると飽きる」と感じた線を、そのまま数字にしただけです。大事なのは、線が引かれていて、機械が毎回同じ厳しさで測れることです。

形式は測れる。中身は測れない

「中国の7ラボがClaudeを蒸留、1.5億回」と書いたことがあります。出典を確認すると、1.51億回はそのうち1社の数値でした。嘘ではないが、合計だと誤読される。

これは画面をどれだけ測っても出てきません。別のエージェントに出典URLを渡し、「この数字は出典のどこに書いてあるか」を1件ずつ答えさせています。


1本あたり40分

工程別の所要時間。合計約40分

VOICEVOXもレンダリングも無料なので、費用は実質ゼロです。X投稿のAPI利用料が1回$0.200かかる程度でした。


自分の題材でやるなら

動画でなくても、スライドでも記事でもアプリ画面でも同じです。

  1. 不合格の条件を、先に数値で書く。 「良いもの」は定義できなくても「出せないもの」の線は引ける。3つで十分
  2. データと表現を分ける。 明日、内容だけ差し替えて2本目が作れるか
  3. 表現から時間を追い出す。 時刻を引数で受ければ、何度実行しても同じ絵になる
  4. 検証はコマンド1つ。 最後に合否の1行だけ出す。判断を挟ませない
  5. 事実は他人に検証させる。 「誇張はないか」ではなく「出典のどこにあるか」を聞く

残るのは、何を作るかどこに線を引くかだけです。


付録: 実装

台本をデータで書く

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を作る前に判定できます。

実機UIの領域を定義する

# 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)

404を取りこぼさない

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行だけです。


図つきの完全版(画面の構造図・セーフゾーンの実物・動画の埋め込み)はこちらです。
https://yuichi916.github.io/method/ai-agent-shorts.html

パイプライン一式(スクリプト・画面テンプレート・検証スクリプト・Claude Codeスキル)の配布も準備中です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?