短い台詞ほど、文字数だけでは収録尺を守れなかった
TL;DR
- 短尺動画で数秒の発話枠が決まっている場合、LLMに定型台詞まで書き直させると、再生成しても尺に収まらないことがある。
- 言い回しを固定できる区間は構造化した入力から組み立て、説明が必要な区間だけをLLMに生成させる。
- 固定した台詞もTTSで実際に合成し、WAVの長さを発話枠と照合する。収まらなければ入力を短くしてから進める。
- 文字数は事前の目安として使えるが、句読点による間や話速を含む最終判定には音声の長さが必要だった。
概要
私は株式会社オートクチュールで実装とディレクションを兼ねている。個人開発で短尺動画の台本と音声を組み立てた際、数秒だけ表示する二択の台詞が、LLMの再生成を重ねても発話枠に入らなかった。これは当時の試作で起きた事例であり、LLM全般の失敗率を測ったものではない。
この記事は、LLMとTTSを組み合わせて時間制約のある音声コンテンツを作る人向けに、どの台詞を固定し、どこで合成音声の長さを測るかを整理する。実装と既存テストで確認できる設計を紹介し、実音声の再測定結果は載せない。
目次
- 再生成を繰り返しても、短い発話枠には入らなかった
- 定型部分を固定し、残りの生成には確定文を渡す
- 文字数の検査を通っても、TTS合成後の秒数は変わる
- 音声を合成してから尺を判定する
- テストで確かめた範囲と、まだ測れていない範囲
- この設計を使う条件
- まとめ
- 参考
再生成を繰り返しても、短い発話枠には入らなかった
短尺動画では、冒頭の問い、二択、説明、締めのように、画面と音声を時間帯ごとに分けていた。ここでは一つの時間帯を「区間」と呼ぶ。二択の区間では、選択肢を読み上げた後、視聴者が選ぶための短い間も必要になる。
最初は各区間の台詞をLLMに生成させ、長ければ再生成していた。しかし、同じ意味を保って書き直しても、語尾、接続詞、句読点の入り方が変わる。短い区間では、その差が発話枠の超過につながった。試作の失敗は、台本全体の長さよりも、言い回しの自由度が要らない区間に自由生成を使っていたことを見直すきっかけになった。
判断を分けたのは、台詞の役割だ。
| 区間の役割 | 台詞の決め方 | 理由 |
|---|---|---|
| 冒頭の問い、二択の提示 | 入力フィールドから固定文を組み立てる | 表現の幅より、提示順と尺の安定が重要 |
| 理由や補足の説明 | LLMに生成させる | 題材に合わせた説明が必要 |
| 全区間が固定できる形式 | LLMを呼ばない | 生成対象がない |
定型部分を固定し、残りの生成には確定文を渡す
定型の二択は、題材ごとのラベルを構造化した入力として受け取り、一定の形にする。次は仕組みを示すために単純化した例で、ラベルは架空のものだ。
def choice_line(label_a: str, label_b: str) -> str:
if not label_a or not label_b:
raise ValueError("選択肢のラベルが必要です")
return f"Aは{label_a}。Bは{label_b}。"
print(choice_line("読書", "散歩"))
# Aは読書。Bは散歩。
元の案では選択肢の記号の後に読点を入れていた。当時の一例では、その案が短い発話枠を超え、助詞を使う形に替えると収まった。文章として意味を通しながら、合成音声で間が入る箇所を減らす判断だった。エンジンの版などをそろえた公開比較ではないため、他の台詞でも同じ結果になるとは言えない。
固定文を作るだけでは、後続のLLM生成が冒頭と重複する可能性がある。そこで、LLMには生成対象の区間だけを要求し、確定済みの台詞は文脈として渡す。次は処理の骨格を示す疑似コードだ。
fixed = {"choice": choice_line("読書", "散歩")}
free_beats = beatsのうち script が template でない区間
free_beats があれば:
generated = LLMに free_beats のみ依頼し、fixed を文脈として渡す
なければ:
generated = {} # LLMを呼ばない
script = fixed と generated を統合
実装では、固定区間を再生成対象に指定した場合もエラーにする。長すぎる固定文をLLMに言い換えさせると、固定した理由が失われるためだ。その場合はラベルや冒頭の入力を短くする。
文字数の検査を通っても、TTS合成後の秒数は変わる
区間の発話可能時間は、区間の幅から必要な無音の間を引いて考える。たとえば説明用の仮の設定として、3.0秒の区間で最後に0.5秒の間を取りたければ、発話に使えるのは2.5秒だ。
発話可能時間 = 区間の終了時刻 − 開始時刻 − 発話後の間
文字数から発話時間を見積もる検査は、明らかに長い文を早く見つけるのに役立つ。しかし、同じ文字数でも句読点、読み、話者、話速で実際の長さは変わる。定型文にした後も、文字数の検査だけで合格にしない。
VOICEVOX ENGINEの公式READMEには、/audio_queryで合成用のクエリを作り、/synthesisに渡してWAVを得る手順がある。speedScaleもクエリで調整できる。音声を調整するサンプル
エンジンを起動済みなら、架空の選択肢を次のように合成できる。speaker=1は公式READMEの例に合わせた値で、利用する話者のIDは/speakersで確認する。これは読者が自分の環境で測るための例で、執筆時の実行結果ではない。
curl -fsS -X POST --get \
--data-urlencode 'text=Aは読書。Bは散歩。' \
--data-urlencode 'speaker=1' \
'http://127.0.0.1:50021/audio_query' > query.json
curl -fsS -X POST -H 'Content-Type: application/json' \
--data-binary @query.json \
'http://127.0.0.1:50021/synthesis?speaker=1' > voice.wav
この例は既定の話速を使う。話速を変えて測るときは、query.jsonのspeedScaleを設定してから/synthesisへ渡し、比較する文で同じ値を使う。合成した音声を公開する場合は、VOICEVOXのソフトウェア利用規約と選んだ音声ライブラリの規約、必要なクレジットを確認する。
音声を合成してから尺を判定する
私の実装では、動画生成前の確認段階で固定台詞をTTSに渡し、合成された音声の長さを区間ごとの許容範囲と照合する。許された話速調整で収まるなら進める。収まらない場合は処理を止め、どの入力を短くすべきかを示す。通常の動画生成でも合成後の時間軸を検査する。固定台詞の超過を先に見つけられるのが、事前確認の役割だ。
WAVの長さを読む最小例は次のとおり。WAVファイルを合成した後に実行するコードであり、ここに表示した秒数は実測結果ではない。
import wave
from pathlib import Path
def wav_seconds(path: Path) -> float:
with wave.open(str(path), "rb") as audio:
return audio.getnframes() / audio.getframerate()
beat_start_sec = 1.0
beat_end_sec = 4.0
post_pause_sec = 0.5
speech_budget_sec = (beat_end_sec - beat_start_sec) - post_pause_sec
duration = wav_seconds(Path("voice.wav"))
if duration > speech_budget_sec:
raise ValueError("発話枠に収まりません。元の台詞を短くしてください")
getnframes()とgetframerate()はPython標準ライブラリのWAV読み取りAPIだ。Python wave ドキュメント
実装でキャッシュを使うなら、少なくとも台詞・話者・話速をキーに含める。同じ文でも話者や話速が違えば、以前の音声の長さを再利用できない。長期間使うキャッシュでは、エンジンの版、辞書、合成クエリの変更も失効条件にする。許容範囲や話速の上限は、文章の検査とは別に映像側の制約として持つ。
テストで確かめた範囲と、まだ測れていない範囲
執筆時、台詞の振り分けと失敗時の制御に関する既存テストが成功した。LLMとTTSは模擬しており、音響結果のテストではない。確認できたのは次の振る舞いだ。
- 固定区間はLLMの出力要求から除外し、確定文として後続の生成に渡す。
- すべて固定区間ならLLMを呼ばない。
- 固定区間の再生成要求をエラーにする。
- TTSから返る長さが許容範囲を超え、話速調整でも収まらない場合は処理を止める。
このテストではLLMとTTSを模擬しているため、生成の振り分けと失敗時の制御を確かめた結果だ。今回の執筆環境ではVOICEVOX ENGINEが起動しておらず、上記の架空の二択を実音声で再測定していない。試作時に見つけた句読点の影響も、エンジンの版・話者・話速をそろえた公開ベンチマークではない。音声の秒数は、自分が使う環境で合成して確かめる必要がある。
この設計を使う条件
固定文が効くのは、順序や意味が決まっていて、言い回しの変化より尺の再現性が大切な区間だ。商品説明や物語のように、言葉選び自体が価値になる区間ではLLMの生成を残す。固定文と自由文が混在するなら、確定文を後続生成の文脈に渡して接続を確認する。
実装時は、次の順番で確認できる。
- 区間ごとに、発話可能時間と無音の間を決める。
- 文言を固定できる区間だけ、構造化した入力から台詞を作る。
- 自由生成には確定文を渡し、固定区間を出力させない。
- 固定文を実際の話者・話速で合成し、WAVの秒数を測る。
- 許容する速度調整でも収まらなければ、入力文を短くして再確認する。
まとめ
短い発話枠で再生成を繰り返しても収まらないときは、まずその区間に言い回しの自由度が必要かを見直す。定型なら入力とテンプレートで台詞を確定し、説明が必要な部分にLLMを使う。最後の判定は、文字数の推定ではなく、使う話者と話速で合成した音声の長さに置く。
参考
公式一次資料
- VOICEVOX ENGINE公式:音声合成の手順 — HTTPでの音声合成、話者ID、話速調整の手順。
- VOICEVOX ENGINE公式:APIの仕様 — エンドポイントと合成パラメータの仕様。
- VOICEVOX公式:ソフトウェアと音声の利用条件 — 音声ライブラリの規約とクレジットについての案内。
-
Python
waveドキュメント — WAVのフレーム数とサンプリングレートの取得。