ボイスオーバーとTTS
ボイスオーバーがタイムラインを動かします。ビジュアルを作成する前に生成します。
エンジンを選ぶ
| エンジン | 配置 | 強み | 注意点 |
|---|---|---|---|
| ElevenLabs | クラウドAPI | 最も自然な音質、音声クローン | 有料、同意のある声のみクローン可 |
| OpenAI TTS | クラウドAPI | シンプル、安定 | 音声の選択肢が限られる |
| Kokoro | ローカル | 無料、高品質、オフライン | Pythonのセットアップが必要 |
| Piper | ローカル | 高速、CPUで動作 | 表現力がやや劣る |
| 録音音声 | マイク | 最も本物らしい | クリーンアップが必要 |
APIキーは環境変数または.envファイル(gitignore対象)に保存します。絶対に表示やコミットをしないでください。
スクリプトを音声用に準備する
- 数字、単位、略語は発音される形で書く:「twenty twenty-six」、「A P I」(綴る場合)、「gigabytes」。
- 呼吸のためにカンマを入れ、停止にはピリオドを使う。括弧は避ける。
- 難しい名前は発音リストにまとめ、短いサンプルで先にテストする。
- シーンごとに1ファイルに分割する(
audio/scene-01.mp3)。シーンごとのファイルなら、1行だけ再録音でき、タイミングの柔軟性が保てる。
生成(例:ElevenLabs REST)
import os, requests, pathlib
def tts(text: str, voice_id: str, out: pathlib.Path) -> None:
r = requests.post(
f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}",
headers={"xi-api-key": os.environ["ELEVENLABS_API_KEY"], "Accept": "audio/mpeg"},
json={"text": text, "model_id": "eleven_multilingual_v2",
"voice_settings": {"stability": 0.5, "similarity_boost": 0.75}},
timeout=120,
)
r.raise_for_status()
out.write_bytes(r.content)
モデル名やエンドポイントは変更されることがあります。使用前にプロバイダの最新ドキュメントで確認してください。
計測とペーシング
ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 audio/scene-01.mp3
- 目標は1分あたり140~160語(語数 / 秒数 * 60)。シーンが180語/分を超える場合は、少し異なる読み上げで再生成するか、テキストをトリミングする。早口に聞こえます。
- 音声を約10%以上タイムストレッチしないでください。合成臭くなります。代わりに書き直しましょう。
録音音声のクリーンアップ
# ハイパス、軽いノイズリダクション、コンプレッション、ラウドネス
ffmpeg -i raw.wav -af "highpass=f=80,afftdn=nf=-25,acompressor=threshold=-18dB:ratio=3:attack=5:release=80,loudnorm=I=-16:TP=-1.5:LRA=11" -ar 48000 clean.wav
先頭と末尾の無音をトリミング:
ffmpeg -i in.wav -af "silenceremove=start_periods=1:start_threshold=-45dB,areverse,silenceremove=start_periods=1:start_threshold=-45dB,areverse" out.wav
結合
# 0.4秒のギャップを入れてシーンファイルを結合
python - <<'PY'
import subprocess, pathlib
files = sorted(pathlib.Path("audio").glob("scene-*.mp3"))
parts = []
for i, f in enumerate(files):
parts += ["-i", str(f)]
n = len(files)
filt = "".join(f"[{i}:a]apad=pad_dur=0.4[a{i}];" for i in range(n)) + "".join(f"[a{i}]" for i in range(n)) + f"concat=n={n}:v=0:a=1[out]"
subprocess.run(["ffmpeg", "-y", *parts, "-filter_complex", filt, "-map", "[out]", "voiceover.mp3"], check=True)
PY
その後、audio-mixingで音楽とミックスし、captions-subtitlesでキャプションを付けます。
倫理と権利
- 話者の明示的な許可がある場合のみ音声をクローンしてください。
- プラットフォームのルールや法律で要求される場合は、合成音声であることを開示してください。
- TTSプロバイダのライセンスで商用利用が許可されているか確認してください。