TL;DR
- 漫才台本(JSON)を入れると、話者別TTS → 間/かぶせ調整 → 2人同時リップシンク動画までを一本で生成するパイプラインを作りました。
- ミソは「漫才は full-duplex(二人が間・被せ・天丼で噛み合う)」という点。単独話者の talking-head 生成では足りないので、話者別ステム+2話者ツーショットの音声駆動アバターで“掛け合い”ごと作ります。
- すべて オリジナル台本+合成顔(実在しない2人) で完結させ、Apache-2.0 で公開しました。
- リポジトリ: https://github.com/yutoAb/manzai-studio
(お題「クラウド」から自動生成した漫才を、実在しない架空2人の合成写真で M-1 風ステージに乗せた例。デモ集は GitHub Pages、フル動画はReleases に添付)
なぜ作ったか:漫才は「単独しゃべり」では作れない
リップシンク動画生成(talking-head)はかなり実用域です。が、それらの多くは 1人がカメラに向かって喋る前提。漫才は違います。
- ボケとツッコミが 間(ま) で噛み合う
- ツッコミが 食い気味(かぶせ) で入る
- 天丼や転換で“あえて”間を空ける
つまり二者のタイミング設計こそが面白さの本体です。ここを台本から制御し、かつ2人が同時に映って各自リップシンクする動画にするのが目標でした。
全体構成
台本 JSON ──> ① TTS (ElevenLabs v3) ──> ② 間・かぶせ調整 + 結合 ──> ③ 映像化
1セリフ = 1音声ファイル 完成音声 + 話者別ステム 2話者同時リップシンク
+ タイムライン (音声駆動アバター)
① 台本フォーマット:タイミングを“データ”にする
台本は JSON。ポイントは gap_ms と効果音キューです。
{
"title": "マンジャロ",
"speakers": {
"boke": { "name": "ボケ", "voice_id": "..." },
"tsukkomi": { "name": "ツッコミ", "voice_id": "..." }
},
"lines": [
// gap_ms: 直前のセリフ終わりからの間。負値で「かぶせ」(食い気味ツッコミ)
{ "speaker": "tsukkomi", "text": "どうもー", "gap_ms": 0 },
{ "speaker": "boke", "text": "いきなりやけどな", "gap_ms": 250 },
// <...> は非言語の効果音キュー。TTSせず別途SFXを差し込む
{ "speaker": "tsukkomi", "text": "<叩く>", "gap_ms": -100 }
]
}
テンポの目安(経験則):
- ボケ→ツッコミは 100–250ms
- 強いツッコミは -150ms 前後のかぶせ
- 天丼や転換の前は 600ms 以上空ける
各行・各話者に voice_settings(stability/style)を持たせて上書きでき、後半ほど stability↓ / style↑ にランプさせると、ネタのエスカレーションが少し付きます。
② 音声:かぶせ区間は自動ダッキング
assemble_audio.py が gap_ms を解釈して各セリフを時間軸に配置します。重要なのは 負の gap_ms(かぶせ) の扱い:
- 被せられる側を自動でダッキング(音量を一時的に下げる)して、食い気味でも台詞が潰れないようにする
- 出力は3点:
-
manzai.wav(完成ミックス) -
stem_<speaker>.wav(話者別ステム=リップシンク駆動用) -
timeline.json(各セリフの開始/終了/話者)
-
この話者別ステムが③で効いてきます。
ElevenLabs v3 の注意:
[laughs]のような角括弧タグを読み上げてしまうことがあるので、抑揚は角括弧タグではなくvoice_settings+句読点・「…」で表現しています。
③ 映像:2話者同時リップシンク
主力は LongCat-Video-Avatar-1.5(MultiTalk 後継)の multi-audio モード。
2人ツーショット参照画像 + person1=stem_boke.wav + person2=stem_tsukkomi.wav (audio_type=para)
└─> 話者ごとにリップシンクした掛け合い動画(音声 mux 済み)
- 参照画像は白背景・上半身ツーショットが安定
- 駆動音声は②の
stem_<speaker>.wavを 16kHz mono に変換して person1/person2 に割り当て(左=person1、右=person2 に自動で振られる) - 長尺は
num_segmentsを伸ばす(1セグメント ≈ 先頭3.7s + 以降3.2s/seg)。132秒なら 42 セグメント
「人間っぽさ」と権利をどう両立したか
最初はフラットなイラスト立ち絵で作っていましたが、アニメ的すぎて人間味が出ない。かといって実写映像から人物を切り出すと肖像権・著作権に触れる。
解決策は 「実在しない架空の2人」を画像生成で1枚作り、それを参照画像にすること。合成顔なので誰の肖像でもなく、写実的な掛け合い動画が得られます。台本も完全オリジナルにしたので、まるごと公開可能になりました。
ハマりどころ(共有マシン / Blackwell GPU)
ここが一番の知見かもしれません。
- PyTorch は cu128:Blackwell 世代 GPU は cu128 ビルドの torch が必要
-
ffmpeg はシステムのものが古い:
imageio-ffmpeg同梱バイナリ(7.0.2)を使うと安定 -
bf16 + 単一プロセス:共有マシン(RAM 共有)では int8 量子化ロードの一時ピーク(〜75GB)で OOM。
--nproc_per_node=1の bf16 ロードに切り替えて回避 -
720p で VRAM 断片化 OOM:長尺でセグメントをまたぐと KV キャッシュが蓄積し、断片化で確保失敗。
offload_kv_cache=True(CPUオフロード)+PYTORCH_CUDA_ALLOC_CONF=expandable_segments:Trueで VRAM が頭打ちになり完走
到達点と限界
到達点:
- オリジナルネタ ~100秒、実在ネタでの私的検証で ~285秒(全ネタ通し)を一本完走
- ターン型・重なり型いずれも成立
限界(モデル側の天井):
- 長時間生成での顔の identity ドリフト
- 左下のウォーターマーク幻影
-
極端な同時発話下でのリップシンク不安定
- 検証として、話者別の bbox binding を A/B したが、クリーンな左右対称ツーショットでは自動の左右割りで十分で追加効果はなかった
残る課題:掛け合いの“間”そのものをどう生成するか
今は台本のタイミングを人手で設計しています。本質的に難しいのは、ターンテイキングや被りの自然さをどう作るか——これは talking-head ではなく「対話そのものの生成」の問題で、まだ open な領域だと感じています。
まとめ
- 漫才は full-duplex。タイミングをデータ化し、話者別ステム+2話者同時リップシンクで“掛け合い”ごと生成できた
- 合成顔+オリジナル台本でまるごと公開可能な成果物にした
- コードは Apache-2.0:https://github.com/yutoAb/manzai-studio
外部モデル/サービス(ElevenLabs / LongCat-Video-Avatar / Whisper 等)は各規約に従ってください。実写映像・実在人物の声まね・既存ネタは私的利用の範囲に留めるのが安全です。
