このサイトでの挿絵は、Gemini Proによる生成が大半です。 また、情報収集・まとめなどのサイトについては、検索結果をベースとしたものを使用しています。可能な限り出典を明示するよう努めますが完全ではありません。各自で調べる事もお忘れなく。

podcast-tool 開発日記 #4 — 動画対応の幕開け

2025年8月後半。前回(第3回)でキャッシュ基盤と複数エンジン対応が固まったその翌週、podcast-tool に静かな転換点が訪れた。コミットハッシュ 43fe3a1——そこに video.py というファイルが初めて登場する。220行のそのモジュールが、「音声を作るツール」を「音と映像を作るツール」へと変えていく起点になった。

動画生成機能の初日(8月16日)

8月16日土曜日の午後1時、最初の動画生成機能が main に入った(43fe3a1)。コミットメッセージには「プロトタイプ版(1セクション1画像)」という但し書きがある。

この時点での仕様はシンプルだ。台本の各セクション(INTRO・MAIN1〜5・ENDING)ごとに1枚の静止画を割り当て、そのセクションの音声時間ぶんだけ画像を引き伸ばして動画化する。セクション動画を順に結合すれば、音声と同期したMP4が完成する——という「1セクション1画像」方式だ。

技術的な選択はこうだった。

  • 解像度: 1080p
  • フレームレート: 30fps
  • コーデック: H.264
  • 画像処理: Pillow でリサイズ、アスペクト比をレターボックスで吸収
  • 動画生成: ffmpeg を subprocess 経由で呼び出す
  • エントリポイント: --video オプション追加(音声生成と同時実行)

新たに生まれた video.py は、VideoConfig というデータクラスを中心に組み立てられていた。

1
2
3
4
5
6
7
8
@dataclass
class VideoConfig:
    """動画生成設定"""
    enabled: bool
    resolution: str
    framerate: int
    codec: str
    output_format: str

設定を型で束ねるのは、前回(第3回)から続く流儀だ。cache_utils.py でも同じアプローチを取っていた。「動くコードを書く」だけでなく「あとで変えやすいコードを書く」という意識が、ここでも形になっている。

同じコミットにはテストも276行分ついてくる(tests/test_video.py + tests/test_main_video.py)。7月に根づいた「TDDでいく」という習慣が、新領域にも最初から持ち込まれている。

