背景
最近『夜は短し歩けよ乙女』という小説を読んだ時に、この感想をミルクボーイ風に紹介するというコンセプトを思いついた。
ChatGPTにプロットを作ってもらい、VOICEVOXで音声を作り、Canvaで背景画像を作り、ffmpegでそれらを合成して動画にするという作業を試してみた。
その結果としてできた動画:
プロットに凝らなければ、ほとんどの作業は30分程度で終わる簡単なものだったが、字幕を付けようと思うと非常に面倒な作業が必要だということに気が付いた。
字幕を付ける作業
まずVOICEVOXは、入力されたテキストをCSV形式で出力することができる。
しかしVOICEVOXは漢字の読み方などを誤ることがあるため、時おり入力をひらがなに直したりしているので、そのまま字幕には使えない。
また、テキストデータを字幕として表示するには「何分何秒の時点から何分何秒の時点まで表示する」という情報を入力しなければならない。
実際に動画を見ながら手で秒数を記録して打ち込んでいく作業は控えめに言っても地獄だった。
実装方針
VOICEVOXは発話ごとに音声データを出力することができるため、その音声データの長さと、発話と発話の間の間隔をもとに、字幕を表示すべき時間を自動で決定できるのではないかと考えた。
また、ffmpegを利用すれば動画に字幕を焼き付ける(映像の中で字幕としてテキストを表示する)ことができるということも分かったので、まずは 「背景画像」「発話ごとに分割された音声データ」「字幕データ」をもとに、字幕を焼き付けられた動画ファイルを生成する ことができるスクリプトを実装することにした。
成果物
そうしてできたのがこのスクリプトである。
takano-hi/video-creator
使い方としては、作りたい動画ごとにディレクトリを用意し、そこに下記の材料を投入する。
- 背景画像 (cover.png)
- 字幕データ (subtitle.csv)
- 音声データ (*.wav)
その状態で Ruby スクリプトにディレクトリ名を渡して実行すれば、10秒前後で字幕テキストが焼き付けられた動画ファイルを生成してくれる。
ずんだもんボイスと四国めたんボイスに関しては、字幕テキストを専用の色に変更してくれるおまけつきだ。
最終的には下記のような動画に仕上がった。
処理の流れ
音声データの結合
VOICEVOXから出力した、発話ごとに分割された音声ファイルを、発話間隔(0.2秒)を空けながら結合する。
ffmpegのコマンドは下記のような形になる。
$ ffmpeg -i audio1.wav -i audio2.wav -i audio3.wav -filter_complex "anullsrc=r=44100:cl=stereo:d=0.2,asplit=3[s0][s1][s2]; [0:a][s0][1:a][s1][1:a][s1]:v=0:a=1[out]" -map "[out] merged.mp3"
-
anullsrc=r=44100:cl=stereo:d=0.2,asplit=3[s0][s1][s2]のところで無音の0.2秒のデータを3つ作り、それぞれに [s0], [s1], [s2] という名前を付けている -
[0:a][s0][1:a][s1][2:a][s2]:v=0:a=1[out]のところで「1つ目の入力ソース」「1つ目の無音データ」「2つ目の入力ソース」「2つ目の無音データ」「3つ目の入力ソース」「3つ目の無音データ」の順に結合し、それにoutという名前を付けている -
-map "[out]"で、outと名付けたデータを最終的な出力として受け取り、merged.mp3というファイルに書き込んでいる
という具合らしい。細かいところは分からない。
字幕データの生成
世の中には字幕情報を表現するファイルフォーマットがあるらしい。
- SRTファイル: シンプルに時刻とテキストの情報を列挙する形式
- ASSファイル: 字幕1行ごとにスタイルや表示位置を指定できるなど、メタデータ込みでデータを記述する形式
今回は発言者ごとに字幕の色を変えたかったりしたのでASSファイルを採用した。
SRTファイルはそのままYouTubeにアップロードして動画のメタデータを設定できるようなので、そのうちSRTファイルも同時に生成するよう機能追加したい。
ASSファイルは下記のような形式になっている。
[Script Info]
; Script generated by FFmpeg/Lavc61.19.101
ScriptType: v4.00+
PlayResX: 384
PlayResY: 288
ScaledBorderAndShadow: yes
YCbCr Matrix: None
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,12,&Hffffff,&Hffffff,&H0,&H0,0,0,0,0,100,100,0,0,1,1,0,2,10,10,10,1
Style: Green,Arial,12,&H00E676,&H00E676,&H0,&H0,0,0,0,0,100,100,0,0,1,1,0,2,10,10,10,1
Style: Pink,Arial,12,&HFF80AB,&HFF80AB,&H0,&H0,0,0,0,0,100,100,0,0,1,1,0,2,10,10,10,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.30,0:00:04.04,Pink,,0,0,0,,うちのおかんが最近読んだ本のタイトルを\n忘れたらしいねん。
Dialogue: 0,0:00:04.24,0:00:09.95,Green,,0,0,0,,ほな俺が一緒に考えてあげるから、\nおかんが言うてた特徴を教えてみて。
細かいことは調べてないので分からないが、大きく [Script Info] [Styles] [Events] の3セクションに分かれており、Eventsのところに字幕データの本体が入ってくる。
このASS形式の字幕データがあればffmpegで字幕を焼き付けることができるので、VOICEVOXが出力するCSVファイルをASSファイルに変換する処理を実装した。
ASSファイルさえできてしまえば、あとは下記のようなコマンドで動画に焼き付けることができる。
$ ffmpeg -loop 1 -i cover.png -i merged.mp3 -vf "ass=subtitle.ass" -c:v libx264 -tune stillimage -c:a aac -b:a 192k -shortest -pix_fmt yuv420p video.mp4
-vf "ass=subtitle.ass" というオプションだけで映像に字幕を焼き付けてくれるのでたいへん便利である。
工夫点
VOICEVOXが出力する字幕データ(CSVファイル)は、各セルの値をエスケープしてくれていないので、字幕が長過ぎる場合に改行コードを入れるとCSVファイルの行末の改行コードと区別できなくなる問題があった。
字幕データの中に {br} という文字列を入れることで改行コードへ置き換えるようにした。
今後の展望
どうやらVOICEVOXは、ソフトを立ち上げるとlocalhostのAPI経由で機能を利用できるようになるらしい。もしくはDockerイメージ経由でAPIを立てる方法もあるとか。
そうすると、自分でテキストと画像を入力するフォームを作れば、それをもとにVOICEVOXで音声データと字幕データを生成するところまで自動化できるということになるため、この動画の生産が拡大するようならそこまで機能を拡張したいお気持ち。
最後に
スクリプトのおかげで字幕付きの動画を生成することが簡単になり、代わりにChatGPTと対話しながらプロットをブラッシュアップする時間の余裕が生まれた。
小説を読み、その内容と向き合って面白い掛け合いを生み出す時間にこそ人間の時間とクリエイティビティは使われるべきであり、字幕の時間を記録しながら打ち込むとかいう無駄な作業はできるだけ簡素化していく方が望ましい。
また面白い本に出会えた時はミルクボーイ風のフォーマットで紹介する動画を作りたいし、何なら他にも色んな動画作りにチャレンジしてみたい。