Synctoon — スクリプト1枚から2Dアニメ動画を生成するAIパイプライン
Geminiが感情・動作・背景を演出し、Gentleがリップシンクを合わせ、Pillowがフレームを合成する
ナレーション音声とスクリプトテキストファイルの2つを投げると、2Dトーキングヘッドアニメ動画が出来上がる。キャラクターがセリフに合わせて口を動かし、感情に応じて表情が変わり、シーンに合った背景が敷かれる。
Synctoonはこれを全部自動で処理する。
3段階パイプライン
全体の流れはシンプルだ。分析 → 合成 → 動画化。
Stage 1 — AI分析 + 音声アライメント(core.py)
Gentle(Dockerベースの強制アライナー)に音声とテキストを渡すと、単語ごとの開始/終了タイムスタンプが返る。「Hello」が0.5秒〜0.8秒区間だ、という具合に。
次にGemini 2.0 Flashにテキストを8回送る。それぞれ異なる演出要素を分析する:
頭の方向(左/右/正面)、目の方向(左/右/正面、90%正面バイアス)、キャラクター割り当て(誰が話しているか)、感情(14種 — happy, sad, angry, shock, evil_laughなど)、ボディポーズ(47種 — dancing, kung_fu, meditationなど)、強度(通常/強調)、ズームレベル(0/1/2)、背景(31種 — office, forest, bedroom, parkなど)
AI応答はmarshmallowスキーマで検証する。検証失敗時はエラーメッセージをプロンプトに付加してリトライ。最大3回。自己修正ループ。
次にg2p_enが各英単語を音素(phoneme)に変換する。「Hello」→ HH, AH, L, OW。単語の時間区間を音素数で均等配分し、フレームごとの口の形を決定する。
最後に24fps基準のフレーム別CSVを生成。各フレームにキャラクター、感情、ボディポーズ、頭方向、目方向、背景、口の形、ズーム、まばたき情報が入る。80フレームごとに3フレームのまばたきシーケンスも自動挿入。
Stage 2 — フレーム合成(frame_generator.py)
CSVを1行ずつ読み、5レイヤーを合成する。背景 → ボディ → 頭 → 目 → 口。Pillow(PIL)でPNG画像を重ね合わせる。
metadata.jsonに各レイヤーの位置とサイズがピクセル単位で定義されている。頭の上に目が(x, y)座標に来て、口が(x, y)座標に来る。
賢いのは重複フレームキャッシングだ。キャラクター+感情+ポーズ+頭+目+口+背景+ズームの組み合わせが同じなら、前回合成したPNGを再利用する。口だけ変わらない区間が連続すれば、1フレームで数十フレームをカバーする。
Stage 3 — 動画コンパイル(frame_to_video.py)
OpenCVのVideoWriterがPNGシーケンスを24fps MP4にまとめる。FFmpegで元音声を合わせて最終動画完成。
音素 → 口の形マッピング
mouth_image.jsonが各音素を特定の口画像にマッピングする。音素「AH」→ m_a_e_ah_h(嬉しい表情用)、m_a_e_ah_s(悲しい表情用)。感情状態によって同じ発音でも異なる口アセットを使う。
45音素が17口形状(viseme)にグルーピングされる。1つの口の形が複数の類似音素をカバーする構造。
アセットシステム
キャラクターアセットは階層的だ:
images/characters/character_1/の下にbody/、head/、eyes/、mouth/、background/フォルダがある。
ボディポーズ47種。感情14種。背景31種。2Dパペットアニメとしては組み合わせ数がかなり多い。各フォルダにバリアント画像が複数あり、同じポーズでも毎回異なる画像がランダム選択される。
現時点の限界
プロトタイプ段階なのがコードの随所に見える。ファイルパスがハードコードされており(/home/oye/Downloads/...)、APIキーがソースに露出している。キャラクターも現在1体のみ有効(マルチキャラ構造は整っているが)。
Gentle Dockerコンテナが起動していなければならず、Gemini API呼び出し間に6秒のsleepが入る(レートリミット回避)。8回呼ぶのでAI分析段階だけで最低48秒。
Web UIはない。全てCLI。
それでもアプローチ自体は面白い。LLMを「アニメーション・ディレクター」として使うパターン。テキストを読んで感情、ポーズ、カメラ、背景を決めるのは人間がやっていた仕事だが、それをプロンプト8本で自動化した。
パイプラインコード探索
各カードをクリックすると該当部分の実際のソースコードが展開されます
Dockerコンテナ(port 49153)に音声+テキストをPOSTし、単語ごとのタイムスタンプJSONを取得。
6秒間隔でGeminiにテキストを送り、頭方向・目方向・キャラクター・感情・ポーズ・強度・ズーム・背景を分析。
AI応答をmarshmallowスキーマで検証、失敗時はエラーをプロンプトに付加してリトライ(最大3回)。
"Hello" → HH, AH, L, OW。時間区間を音素数で均等配分、フレーム別口の形を決定。
metadata.jsonのピクセル座標で目と口を頭に、頭をボディに、キャラクターを背景上に合成。
全パラメータを文字列キーに。同一組み合わせならキャッシュPNGを再利用。
アセットレイヤー合成順序
実践ステップ
スクリプト(.txt)とナレーション音声(.mp3)を準備
Gentle Dockerコンテナ起動 → 音声テキスト強制アライメント(単語別タイムスタンプ)
Gemini API 8回呼び出し → 感情・ポーズ・背景・カメラなど演出指示を自動生成
g2p_enで単語→音素変換、フレーム別口の形CSV生成
Pillowで5レイヤー(背景→ボディ→頭→目→口)合成、重複フレームキャッシング
OpenCVでPNG→24fps MP4コンパイル、FFmpegで音声合成
メリット
- ✓ 完全オープンソース — コード・アセット全公開、カスタマイズ自由
- ✓ LLMベース自動演出 — 感情14種、ポーズ47種、背景31種の組み合わせをAIが決定
- ✓ 音素単位リップシンク — Gentle強制アライメント + g2pで口の動きが自然に合う
- ✓ フレーム重複キャッシングで合成時間を大幅に削減
デメリット
- ✗ 初期プロトタイプ — ハードコードパス、APIキー露出などプロダクション水準ではない
- ✗ 英語専用 — g2p_enが英語音素のみ対応、韓国語/日本語不可
- ✗ Gemini API 8回呼び出し+6秒間隔 → AI分析だけで最低48秒
- ✗ キャラクターアセット1体のみ有効 — マルチキャラ構造はあるがアセット不足