Communitygithub.com

igm61799-cloud/voiceover-to-animation

音声ナレーションから、完全にコードで描かれた2Dアニメーション動画(MP4)を生成します。動画生成モデルやストック映像は使わず、すべてCanvas 2Dでフレームを描画し、ナレーションの各単語のタイミングに合わせてカット編集し、ヘッドレスChromiumとffmpegでレンダリングします。

voiceover-to-animation とは?

このスキルは、音声ファイル(voiceover.mp3、ナレーション、ポッドキャストクリップなど)を、コードのみで生成された完全な2Dアニメーション動画に変換するためのツールです。動画生成AIやストック映像に依存せず、すべてのフレームをCanvas 2Dを使用してプログラムで描画します。主な特徴は、各シーンが時間の純粋関数として定義されるため、フレームの並列レンダリングやフレームごとのレビューが可能で、音声との完全な同期を実現します。ワークフローは、まず音声を文字起こしして単語ごとのタイミングを取得し、テンプレートプロジェクトをセットアップ、ストーリーボードを作成してからシーンをコーディングします。各シーンはコードで記述され、ヘッドレスChromiumでフレームをレンダリングし、ffmpegでMP4に出力します。この手法は、Kurzgesagtスタイルの動画、解説動画、モーショングラフィックス、Bロールなど、ゼロからアニメーションを作成したい場合に特に有効で、一回限りのアドホックなHTML/CSSアニメーションよりも、音声と同期した実際の動画ファイルが必要な場合に推奨されます。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/igm61799-cloud/voiceover-to-animation/tree/HEAD

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

ナレーション → コードで描かれた2Dアニメーション作品(Short Film)

あなたはスライドショーではなく、ショートフィルムを作っています。ナレーションは固定されています。あなたの仕事は、見ている人がその瞬間に「見る」ものを考え出し、それをコードで描き、カットと視覚的なビートが正しい単語に合ったMP4を提供することです。

パイプライン全体は決定論的です。各シーンは時間の純粋関数であるため、どのフレームも単独でレンダリングできます。この性質により、インスタントなスチルレビュー、並列レンダリング、完全な音声同期が可能になります。

マシンに必要なもの

開始前にこれらを確認してください。これらが唯一の可動部分です。

python -c "import faster_whisper, playwright, PIL; print('ok')"
ffmpeg -version

不足している場合: pip install faster-whisper playwright pillow、その後 python -m playwright install chromium。ffmpegはPATHに含まれている必要があります。何かがインストールできない場合は、ユーザーに不足しているものを伝え、より弱い方法に切り替えないでください。

ワークフロー

ユーザーの視点をシンプルに保ちましょう。ユーザーは音声を提供し、動画を望んでいます。以下の手順を順に実行し、可視化されたタスクリストを維持してください。

1. まずは聞く:単語のタイミング

python <skill>/scripts/transcribe.py voiceover.mp3 work

これにより、文とその開始/終了時刻、およびすべての単語が word@start 形式で出力され、work/words.json に保存されます。以降のすべての処理は、これらの数値に基づいて計画されます。合計時間をメモしてください。

2. 音声の隣にプロジェクトをセットアップ

<skill>/assets/template/ を作業フォルダにコピーし、以下のようになるようにします。

voiceover.mp3
render.py
src/index.html  src/engine.js  src/scenes.js  src/main.js

src/engine.js を一度読んで(短いです)、references/engine-api.md をざっと見て、すでに何があるかを把握してください:イージング、シード付き乱数、カメラ、部分描画パス、そして版画風のモチーフ(雲、雨、稲妻、煙、スカイライン)などです。

3. アートの方向性を決め、それから単語に合わせてストーリーボードを作成

1つのビジュアル言語を選び、フィルム全体でそれを守りましょう:小さなパレット(5〜6色)、エッジとテクスチャの扱い方、人物の描き方。テンプレートには、リノカット/版画風のルック(骨のような紙、ネイビーブルーのインク、バーミリオン、ゴールド、手で切ったような揺れるエッジ)が同梱されています。それを使うか、scenes.js のパレットと POST スイッチを変更してスタイルを変更しても構いませんが、シーン間でスタイルを混在させないでください。バラエティは、各シーンでどの色が支配的か(夜のシーン、紙のシーン、赤のシーン)によって生まれ、ルールを変えることではありません。

次に、コードを書く前にストーリーボードを作成します:各ショットの絶対的な開始/終了時間、各ショットがカバーする単語、画面に表示されるもの、各ビートが着地する正確な単語のテーブルです。references/storyboarding.md を読んで、ナレーションを画像に変換する方法を学んでください。これがフィルムの良し悪しを決める部分です。

簡略版:

  • フレーズごとに1つのアイデア。3つの節を含む文は、通常、3つのショット、または1つのショット内の3つのビートになります。
  • 物事を表示し、キャプションを付けないでください。ナレーションを繰り返す画面上のテキストは、視聴者に見る代わりに読ませてしまいます。デフォルトではテキストは一切表示せず、名前、実際のデータ、チャート上の短い中立的なラベルのみ例外とします。ユーザーがテキストの好みを明示している場合は、それが優先されます。
  • フィルムに背骨を与えましょう:繰り返し登場するキャラクターやモチーフ、そして冒頭の何かを最後にコールバックする要素です。
  • 意図的にスケールとカメラを変えましょう:ワイド、クローズアップ、図解、地図、断面図。連続する2つのショットが同じ構図であってはなりません。
  • ナレーションがオチで終わる場合は、最後の単語の後に1〜2秒の無音のテールを追加して、最終的な画像が着地できるようにしてください。render.py が音声をパディングします。

