このシリーズについて
podcast-tool という自作ツールがある。台本と話者定義、BGM設定を放り込むと、音声合成エンジンで読み上げて、BGMを混ぜて、最終的に動画付きの一本に仕上げてくれる——そういうものだ。気づけば1年近く、1000以上のコミットを積み重ねていた。
この連載は、その積み重ねを コミット履歴という一次資料 から振り返る開発日記だ。「この頃、何を考えて、どう作っていたのか」を、当時のソースコードと差分を引きながら掘り起こしていく。扱うのはあくまでシステム・コードの話。ポッドキャストそのものの中身には踏み込まない。
進め方の建て付けも書いておく。ネタを決めて一次資料を当たり、初稿を起こすところまでは自分の手で書く。そこから先のファクトチェックや校正は、ワークフロー化して AI に手伝ってもらっている。コミットハッシュや関数名がコードと食い違っていないか、日付の前後関係が正しいか——その手の照合は機械の得意分野だ。
第1回は、すべての始まり。最初のコミットが置かれた日を追う。
ゼロコミットの景色
リポジトリの最初のコミットは、2025年7月11日の朝10時18分。中身は pyproject.toml がたった一つあるだけだった。src/podcast_tool を uv のパッケージとして登録する、それだけのコミットだ。
| |
ツールでもなんでもない。まだ「箱」を用意しただけ。だがこの日の午後、この箱は一気に中身で満たされることになる。
26分の怒涛
午後4時25分、手が動き出す。ここからのコミットログを時刻つきで並べてみる。
| |
26分だ。CLIの骨格を組み、設定ファイルを読み込めるようにし、VOICEVOX で音声を合成し、それを混ぜて m4a で書き出す——「ツールとして動く」最小形が、この短い時間で立ち上がっている。16時46分のコミット、音声ミキシングとffmpegによるm4a出力機能を追加 が、事実上の「音が出た瞬間」だった。
勢いがそのままログに残っているのが面白い。1分刻みでコミットが積まれ、設計を頭の中で組み上げながら手を動かしていた様子がうかがえる。
typer をやめて argparse にした話
この日いちばん「生々しい」のは、16:29 から16:51 にかけての出来事だ。
最初はCLIフレームワークに typer を使おうとしていた(16:29)。ところが直後の16:30、uv run で実行したときに引数解析がうまくいかない問題にぶつかる。コミットメッセージにはっきり「調査と回避策の確認」と書いてある。そして16:51、最終的に標準ライブラリの argparse へ乗り換えた。
華やかなライブラリより、uv run という実行環境と素直に噛み合うほうを選ぶ。30分足らずで下したこの判断は、その後のツール全体の地に足のついた作りを象徴しているように思う。
最初から「データ駆動」だった
誕生時点(16:33 のコミット)のファイル構成を見ると、設計の骨格がもう見える。
| |
入力は 4つの設定ファイル、出力は一本の音声。ロジックではなくデータで振る舞いを決める、という発想が初日から入っていた。台本・話者・読み替え・BGM をそれぞれ独立したファイルに分けたこの形は、このあと長く土台であり続ける。
295行、ぜんぶ入りの main.py
中身の main.py は、この時点で 単一ファイル・295行。関数を並べてみるとこうなっている。
| 関数 | 役割 |
|---|---|
load_speakers() / load_replace_dict() / load_script() / load_bgm_config() | 4つの設定ファイルの読み込み |
generate_voice(text, speaker_id, replace_dict) | 1セリフ → 音声(VOICEVOX へHTTP) |
mix_audio(voice_paths, bgm_config, output_file) | 音声群とBGMをミックス |
main(...) | 並列で合成 → ミックス → ffmpeg で m4a 化 |
依存ライブラリは httpx(エンジンへのHTTP)、concurrent.futures(並列合成)、pedalboard と numpy(音響処理)、そして subprocess 経由の ffmpeg。注目したいのは、初日から ThreadPoolExecutor で音声合成を並列化していることだ。セリフが増えても待たされにくいように、という速度への意識が出発点からあった。
いわゆる「全部入りの一枚岩」。今読むと分割したくなる作りだが、まずは動くものを一本のファイルで通しきる、というのは立ち上げ期の正しい姿だと思う。この295行が、のちの大規模なリファクタリング(core / models / audio / video への分割)の出発点になる——その話はずっと先の回で。
この日、できたこと・できなかったこと
誕生時点の README は、自分のツールをこう紹介している。
台本、話者定義、BGM設定などから、VOICEVOXを利用してポッドキャストを自動生成するPythonツール
前提は Python 3.12 以上、uv、ローカルで動く VOICEVOX Engine。出力は当初「1つのWAVファイル」とあり、m4a 対応はこの直後に入ったのが分かる。
できたこと: 台本から音声を合成し、BGMと混ぜ、ファイルに書き出す。一連の流れがこの日のうちに通った。
できなかったこと: まだ単一エンジン専用で、キャッシュもなく、当然ながら動画も字幕もない。複数エンジン対応も、超解像も、YouTube Shorts も、AIによる抑揚付けも——いま README に並ぶ機能たちは、すべてこの先の積み重ねの結果だ。
次回予告
第2回は、誕生の翌日から続く「音を整える」期間を追う。話者ごとの速度調整、BGMのクロスフェードとループ、セクションの切れ目に入れる無音、動的な音量調整……「とりあえず音が出る」から「聴けるものにする」へ。地味だが効く改良が並んだ数日間だ。
この記事は podcast-tool のコミット履歴を一次資料として書いています。引用したコミットハッシュ・時刻・コード構成は当時のリポジトリ状態に基づきます。