自己紹介
株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。
Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
▶コーポレートサイト
はじめに
「YouTube動画を作りたいけど、編集が面倒で手が出ない」
個人開発者やエンジニアなら、一度はそう思ったことがあるのではないでしょうか。自分もそうでした。台本を書いて、スライドを作って、音声を録って、編集して......。動画1本に何時間もかかるのが普通です。
そこで、Claude Code に指示するだけで、台本・スライドHTML・サムネイル・VOICEVOX音声まで自動生成されるパイプラインを構築しました。人間がやるのは iMovie での最終合成と YouTube へのアップロードだけです。
実際にこのパイプラインで制作した動画がこちらです。
▶ Claude Code vs ChatGPT ― 何が違うのか

この記事では、パイプラインの全体像と、各ステップの実装を詳しく紹介します。
全体像
制作フローは以下の9ステップです。
| # | ステップ | 担当 | 成果物 |
|---|---|---|---|
| 1 | PLAN.md で次の動画を特定 | 人間 | テーマ決定 |
| 2 | 台本(Markdown)を生成 | Claude Code | scripts/002_cc_vs_chatgpt.md |
| 3 | スライドHTML 9枚を生成 | Claude Code |
slides/002_cc_vs_chatgpt/slide_01.html ... |
| 4 | サムネイルHTML を生成 | Claude Code | thumbnails/002_cc_vs_chatgpt.html |
| 5 | Puppeteer で HTML → PNG 変換 | Claude Code | スライド PNG 9枚 + サムネイル PNG |
| 6 | 掛け合い形式の音声台本に変換 | Claude Code | episodes/002_cc_vs_chatgpt/002_cc_vs_chatgpt_script.txt |
| 7 | VOICEVOX + Python で音声生成・結合 | Claude Code | WAV 46件 → 結合 WAV(5分1秒) |
| 8 | iMovie でスライド + 音声を合成 | 人間 | MP4 |
| 9 | YouTube に投稿 | 人間 | 公開 |
9ステップのうち、人間が手を動かすのは 1・8・9 の3つだけです。残り6ステップはすべて Claude Code が実行します。
フォルダ構成
~/Youtube/
├── GUIDE.md # 運用ガイド(テンプレート集)
├── PLAN.md # 動画一覧・スケジュール
├── youtube_audio_pipeline.py # 音声生成パイプライン
├── scripts/ # 台本(Markdown)
│ └── 002_cc_vs_chatgpt.md
├── slides/ # スライドHTML・PNG(1920x1080)
│ └── 002_cc_vs_chatgpt/
│ ├── slide_01.html
│ ├── slide_01.png
│ └── ...
├── thumbnails/ # サムネイルHTML・PNG(1280x720)
│ ├── 002_cc_vs_chatgpt.html
│ └── 002_cc_vs_chatgpt.png
└── episodes/ # エピソード別の音声ファイル
└── 002_cc_vs_chatgpt/
├── 002_cc_vs_chatgpt_script.txt # 音声用台本
├── 002_cc_vs_chatgpt_combined.wav # 結合済み音声
└── audio_002_cc_vs_chatgpt/ # 個別WAVファイル
├── 001_つむぎ.wav
├── 002_きりたん.wav
└── ...(46件)
ステップ1: PLAN.md で動画を管理する
PLAN.md にシリーズ全体の動画一覧を記載しています。Claude Code はこのファイルを読んで「次に作るべき動画」を特定します。
# YouTube 動画一覧
| # | タイトル | ベース素材 | 想定尺 | 状態 |
|---|---------|-----------|--------|------|
| 001 | CLAUDE.mdを書くだけで出力品質が劇的に変わる | Qiita: claude_code/09 | 5-7分 | 未着手 |
| 002 | Claude Code vs ChatGPT ― 何が違うのか | X図解: 002 | 5-7分 | 公開済 |
| 003 | Claude Codeの便利コマンド5選 | X図解: 003 | 5-7分 | 未着手 |
Claude Code への指示は一言で済みます。
PLAN.md を見て、002 の動画制作を始めてください。
ステップ2: 台本を生成する
Claude Code は、ベース素材(Qiita 記事や X 図解)を読み込み、YouTube 向けの話し言葉に変換した台本を scripts/ に生成します。
台本テンプレート
GUIDE.md にテンプレートを定義しておくことで、Claude Code が毎回同じ構成で台本を生成します。
# 動画タイトル
## 動画情報
- 想定尺: X分
- ベース記事: Qiitaの記事パス
- ベース図解: X図解の番号
---
## オープニング(30秒)
[スライド: タイトル]
(読み上げテキスト)
---
## セクション1: 見出し(2分)
[スライド: slide_02]
(読み上げテキスト)
実際に生成された台本(抜粋)
## セクション1: ChatGPTのアプローチ(0:30〜1:45)
[スライド: slide_02 ChatGPTのアプローチ]
まず、ChatGPTのコーディング支援から見ていきましょう。
ChatGPTは「チャット画面でコードを生成する」というアプローチです。
質問を入力すると、チャット画面にコードが表示される。
で、そのコードをコピーして、自分でファイルに貼り付ける必要があります。
ポイントは以下の通りです。
- 1文を短くして読み上げやすくする
- 「〜ですよね」「〜なんです」のような口語体を使う
- 1スライドあたり30秒〜1分を目安にする
ステップ3: スライドHTMLを生成する
スライドは HTML で作成し、Puppeteer で PNG に変換します。HTML にする理由は、Claude Code がコードとしてスライドを直接生成・編集できるからです。
スライド仕様
- サイズ: 1920 x 1080px(フルHD)
- タイトル/まとめスライド: ダーク背景
- 解説スライド: 白背景
タイトルスライド(ダーク背景)のテンプレート
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1920px; height: 1080px;
font-family: -apple-system, BlinkMacSystemFont, 'Hiragino Sans', sans-serif;
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%);
display: flex; align-items: center; justify-content: center;
position: relative; overflow: hidden;
}
.grid-pattern {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background-image:
linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
background-size: 60px 60px;
pointer-events: none;
}
.bg-circle {
position: absolute; border-radius: 50%;
opacity: 0.07; pointer-events: none;
}
.bg-circle-1 { width: 600px; height: 600px; background: #3b82f6; top: -200px; right: -100px; }
.bg-circle-2 { width: 400px; height: 400px; background: #8b5cf6; bottom: -150px; left: -50px; }
.content { text-align: center; z-index: 1; }
.badge {
display: inline-block;
background: rgba(59,130,246,0.15);
border: 1px solid rgba(59,130,246,0.3);
color: #60a5fa;
font-size: 16px; font-weight: 600;
padding: 8px 20px; border-radius: 40px;
margin-bottom: 32px;
}
.main-title {
font-size: 52px; font-weight: 800;
color: #f1f5f9; line-height: 1.4;
margin-bottom: 24px;
}
.highlight {
background: linear-gradient(90deg, #3b82f6, #8b5cf6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.subtitle { font-size: 22px; color: #94a3b8; }
.bottom-bar {
position: absolute; bottom: 0; left: 0;
width: 100%; height: 6px;
background: linear-gradient(90deg, #3b82f6, #8b5cf6, #06b6d4);
}
.account {
position: absolute; bottom: 24px; left: 40px;
font-size: 16px; color: #94a3b8;
}
.page-number {
position: absolute; bottom: 24px; right: 40px;
font-size: 18px; color: #64748b;
}
</style>
</head>
<body>
<div class="grid-pattern"></div>
<div class="bg-circle bg-circle-1"></div>
<div class="bg-circle bg-circle-2"></div>
<div class="content">
<div class="badge">Claude Code 解説</div>
<div class="main-title">
スライドタイトル<br>
<span class="highlight">強調テキスト</span>
</div>
<div class="subtitle">サブタイトル</div>
</div>
<div class="bottom-bar"></div>
<div class="account">@kotaro_ai_lab</div>
<div class="page-number">1 / 9</div>
</body>
</html>
白背景スライドのテンプレート
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1920px; height: 1080px;
font-family: -apple-system, BlinkMacSystemFont, 'Hiragino Sans', sans-serif;
background: #f8fafc;
padding: 60px 80px;
position: relative;
}
.slide-title {
font-size: 40px; font-weight: 700;
color: #1e293b;
margin-bottom: 40px;
padding-bottom: 16px;
border-bottom: 4px solid #3b82f6;
}
.page-num {
position: absolute; bottom: 30px; right: 50px;
color: #94a3b8; font-size: 18px; font-weight: 600;
}
.accent-bar {
position: absolute; bottom: 0; left: 0;
width: 100%; height: 6px;
background: linear-gradient(90deg, #3b82f6, #8b5cf6, #06b6d4);
}
.channel-name {
position: absolute; bottom: 30px; left: 50px;
color: #94a3b8; font-size: 16px;
}
</style>
</head>
<body>
<div class="slide-title">見出し</div>
<!-- スライド内容をここに記述 -->
<div class="channel-name">@kotaro_ai_lab</div>
<div class="page-num">2 / 9</div>
<div class="accent-bar"></div>
</body>
</html>
デザインルール
GUIDE.md に以下のルールを記載しておくと、Claude Code が統一感のあるスライドを生成します。
- X 図解(1200x675)より大きいので、フォントサイズを1.5倍にする
- タイトル: 40-56px / ラベル: 22-24px / 詳細: 18-20px
- 余白を多めに取る(動画は画面が大きいので情報密度を下げる)
- 左下に
@kotaro_ai_labを常に表示
ステップ4: サムネイルHTMLを生成する
サムネイルも HTML で作成します。仕様は以下の通りです。
- サイズ: 1280 x 720px(YouTube推奨)
- ダーク背景 + 大きな白文字
- キーワードにグラデーション色
- スマホでも読める文字サイズ(最低48px)
サムネイルHTMLの例
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1280px; height: 720px;
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%);
display: flex; align-items: center; justify-content: center;
font-family: -apple-system, BlinkMacSystemFont, 'Hiragino Sans', sans-serif;
position: relative; overflow: hidden;
}
.bg-glow1 {
position: absolute; width: 400px; height: 400px;
background: radial-gradient(circle, rgba(59,130,246,0.15) 0%, transparent 70%);
top: -100px; right: -50px;
}
.bg-glow2 {
position: absolute; width: 350px; height: 350px;
background: radial-gradient(circle, rgba(139,92,246,0.12) 0%, transparent 70%);
bottom: -80px; left: -50px;
}
.content {
display: flex; flex-direction: column; align-items: center;
gap: 12px; z-index: 1; text-align: center;
}
.line1 {
font-size: 52px; font-weight: 900; color: #fff;
text-shadow: 0 2px 20px rgba(0,0,0,0.5);
}
.line2 {
font-size: 64px; font-weight: 900;
background: linear-gradient(90deg, #3b82f6, #8b5cf6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.gradient-bar {
position: absolute; bottom: 0; left: 0;
width: 100%; height: 4px;
background: linear-gradient(90deg, #3b82f6, #8b5cf6, #06b6d4);
}
</style>
</head>
<body>
<div class="bg-glow1"></div>
<div class="bg-glow2"></div>
<div class="content">
<div class="line1">メインタイトル</div>
<div class="line2">強調キーワード</div>
</div>
<div class="gradient-bar"></div>
</body>
</html>
ステップ5: Puppeteer で HTML → PNG 変換
Claude Code が Node.js + Puppeteer を使って HTML を PNG に変換します。
スライド変換(1920x1080)
cd ~/Youtube && node -e "
const puppeteer = require('puppeteer');
const path = require('path');
async function shot(html, out) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 2 });
await page.goto('file://' + path.resolve(html), { waitUntil: 'networkidle0' });
await page.screenshot({ path: out, clip: { x: 0, y: 0, width: 1920, height: 1080 } });
await browser.close();
console.log('done: ' + out);
}
const base = path.resolve('slides/002_cc_vs_chatgpt');
(async () => {
for (let i = 1; i <= 9; i++) {
const num = String(i).padStart(2, '0');
await shot(base + '/slide_' + num + '.html', base + '/slide_' + num + '.png');
}
})();
"
サムネイル変換(1280x720)
cd ~/Youtube && node -e "
const puppeteer = require('puppeteer');
const path = require('path');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 720, deviceScaleFactor: 2 });
const htmlPath = path.resolve('thumbnails/002_cc_vs_chatgpt.html');
await page.goto('file://' + htmlPath, { waitUntil: 'networkidle0' });
await page.screenshot({
path: path.resolve('thumbnails/002_cc_vs_chatgpt.png'),
clip: { x: 0, y: 0, width: 1280, height: 720 }
});
await browser.close();
console.log('done');
})();
"
deviceScaleFactor: 2 を指定することで、Retina相当の高解像度画像が生成されます。
ステップ6: 掛け合い形式の音声台本に変換する
台本(Markdown)を、VOICEVOX で読み上げるための音声台本(テキスト)に変換します。
今回は「つむぎ」と「きりたん」の掛け合い形式を採用しました。Claude Code に以下のように指示するだけで変換されます。
台本 scripts/002_cc_vs_chatgpt.md を、つむぎ(メイン解説)ときりたん(相槌・質問)の
掛け合い形式に変換して、episodes/002_cc_vs_chatgpt/002_cc_vs_chatgpt_script.txt に保存して。
音声台本の形式
━━━━━━━━━━━━━━━━━━━━━━━━━
【Claude Code解説】Claude Code vs ChatGPT ― 何が違うのか
━━━━━━━━━━━━━━━━━━━━━━━━━
■ オープニング
つむぎ:
AIでコーディングって聞くと、ChatGPTを思い浮かべる人が多いですよね。
きりたん:
確かに。ChatGPT使ってコード書いてもらってる人は多いよね。
つむぎ:
でも実は、Claude CodeとChatGPTって、同じAIコーディングでもアプローチが
全く違うんです。今回はこの2つを比較して、何がどう違うのかを解説していきます。
パーサーが認識するルールは以下の通りです。
-
つむぎ:またはきりたん:で話者を切り替え -
━や■で始まる行はセクション区切り(音声生成の対象外) - 空行は無視される
ステップ7: VOICEVOX + Python で音声を生成する
ここがパイプラインの核心部分です。Python スクリプト youtube_audio_pipeline.py が以下を自動実行します。
- VOICEVOX API に接続して話者IDを取得
- 音声台本をパースしてセリフを抽出
- セリフごとに VOICEVOX で音声合成(WAV)
- 全 WAV ファイルを1つに結合
前提条件
-
VOICEVOX がローカルで起動していること(
http://localhost:50021) - Python 3 と
requestsライブラリがインストール済みであること
youtube_audio_pipeline.py
"""
YouTube解説動画 音声パイプライン
VOICEVOX を使って音声台本から音声を自動生成する
使い方:
python3 ~/Youtube/youtube_audio_pipeline.py 002_cc_vs_chatgpt
"""
import requests
import json
import os
import re
import sys
import wave
from pathlib import Path
VOICEVOX_URL = "http://localhost:50021"
YOUTUBE_DIR = Path(__file__).parent
EPISODES_DIR = YOUTUBE_DIR / "episodes"
STYLE_NAME = "ノーマル"
SPEAKER_MAP = {
"きりたん": "東北きりたん",
"つむぎ": "春日部つむぎ",
}
def get_speaker_ids():
"""VOICEVOXから話者IDを取得する"""
try:
res = requests.get(f"{VOICEVOX_URL}/speakers", timeout=5)
res.raise_for_status()
except requests.exceptions.ConnectionError:
print("VOICEVOX に接続できません。アプリが起動しているか確認してください。")
sys.exit(1)
speakers = res.json()
id_map = {}
for speaker in speakers:
for key, vox_name in SPEAKER_MAP.items():
if vox_name in speaker["name"]:
for style in speaker["styles"]:
if STYLE_NAME in style["name"]:
id_map[key] = style["id"]
break
if key not in id_map and speaker["styles"]:
id_map[key] = speaker["styles"][0]["id"]
for key in SPEAKER_MAP:
if key not in id_map:
id_map[key] = speakers[0]["styles"][0]["id"]
print(f" {key} が見つからないためフォールバックIDを使用します")
for key, sid in id_map.items():
print(f" {key} -> speaker_id: {sid}")
return id_map
def parse_script(filepath):
"""音声台本をパースして (話者, テキスト) のリストを返す"""
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
lines = content.split("\n")
result = []
current_speaker = None
current_lines = []
speaker_pattern = re.compile(r"^(きりたん|つむぎ):\s*$")
for line in lines:
m = speaker_pattern.match(line.strip())
if m:
if current_speaker and current_lines:
text = "\n".join(current_lines).strip()
if text:
result.append((current_speaker, text))
current_speaker = m.group(1)
current_lines = []
elif current_speaker:
if line.startswith("━") or line.startswith("─") or \
line.startswith("■") or line.startswith("【参考"):
if current_lines:
text = "\n".join(current_lines).strip()
if text:
result.append((current_speaker, text))
current_speaker = None
current_lines = []
else:
stripped = line.strip()
if stripped:
current_lines.append(stripped)
if current_speaker and current_lines:
text = "\n".join(current_lines).strip()
if text:
result.append((current_speaker, text))
return result
def generate_audio(text, speaker_id):
"""VOICEVOX APIで音声を合成する"""
res = requests.post(
f"{VOICEVOX_URL}/audio_query",
params={"text": text, "speaker": speaker_id},
timeout=30
)
res.raise_for_status()
query = res.json()
res2 = requests.post(
f"{VOICEVOX_URL}/synthesis",
params={"speaker": speaker_id},
data=json.dumps(query),
headers={"Content-Type": "application/json"},
timeout=60
)
res2.raise_for_status()
return res2.content
def merge_wav_files(wav_paths, output_path):
"""複数のWAVファイルを1つに結合する"""
data_list = []
params = None
for path in wav_paths:
with wave.open(str(path), "rb") as w:
if params is None:
params = w.getparams()
frames = w.readframes(w.getnframes())
data_list.append(frames)
with wave.open(str(output_path), "wb") as out:
out.setparams(params)
for data in data_list:
out.writeframes(data)
total_frames = sum(len(d) for d in data_list) // (params.sampwidth * params.nchannels)
duration_sec = total_frames / params.framerate
return duration_sec
def main():
if len(sys.argv) < 2:
print("使い方: python3 youtube_audio_pipeline.py <episode_name>")
print("例: python3 youtube_audio_pipeline.py 001_claudemd")
sys.exit(1)
episode_name = sys.argv[1]
print("=" * 55)
print(f" YouTube音声パイプライン [{episode_name}]")
print("=" * 55)
episode_dir = EPISODES_DIR / episode_name
script_file = episode_dir / f"{episode_name}_script.txt"
audio_dir = episode_dir / f"audio_{episode_name}"
combined_file = episode_dir / f"{episode_name}_combined.wav"
if not script_file.exists():
print(f"\n 台本ファイルが見つかりません: {script_file}")
sys.exit(1)
audio_dir.mkdir(exist_ok=True)
print("\n STEP 1: VOICEVOX に接続中...")
speaker_ids = get_speaker_ids()
print(f"\n STEP 2: 台本を読み込み中...")
lines = parse_script(script_file)
print(f" {len(lines)} 件のセリフを検出")
print(f"\n STEP 3: 音声を生成中... ({len(lines)} 件)")
errors = []
wav_paths = []
for i, (speaker, text) in enumerate(lines, start=1):
speaker_id = speaker_ids[speaker]
filename = f"{i:03d}_{speaker}.wav"
output_path = audio_dir / filename
wav_paths.append(output_path)
if output_path.exists():
print(f" [{i:03d}/{len(lines)}] スキップ(生成済み): {filename}")
continue
preview = text[:25].replace("\n", " ")
if len(text) > 25:
preview += "..."
print(f" [{i:03d}/{len(lines)}] {speaker}: {preview}")
try:
audio = generate_audio(text, speaker_id)
with open(output_path, "wb") as f:
f.write(audio)
except Exception as e:
print(f" エラー: {e}")
errors.append((i, speaker, str(e)))
success = len(lines) - len(errors)
print(f"\n 音声生成完了: {success}/{len(lines)} 件")
print(f"\n STEP 4: 音声ファイルを結合中...")
existing_wavs = [p for p in wav_paths if p.exists()]
if len(existing_wavs) == 0:
print(" 結合できるファイルがありません")
sys.exit(1)
try:
duration = merge_wav_files(existing_wavs, combined_file)
minutes = int(duration // 60)
seconds = int(duration % 60)
print(f" 結合完了: {combined_file.name}")
print(f" 総再生時間: {minutes}分{seconds}秒")
except Exception as e:
print(f" 結合エラー: {e}")
sys.exit(1)
print("\n" + "=" * 55)
print(" パイプライン完了!")
print("=" * 55)
print(f" 個別ファイル: {audio_dir}/")
print(f" 結合ファイル: {combined_file}")
print(f" 総再生時間 : {minutes}分{seconds}秒")
if errors:
print(f" エラー : {len(errors)} 件")
print()
print(" 次のステップ:")
print(f" 1. iMovie でスライド画像を順番に配置")
print(f" 2. {episode_name}_combined.wav をインポート")
print(" 3. スライドの切り替えタイミングを音声に合わせる")
print(" 4. YouTube にアップロード!")
print("=" * 55)
if __name__ == "__main__":
main()
実行方法
# 1. VOICEVOX を起動しておく(GUIアプリを開くだけでOK)
# 2. パイプラインを実行
python3 ~/Youtube/youtube_audio_pipeline.py 002_cc_vs_chatgpt
実行結果(002_cc_vs_chatgpt の場合)
=======================================================
YouTube音声パイプライン [002_cc_vs_chatgpt]
=======================================================
STEP 1: VOICEVOX に接続中...
きりたん -> speaker_id: 80
つむぎ -> speaker_id: 8
STEP 2: 台本を読み込み中...
46 件のセリフを検出
STEP 3: 音声を生成中... (46 件)
[001/046] つむぎ: AIでコーディングって聞くと、Ch...
[002/046] きりたん: 確かに。ChatGPT使ってコード書...
...
STEP 4: 音声ファイルを結合中...
結合完了: 002_cc_vs_chatgpt_combined.wav
総再生時間: 5分1秒
=======================================================
パイプライン完了!
=======================================================
46件のセリフから WAV ファイルが生成され、1つに結合されました。総再生時間は5分1秒です。
仕組みの詳細
パイプラインは4つのステップで構成されています。
STEP 1: 話者ID取得
VOICEVOX の /speakers API を呼び出し、「東北きりたん」「春日部つむぎ」の speaker_id を取得します。SPEAKER_MAP で台本上の名前と VOICEVOX 上のキャラクター名を対応付けています。
SPEAKER_MAP = {
"きりたん": "東北きりたん",
"つむぎ": "春日部つむぎ",
}
STEP 2: 台本パース
parse_script() が音声台本を解析し、(話者, テキスト) のタプルリストを返します。正規表現 ^(きりたん|つむぎ):\s*$ で話者の切り替えを検出し、セクション区切り文字(━, ■ など)で区間を終了します。
STEP 3: 音声合成
VOICEVOX の REST API を2段階で呼び出します。
-
/audio_query— テキストから音声合成クエリを生成 -
/synthesis— クエリから WAV バイナリを生成
生成済みのファイルが存在する場合はスキップするため、途中で失敗しても再実行すれば続きから処理されます。
STEP 4: WAV 結合
Python の wave モジュールを使って全 WAV ファイルを順番に結合します。外部ライブラリは不要です。
ステップ8-9: iMovie で合成 → YouTube に投稿
最後の手動ステップです。
- iMovie を開く
- スライド PNG を順番にタイムラインに配置
-
002_cc_vs_chatgpt_combined.wavをインポート - スライドの切り替えタイミングを音声に合わせる
- 書き出して YouTube にアップロード
この部分は手動ですが、スライドと音声がすべて揃った状態なので、15〜20分程度で完了します。
GUIDE.md で Claude Code の出力品質を安定させる
パイプライン全体を支えているのが GUIDE.md(運用ガイド)です。以下の情報を1つのファイルにまとめています。
| セクション | 内容 |
|---|---|
| フォルダ構成 | 各ファイルの保存先ルール |
| 台本テンプレート | セクション構成・口語体のルール |
| スライドHTML テンプレート | 白背景・ダーク背景の CSS |
| サムネイル仕様 | サイズ・デザイン方針 |
| Puppeteer コマンド | 画像変換の実行コマンド |
| 投稿時の設定 | タイトル・説明欄・タグのテンプレート |
Claude Code はこの GUIDE.md を読み込んだうえで各ステップを実行するため、毎回ゼロから指示する必要がありません。「PLAN.md を見て次の動画を作って」の一言で、GUIDE.md のルールに従った成果物が生成されます。
実際の Claude Code への指示例
動画1本を制作するとき、Claude Code に送る指示は以下のようなものです。
PLAN.md を見て、002 の動画制作を始めてください。
1. X図解 002 をベースに台本を作成
2. スライドHTML 9枚を生成
3. サムネイルHTMLを生成
4. Puppeteer で全て PNG に変換
5. 掛け合い形式(つむぎ・きりたん)の音声台本に変換
6. youtube_audio_pipeline.py を実行して音声を生成
Claude Code はこの指示を受けて、GUIDE.md のテンプレートに従いながら、ステップ2〜7を順番に実行していきます。途中で「スライドの情報量が多すぎる」「フォントサイズが小さい」といったフィードバックを返すと、その場で修正してくれます。
かかった時間の比較
| 工程 | 手動でやった場合 | このパイプライン |
|---|---|---|
| 台本執筆 | 1〜2時間 | 2〜3分(Claude Code 生成) |
| スライド作成 | 2〜3時間 | 5〜10分(HTML 生成 + Puppeteer 変換) |
| サムネイル作成 | 30分〜1時間 | 2〜3分 |
| 音声録音 | 1〜2時間 | 5〜10分(VOICEVOX 自動生成) |
| 動画編集 | 1〜2時間 | 15〜20分(素材が揃っているため) |
| 合計 | 5.5〜10時間 | 30〜45分 |
特に音声生成の自動化が大きく、「録音のやり直し」が不要になったことで大幅に時間を短縮できています。
まとめ
- Claude Code に GUIDE.md(運用ガイド)を読ませることで、台本・スライド・サムネイル・音声台本を自動生成できる
- VOICEVOX + Python パイプラインで、音声合成から結合まで完全自動化
- Puppeteer で HTML → PNG 変換することで、スライドもコードとして管理できる
- 人間がやるのは iMovie での最終合成と YouTube 投稿だけ
- 動画1本あたりの制作時間を 5〜10時間 → 30〜45分 に短縮
「YouTube をやりたいけど編集が面倒」というエンジニアの方は、ぜひこのアプローチを試してみてください。すべてのソースコードは HTML と Python で構成されているため、自分の環境に合わせたカスタマイズも容易です。
参考
@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!