4. 残りを書く前に、1つのシーンを完全に作り上げる

最初のショットを src/scenes.js に書き、SHOTS に登録して、確認します:

python render.py stills 0.3,1.5,2.9 first

次に、Readツールで work/first.png を開きます。これを他のシーンをコーディングする前に行ってください。これによりパイプラインが機能することが証明され、さらに重要なことに、スタイルをまだ簡単に変更できるうちに評価できます。

5. 残りのシーンを、少しずつ書く

システムを機能させ続けるルール(それぞれが、破ると特定の障害を引き起こすために存在します):

  • 時間の純粋関数。 ショットは function name(g, t, u) であり、t は絶対秒数、u はショットが始まってからの秒数です。フレーム間で蓄積される変数、Math.random()、Date は使用しないでください。hash(n)、noise1(x)、rng(seed) を使用してください。フレームは複数のブラウザによって順不同でレンダリングされます。状態を持つものはすべて、ちらつきや一致しないチャンクとして現れます。
  • 絶対時間でアニメーションする。 E.out(seg(t, 12.62, 13.1)) のように、単語リストの数値をそのまま使って記述します。編集が変更されても、ビートはその単語に留まります。
  • すべてが少し動く。 静止したフレームはバグのように見えます。すべてのショットに遅いカメラドリフト(1 + u * .01 のズームで十分)と、いくつかの二次的な動き(雨、煙、揺れる物体)を与えてください。
  • すべてにイージングをかける。 到着するものには E.out、カメラ移動には E.inOut、ポップアップには E.back、着地には E.bounce。線形の動きは機械的に見えます。
  • 世界をフレームより大きく描く カメラがズームアウトしたり、揺れたりするときは、背景と地面を0..1920よりはるかに超えて塗りつぶし、どの端も露出しないようにします。
  • 衝撃には3つの要素:2〜3フレームの全画面 flash、減衰する shake、そして反応する何か(パフ、反動、色の反転)。
  • シーンコードをファイルに分割する(s1.js、s2.js、...)、ファイルが数百行を超えた場合、そしてそれらを scenes.js の前に index.html に追加します。

2、3のショットを書き、それらのコンタクトシートをレンダリングし、見て、修正し、続けます。フィルム全体を目隠しで書かないでください。

6. スチルでレビューする - これがあなたの目です

あなたはビデオを見ることができないため、コンタクトシートがレビュー手段です。すべてのショットについて、少なくとも開始、メインアクションの途中、終了、そして各ビートの正確なフレームをサンプリングします:

python render.py stills 12.2,13.3,14.3,15.4,16.5 act2     # シートあたり最大約16のタイムスタンプ
python render.py sheet 4                                   # フィルム全体、4秒ごとに1フレーム

PNGを読み、ショットごとに確認します:被写体は一目でわかる十分な大きさですか?何かがクリップされたり、重なったり、浮いたりしていませんか?重要なものは背後にあるものとコントラストがありますか?デザインされたように見えますか、それともフレームの半分が空ですか?修正し、同じタイムスタンプを再レンダリングします。references/pitfalls.md には、犯しやすく、気づきにくいミスのリストがあります。

ページエラーは render.py によって PAGE ERROR: 行として出力されます。黒または半分しか描画されていないフレームは、通常、そのショットでの例外を示しています。

7. レンダリングと検証

python render.py full --workers 8

バックグラウンドで実行します。70秒のフィルムは約2,100フレームで、数分かかります。output.mp4(1920x1080、H.264 + AAC、音声は全長にパディング)が書き出されます。次に、スチルだけでなくファイル自体を検証します:

ffprobe -v error -show_entries format=duration:stream=codec_name,width,height -of default=nw=1 output.mp4
python render.py sheet 6

再生時間、オーディオストリームがあること、概要シートに空のフレームがないことを確認します。レポートでは、スチルとファイルプロパティを確認したが、動いている状態では見ていないことを明確に述べてください。ユーザーは一度自分で視聴する必要があります。

8. 引き渡し

ユーザーに output.mp4 のパス、概要シート、および編集後に再レンダリングする方法についての1行を提供します。src/index.html をブラウザで開くと、音声付きのライブプレーヤーが表示され、これがユーザーにとってタイミングを確認する最も速い方法です。

サウンド

ユーザーが音楽や効果音を要求しない限り、ナレーションのみを提供します。要求された場合は、最終的なmuxで追加のffmpeg入力として追加し、ビジュアルに使用したのと同じビートリストからタイミングを取ります。

参考ファイル

  • references/storyboarding.md - ナレーションをショットに変換する方法;ビートの配置;視覚的なメタファー;シーンをデザインされたものに見せる要素。ステップ3の前に読んでください。
  • references/engine-api.md - engine.js 内のすべてのヘルパーとその1行での使い方。ステップ5の前に読んでください。
  • references/pitfalls.md - 苦労して見つかった失敗モードとその修正方法。ステップ6の前に読んでください。
  • references/example-bells/ - 完全な71秒のフィルム(16ショット、約900行のシーンコード)とそのストーリーボード。storyboard.md を読んで期待される計画レベルを確認し、特定のエフェクト(登るキャラクターを追うカメラ、爆発、地図、線を描く羽ペン、細くなる目)がどのように構築されたかを知りたいときに src/s*.js ファイルを開いてください。

関連スキル