Pythonで「スライド×ナレーション」動画を全自動生成してみた【VOICEVOX + MoviePy】
はじめに:動画制作の「地獄の手作業」から抜け出したくて
技術解説動画を作ろうとしたとき、最初は NotebookLM で自動生成を試みたんですが、出来上がった動画がなんとも「解説動画っぽい硬さ」で…。視聴者に最後まで見てもらえそうな品質には届きませんでした。
そこで方針を転換。NotebookLM でスライド骨子を作り → Canva でデザインしたスライドをベースに動画化するというアプローチに切り替えました。ビジュアルの品質はぐっと上がったんですが、今度は別の問題が出てきました。
「スライドの切り替え」と「VOICEVOX の合成音声」を手動で合わせる作業がつらすぎる…
スライド 1 枚ごとに音声の尺を測って、動画編集ソフトでクリップをドラッグして…。これ、全然クリエイティブじゃないですよね。エンジニアとしては「これ絶対コードで解決できる」と思ったのが、このパイプライン作りのきっかけです。
この記事では、PDF スライド・台本テキスト・BGM を入力するだけで ナレーション付き動画を全自動生成する Python スクリプト の実装について紹介します。
使った技術と選んだ理由
| 分野 | 技術名 | 役割 | ひとこと注意点 |
|---|---|---|---|
| PDF処理 | pdf2image / Pillow | PDF を PNG フレームへ変換・リサイズ・クロップ | Poppler のインストールが必要(brew install poppler) |
| 音声合成 | VOICEVOX API | 台本テキストから WAV を生成 | ローカルサーバーを先に起動しておく必要あり |
| 動画編集 | MoviePy | 画像 + 音声 + BGM をコードでタイムライン構築 | v2 系で API が変わってるので注意(後述) |
| 音声処理 | pydub | BGM のループ・音量調整(dB 指定) | BGM 音量はデフォルト −10dB(settings.json で変更可) |
全部 Python で連携できるのがポイントで、「PDF を食わせたら MP4 が出てくる」一気通貫のパイプラインになっています。
プロジェクト構成:複数動画を並行管理できる設計
movieMaker/
├── main.py # メインスクリプト
├── settings.json # 話者 ID・速度・クレジット等の設定
├── inputs/ # ここに動画ごとのフォルダを追加
│ └── project_A/
│ ├── slides.pdf # スライド
│ ├── script.txt # ページごとの台本
│ └── bgm.mp3 # BGM(なくてもOK)
├── temp/ # 中間ファイル置き場(処理後に消してOK)
│ └── project_A/
│ ├── images/ # page_001.png ...
│ └── audio/ # audio_001.wav ...
└── outputs/
└── project_A.mp4
inputs/ にプロジェクトフォルダを追加するだけで何本でも動画が作れます。settings.json 1 つで話者・速度・クレジットをまとめて管理できる構造にしました。
script.txt のフォーマット
台本は「数字 ---」でスライドごとに区切ります。PDF のページ数と台本のブロック数が合わないとエラーになるので、バリデーションを入れています。
1 ---
こんにちは。1 枚目のスライドの説明です。
2 ---
続いて 2 枚目。
3 ---
最後のスライドです。ありがとうございました。
処理の流れ:6 ステップで動画が完成する
Step 1:PDF → PNG 変換(pdf2image)
images = convert_from_path(pdf_path)
pdf2image.convert_from_path() でページごとに PNG を吐き出します。元のデザイン品質をそのまま維持できるのがうれしいところです。
Step 2:台本テキストをブロックに分割
blocks = re.split(r'\d*\s*---\s*', content)
正規表現で --- 区切りを検出してリスト化します。シンプルですが、これが後続の処理全体のキーになります。
Step 3:VOICEVOX でナレーション生成
各ブロックを 2 段階の API コールで WAV に変換します。
-
POST /audio_query→ 発話クエリ JSON を取得 -
POST /synthesis→ WAV バイナリを保存
settings.json の speed_scale(デフォルト 1.1)でナレーション速度を調整できます。話者キャラクター(speaker_id)も自由に変えられます。
Step 4:映像 × 音声の自動同期(MoviePy)← ここが核心
このパイプラインで一番重要な部分です。
やっていること: 各 WAV ファイルを AudioFileClip として読み込み、その .duration(秒)を ImageClip.with_duration() にそのまま渡します。
audio_clip = AudioFileClip(str(wav_path))
img_clip = ImageClip(img_array).with_duration(audio_clip.duration)
img_clip = img_clip.with_audio(audio_clip)
これだけで「このスライドはナレーションが 4.5 秒だったから 4.5 秒表示する」が自動で決まります。手動でクリップをドラッグする作業が完全になくなります。
タイムラインのドリフトも防げる: 各クリップの duration を実測値から設定するので、30 枚スライドの長い動画でも後半で映像と音声がズレる「ドリフト」が起きません。
解像度の自動判定もやっています: 最初のスライドのアスペクト比を見て、横長なら 1920×1080、縦長なら 1080×1920 をターゲット解像度に自動選択。Pillow でリサイズ → センタークロップして全スライドを統一解像度に揃えます。縦動画(ショート・リール)と横動画(YouTube)を同じパイプラインで作れるのが地味に便利です。
MoviePy v2 の注意点
v1 系のset_duration()やset_audio()は v2 ではwith_duration()/with_audio()に変わっています。バージョンを確認してから使いましょう。
Step 5:BGM のミキシング(pydub)
bgm = AudioSegment.from_file(str(bgm_path))
bgm = bgm + settings["audio"]["bgm_volume_reduction_db"] # デフォルト -10dB
BGM を指定の dB だけ下げ、動画尺に合わせてループ・カット。MoviePy の CompositeAudioClip でナレーションと合成します。
Step 6:MP4 書き出し
final_clip.write_videofile(
str(out_file),
fps=24,
codec="libx264",
audio_codec="aac"
)
outputs/{プロジェクト名}.mp4 として保存されます。
便利機能①:部分実行オプション
台本を 1 行直すたびに全工程を動かし直すのは時間がもったいないですよね。そこで 特定のステップだけ再実行できるオプション を用意しました。
# 台本を修正したら音声だけ再生成
python main.py project_A --only-audio
# それをもとに動画だけ合成
python main.py project_A --only-video
# PDF を差し替えたら画像化だけやり直し
python main.py project_A --only-pdf
# 全部やる(初回はこれ)
python main.py project_A
--only-audio → --only-video の 2 ステップで済むことが多くなり、試行錯誤のサイクルが体感でかなり速くなりました。
便利機能②:クレジットオーバーレイの自動生成
最後のスライドに VOICEVOX のキャラクター名・BGM 素材提供元のクレジットを自動で合成する機能もつけました。
// settings.json
"credit": {
"enabled": true,
"position": "top_right",
"margin_px": 80,
"font_path": "resources/font.ttf",
"font_size": 36
}
enabled: true にしておくだけで、Pillow の RGBA 合成(Image.alpha_composite)を使って半透明の黒背景 + 縁取りテキストをスライドに書き込みます。権利表記の記載漏れという凡ミスをコード側でゼロにできるのがポイントです。
まとめ
このパイプラインを作ってから、動画制作にかかる時間が数時間 → 数分になりました。「編集する時間」がほぼゼロになり、「何を伝えるか」の台本設計だけに集中できるようになったのが一番大きな変化です。
個人的な考えですが、今は Google の NotebookLM をはじめとした AI プロダクトがどんどん強くなっていますよね。競合プロダクトは大変だなと思いつつ、個人開発者的には「巨人の肩に乗る」戦略を取るのが現実的だと思っています。このパイプラインも、最もコストがかかるスライド生成を NotebookLM にアウトソースして、自前の Python で「組み立て」の部分だけを担うという役割分担にしています。
AI を直接 API で叩き続けるより、良いプロダクトをうまく組み合わせてコードで自動化する方が、コスト面でもサステナビリティ的にもリーズナブルだと感じています。
実際に生成した動画がこちらです:
https://youtu.be/sO23CL78eus?si=rOjEn9pPucaSfbrM
縦型のショート動画も同じパイプラインで作れます:
https://youtube.com/shorts/eCi-LeYOPK4?si=WpmE4GGtDNzQSNc4
何か参考になれば幸いです!