Under the hood(内部の仕組み)
トピック(またはユーザーのコード)を、ナレーション付きアニメーション解説動画に変換します。見た目は温かみのある手描き風です:
- クリーム色のドットグリッド「紙」の背景に、インクのラインアート
- リクエストは青色、レスポンスはオレンジ色、暗号化または成功は緑色
- 上部のステップトラッカーと下部の大きなキャプション
- ボックス間を飛び交うパケットと、「世界」間のカメラパン
参考となる成果物は ${CLAUDE_SKILL_DIR}/assets/example-cookie/ にあります。これはGoogleに「cookie」と入力したときに何が起こるかを示しています。複雑なパターンが必要な場合は、その scenes.js を開いてください。
すべてはコード駆動です。キャンバスページは決定的にフレームをレンダリングし、ナレーションクリップが各シーンの長さを設定し、音楽と効果音は同じタイムラインから合成されます。スクリプトを変更するとすべてが再同期され、60秒の動画は約30秒でレンダリングされます。
リクエストされたトピック: $ARGUMENTS
ファイルの場所
- スクリプト:
${CLAUDE_SKILL_DIR}/scripts/(SCRIPTSと呼びます)。 - ランタイム:
UTH_HOME(デフォルトは~/.local/share/under-the-hood) にNodeモジュール、TTS仮想環境、保存された音声ファイルがあります。doctor.shで正確なパスを表示します(PY(TTSのPython)も含む)。 - プロジェクト: カレントワーキングディレクトリ内の
./explainers/<slug>/に作成します。ユーザーが別の場所を指定した場合はそちらを使用します。ワーキングディレクトリがホームフォルダの場合は、~を散らかさないように保存場所を尋ねてください。
プロジェクトフォルダは次のようになります:
index.html engine.js scenes.js script.json timeline.js
narration/ (音声クリップ: <id>.wav, <id>_take2.wav、プレビューファイルも含む)
audio/ (ステムと mix.wav)
.shots/ (スクリーンショット、実行ごとに新しいフォルダ)
<slug>.mp4
ワークフロー
0. セットアップを確認(準備ができたら1行で)
bash ${CLAUDE_SKILL_DIR}/scripts/doctor.sh # 動画にナレーションがない場合は --no-tts を追加
READY と表示された場合は、PY と VOICES をメモして次に進みます。MISSING 行が表示された場合は、under-the-hood-setup スキルをロードして指示に従い、再度 doctor を実行します。
1. 簡単なヒアリング
$ARGUMENTS が空の場合は、トピックを尋ねます。その後、合理的に判断できないことだけを、1回のメッセージで、適切なデフォルト値を提示して尋ねます:
-
焦点(Angle):トピックが広い場合のみ。例:「インターネットの仕組み」は焦点が必要ですが、「git push すると何が起こるか」は必要ありません。
-
音声(Voice):常に選択肢を提示します。ユーザーは自分の声がオプションであることを知らないためです。以下のように提示します:
- 内蔵音声(デフォルト、すぐに使えます)
- 保存済みの音声(
VOICESにリストがある場合) - 自分の声:スマートフォンで約20~30秒録音し、ファイルを送信してください。あなたの声はあなたのマシンに残ります。
- ナレーションなし(音楽と効果音のみ)
自分の声を選んだ場合は、
${CLAUDE_SKILL_DIR}/references/voice.mdを読み、録音のヒントとサンプル文章を何よりも先にユーザーに提供します。ユーザーはあなたがストーリーボードを作成している間に録音できます。 -
長さ(Length):デフォルトは30~60秒、16:9。
これをアンケート形式にしないでください。最大でも2~3つの短い質問です。
2. トピックを理解し、事実を正確に把握する
ユーザーがコードを提供した場合は、それを読み、実際のフローをトレースします。技術系の視聴者は間違いを指摘します。そのため:
- イベントの順序を正しく把握してください。例:既に読み込み済みのページでは、DNSとTLSハンドシェイクはすでに行われているため、初回アクセス時に配置するか、「初回訪問時」とラベルを付けます。
- 現代のシステムが実際に行っていることを優先します。例:Chromeは教科書通りのTCP3ウェイハンドシェイクではなく、QUICとTLS 1.3でGoogleと通信します。
- 簡略化する場合は、サブキャプションに「簡略化」と記載します。
- 説明用の値(IPアドレスの例、
.exampleドメイン、架空のスコア)を使用し、正確なミリ秒数などの偽りの精度は避けます。 - 実際の製品のロゴやピクセル単位で正確なUIを再現しないでください。ラインアートの代用で十分です。
3. ストーリーボードとスクリプトを作成し、制作前に承認を得る
4~8の番号付きステップ(約30~60秒)を計画し、最初に title シーン、最後に end シーンを追加します。各シーンについて以下を作成します:
- ナレーション(
script.json):{"id", "text", "gap"}。gapは行の後のポーズで、0.25~0.45秒。 - キャプション(
cap): 8語以内、ミュート時に読めるもの。サブキャプション(sub): 短い1行。 - 映像のビート(Visual beat):何が表示され、どのように動き、各音が何であるか。
ナレーションを書く前に ${CLAUDE_SKILL_DIR}/references/narration.md を読んでください。記事風のナレーションは、AIが記事を読んでいるように聞こえます。友達に説明するように書きましょう。
ストーリーボードとスクリプトを短いテーブルとしてユーザーに表示し、ゴーサインを待ちます。この段階で言葉を変更するのは自由ですが、音声生成後は再実行が必要になります。
ナレーションや画面テキストでは、全角ダッシュ(—)やセミコロンを使用しないでください。TTSが変に読み上げ、短い文の方が自然に聞こえます。
4. プロジェクトの雛形を作成(Scaffold)
bash SCRIPTS/new_project.sh ./explainers/<slug> "<ページタイトル>"
これにより、engine.js、スターター用の scenes.js(「送信」を押したときの小さな動作例)、および script.json がコピーされます。空でないフォルダには上書きしません。スターターの内容を実際のストーリーボードで置き換えてください。
5. scenes.js を構築する
最初に ${CLAUDE_SKILL_DIR}/references/engine-api.md を読んでください。これには時間モデル、すべてのヘルパー関数、レイアウトゾーン、サウンド名、ブート設定が含まれています。重要な考え方:
defineScenes([...])でデザイン上の時間(design duration)を設定します。prog(t, S.id + a, S.id + b)でデザイン時間でアニメーションさせます。エンジンは各シーンを音声クリップに合わせて伸縮させるため、クリップの長さに手動で調整しないでください。- ワールドは y 150 から 800 の範囲に収めます。トラッカーはその上、キャプションはその下に配置されます。
- 永続させるもの(ブラウザ、サーバーなど)は1つのシーンで表示され、その後も描画され続けます。
- データセンター内部など、もう一つの「場所」については、x + 1920 に描画し、
camera(t)でパンします。パン中に画面が空にならないよう、パンの途中で内容の表示を開始します。 - すべての可視イベントには、
sfx(add)で音を付けます。キー操作にはkey、飛来するパケットにはwhoosh*、到着にはthunkやpop、成功にはchimeやsuccess。これが完成度を高める大きな要素です。
6. 音声なしでプレビュー、フレームを確認
node SCRIPTS/shots.js <project> # 各シーンの中央、.shots/<time>/ フォルダに保存
すべてのスクリーンショットを確認します。表示前に縮小します(macOSでは sips -Z 960、その他では ffmpeg -i in.png -vf scale=960:-1 out.png)。以下をチェック:
- テキストが重なっていたり、端からはみ出していないか
- パン中の空フレーム
- 背後にあるべきものの前面に描画されている要素
修正して再撮影します。ページの起動に失敗した場合、スクリプトがページエラーを表示します。
7. 音声
内蔵音声(--voice なし):
$PY SCRIPTS/narrate.py --script <project>/script.json --out <project>/narration --takes 2
保存済みの音声:--voice <UTH_HOME>/voices/<name>.wav を追加します。
録音からの新しい音声:${CLAUDE_SKILL_DIR}/references/voice.md に従います。prep_voice.sh、簡単なテスト行、同意について説明されています。ユーザー自身の声、または使用同意を得たことを確認した声のみをクローンしてください。
次に、ユーザーに聴いてもらいます:<project>/narration/narration-preview_take1.wav(macOSでは open、Linuxでは xdg-open を使用)。
- 初回実行時は約1GBのモデルウェイトをダウンロードします。その後は、Apple SiliconまたはGPU上でほぼリアルタイムで動作し、CPUでは低速です。
- あなたは音声を聞くことができません。 そのことをユーザーに伝え、テイクを耳で判断してもらうよう依頼してください。ユーザーはテイクをラインごとに混ぜ合わせることができます。
--only <id>で1行だけ再作成できます。 - Turboは
[chuckle]、[laugh]、[sigh]、[gasp]、[surprised]のようなタグをサポートしています。動画ごとに1つか2つまでにしてください。
ナレーションなし:このステップと build_timeline.py をスキップします。シーンはデザイン時間を維持し、動画には音楽と効果音のみが含まれます。
8. タイムライン、レンダリング、確認
python3 SCRIPTS/build_timeline.py <project> [--pick dns=2,enter=2]
node SCRIPTS/render.js <project> [--music-db -12] [--sfx-db 1] [--silent] [--audio-only]
render.js は音声、音楽、効果音のステムを構築してミックスし、音声下で音楽をダッキング(減衰)して -16 LUFS にノーマライズします。<slug>.partial.mp4 にレンダリングし、ffprobe が全長を確認した後にのみ <slug>.mp4 にリネームします。
ユーザーに完了を伝える前に、実際のファイルを確認します:
- ffprobe が期待される再生時間とビデオ・オーディオストリームを表示する
ffmpeg -ss <終盤> -frames:v 1が正しいシーンを表示する- ラウドネスが約 -16 LUFS である
次に、MP4と index.html プレビューを開きます。プレビューは、チャプターとスクラブ機能付きでミックスを同期再生します。
確認した内容、確認できなかった内容(音声の聞こえ方)、簡略化した部分を簡潔に報告します。
作業ルール
- glob削除は禁止。
rmをワイルドカードやrm -rfと組み合わせてプロジェクトフォルダに対して実行しないでください。新しい出力は新しいフォルダに作成し、ファイルは名前で上書きされます。 - 中断されたコマンドは部分的に実行されている可能性があります。 ユーザーがマルチステップコマンドを中断した場合、続行する前にディスク上で何が変更されたかを確認し、部分的な影響をユーザーに伝えてください。クリップの半分が入れ替わった状態では、一見完成したように見える短い部分的な動画が残る可能性があります。
- 好みのヒントに自動で切り替えない。 ユーザーが「テイク2が気に入った」と言ってもまだ決めかねている場合は、再レンダリングする前に確認してください。
- 提案はするが、自動で作成しない: 9:16の縦型バージョン、30秒へのトリミング、またはさらなる仕上げについて、それぞれ1行で提案してください。
トラブルシューティング
PerthImplicitWatermarkerで'NoneType' object is not callableエラー:uv pip install --python $PY "setuptools<81"を実行します。Chatterboxのウォーターマークライブラリにpkg_resourcesが必要です。- 最初のTTS実行は遅い:これはウォームアップとモデルのダウンロードです。以降の実行は高速です。
- 音楽が大きすぎる、または小さすぎる:
--music-db(デフォルト -12)を調整します。効果音:--sfx-db(デフォルト 1)。--audio-onlyを使用してミックスを素早く調整します。 - Chromeが見つからない:
CHROME_PATHを設定するか、under-the-hood-setupスキルをロードします。 - Chromeのオフラインオーディオは実行ごとにビット単位で同一ではないため、レンダリング間でステムがわずかに異なります。タイミングは正確で、フレームはピクセル単位で同一です。