同日14時、台本からトランジションを読む(35798c8

最初の動画実装から約2時間後、もう一本のコミットが続く。35798c8「mediaタグのtransition属性による切り替え効果機能を実装」——プロトタイプが入った当日に、早くも「複数mediaタグ対応は今後の拡張予定」を実装している。

追加されたのは3つのトランジション効果だ。

transition 値効果
fade前の画像から次の画像へフェード
extend前の画像を指定時間延長表示
cut即座に切り替え(デフォルト)

台本の media タグにこう書く。

[media:image.jpg transition=fade transition_duration=1.0]

これを config_utils.py の新関数 parse_media_tag() が解析し、video.pycreate_fade_transition() が実際のフェード映像を作る。台本の記述が画面上の動きに直結する——「書いた通りに動く」という感触がここで生まれた。

同日には仕様書も整備された(5b9cbe5)。SPECIFICATION.md に「E7トランジション効果」として仕様が追記され、README.md には使い方が説明される。機能とドキュメントを同日にそろえる癖がついている。

翌週の試行錯誤(8月17日〜23日)

初日に「動く」状態まで持ち込んだあとは、細部との戦いが続いた。

8/17 には動画ファイルを背景に使う機能が WIP として入る(9756087)。静止画だけでなく、動画クリップをループさせてセクション背景にするアイデアだ。

8/18 はビルドシステムの整備回になる(1829bba)。make/just 両対応のテンプレートベースビルドシステムが実装され、justfile に動画生成ターゲット video-m4a が追加された(6a1d2bf)。「コマンドひとつで音声+動画を両方作れる」という使い勝手が整い始める。同日 Linter 機能が一度追加されてすぐ Revert されるという一幕もあった(af00053e12767f)。まだ準備が整っていなかったのだろう。

8/23 にはフェード切り替えの画像サイズ問題が直される(0930043)。解像度が異なる画像間でフェードをかけると映像が崩れる問題で、generate_section_video 関数に前処理を統合して対応した(b4e8c27)。「実際に動かして見つかるバグ」の典型だ。同日、超解像対応とH.264エンコーダーの修正も入っている(ea26c23)——プロトタイプとしての動画から、品質への意識が芽生えてきた。

大きな整理(8月25日)

8月25日は「整理の日」だった。

午前中に PodcastApplicationクラス が追加される(3643199)。それまでグローバル変数として散らばっていた設定を、クラスに束ねて一元管理しようという大方針転換だ。

[refactor] PodcastApplicationクラスを追加してグローバル状態管理を改善
- PodcastApplicationクラスで設定管理を一元化
- 依存性注入パターンの基盤を構築
- グローバルAPP_CONFIG変数の段階的移行を開始

「段階的移行」という言葉が示す通り、これは一度には終わらない。続く4本のコミットで main() の書き換え、テストの修正、コマンドパス解決の移行が順番に実行された(33fb037, 9dcfb91, badaee8, 959cea5)。

さらに同日、大規模関数分割 のリファクタリング(9d2397f, 3fa5a14, 03865c0)が行われる。動画生成まわりの関数が肥大化していたのだろう。「Phase 2達成」という言葉が示す計画的な分割だ。

一時 Revert されていた Linter 機能もここで改めて入る(a8500ba)。プロジェクトデータの検証——config.json や台本の構造的な整合性を事前チェックする仕組みだ。動画が絡むと「設定ミスで動かない」パターンが増えるため、早期検出の価値が高くなる。

午後には統一エラーハンドリングシステムも追加された(4d2c219)。機能が増えれば、失敗時の情報も整理が要る。

背景動画の登場(8月27日〜28日)

8月27日、静止画の下で動画をループさせる「背景動画」機能が形になる(df74d87a36cb6b)。frame-based transitions というキーワードがコミットメッセージに現れる——セクションごとに画像を切り替えていた初期設計から、フレーム単位で映像を制御する方向へ進化している。

同日、並列音声処理のデッドロック問題も修正された(af3483a)。動画生成を組み込む前から複数スレッドで音声を処理していたが、そこに新しいコードが絡んで問題が顕在化したものと思われる。

8月28日には背景動画のクロスフェード機能が WIP として追加される(6b9dc43, 6eb0076)。「静止画を表示し続ける」から「動画が背景に流れる」へ——出力の質感が変わる一歩だ。

月末のセキュリティ対応(8月31日〜9月1日)

8月最終日には、GitHub Copilot 経由でセキュリティ改善 PR が入る(7553374, PR #2)。URL バリデーションの修正(aa6c837)——VOICEVOX エンジンの localhost 接続が弾かれていた問題だ。機能開発が進む中で見落とされがちなセキュリティ角度を、外部レビューが拾っている。

この期間でできたこと

できたことコミット
video.py 新設・--video オプション追加(1セクション1画像)43fe3a1
media タグの transition 属性(fade/extend/cut)35798c8
justfile への動画生成ターゲット追加6a1d2bf
テンプレートベースビルドシステム1829bba
PodcastApplication クラスによる状態管理一元化3643199
大規模関数分割(Phase 2)とテスト整備03865c0
Linter(プロジェクトデータ検証)本実装a8500ba
統一エラーハンドリング4d2c219
背景動画(静止画下でのループ再生)df74d87
背景動画クロスフェード(WIP)6b9dc43
セキュリティ改善 PR(Copilot)PR #2

転回点を振り返って

第3回まで podcast-tool は「音声を作るツール」だった。8月16日を境に、それは「音と映像を作るツール」へと舵を切った。

最初の video.py は220行のシンプルなプロトタイプだ。それが2週間で、トランジション効果・背景動画・クロスフェード・Linter・大規模リファクタリングまで進んだ。速い。しかしそれは無理に詰め込んだ速さではなく、「動かしながら見えてきた問題を直す」という積み重ねの速さだ。

PodcastApplication クラスの導入は、その勢いの中でも「後で困らないよう整理する」という判断だった。機能が増えるほど、設計の負債が後で響く。それを感じ取って、追加と整理を交互に繰り返している。

8月28日のコミット 6eb0076 のタイトルは「WIP: 背景動画クロスフェード機能の文書化」——まだ作りかけだ。次回は9月以降、この WIP がどこへ向かうかを追う。

次回予告

第5回は9月前半を追う予定だ。背景動画クロスフェードの完成、BGMイントロ・アウトロとの時間整合、そして YouTube Shorts 対応の字幕焼き込みへと展開していく。「音と映像」が揃ってきたあとの次の課題——届け方の多様化——が見えてくる時期だ。


この記事は podcast-tool のコミット履歴を一次資料として書いています。引用したコミットハッシュ・時刻・コード構成は当時のリポジトリ状態に基づきます(時刻は JST 表記)。

Hugo で構築されています。
テーマ StackJimmy によって設計されています。