プロモーションビデオ制作(ローカルツールチェーン) は何をしますか?
エージェント非依存: ファイルを読み取り、シェルコマンドを実行できるあらゆるAIコーディングエージェント向けに作成されています。install.sh によりClaude Code、GitHub Copilot CLI / Microsoft Scout、Google Antigravityにネイティブインストール可能。Cursor、Codex、Windsurfなどからはプレーンマークダウンの実行手順書として使用できます。
このツールチェーンは、Idan Shimon(Microsoft、github.com/idanshimon)氏によって最初に実証されました。彼がAIエージェントにこのスタックをローカルで駆動させ、コストゼロで制作した58秒のコンセプト説明動画が、このスキルの原形です。
インストール場所を仮定しないでください。PROMO_VIDEO_SKILL_DIR をこの SKILL.md が配置されているディレクトリに設定してください。その scripts/ ディレクトリには、TTSスクリプト、頭字語発音補助ツール、固定されたTTS依存関係があります。assets/ には、構成スケルトン、空間コンポーネントテンプレート、決定ログスキーマがあります。references/ には、マシンごとに一度だけ実行するツールチェーンブートストラップ(install.md)があります。
中核原則: スクリプトを最初に確定させ、下流のすべての数値は実際のナレーション音声から導出され、決して推測されません。
説明動画ではなく、プロモーション: このスキルは「なぜ(WHY)」を売ります。対象視聴者、課題、変革、証拠、ひとつのCTA(行動喚起)です。仕組み(「どのように動作するか」)は最大でも1テイク、それもユーザーが指示した場合のみです。仕組みを教えるリクエストは、姉妹スキルの explainer-video に属します。
ツールプロファイル
| ツール | 場所 | 実績バージョン | ライセンス | 役割 | 呼び出し方 |
|---|---|---|---|---|---|
| OpenMontage | ~/OpenMontage | 2026-07 チェックアウト | AGPLv3(ツールのみ。動画には何も含まれません) | パイプラインガバナンス: ステージ順序、決定ログ、ゲート、レンダリング後のセルフレビュー規律 | AGENT_GUIDE.md + pipeline_defs/animation.yaml を読んでください。あなた(エージェント)がオーケストレーターです。デーモンは実行されません。 |
| Kokoro | PyPI | Kokoro-82M (hexgrad)、kokoro==0.9.4 | コード Apache-2.0、モデル重み Apache-2.0 | ナレーションTTS、シーンごとに24 kHz モノラルWAV | Python 3.12 venv; KPipeline(lang_code="a")、音声 af_heart、speed パラメータ |
| HyperFrames | ~/hyperframes | 0.7.57、ソースからビルド | Apache 2.0 | HTML/CSS/GSAP 構成 → ヘッドレスChrome経由で決定論的MP4 | node ~/hyperframes/packages/cli/dist/cli.js <cmd> |
| FFmpeg | ~/FFmpeg ソース; ビルドディレクトリ内のバイナリ | git master (N-125608 時代) | GPL ビルド (libx264 有効) | 音楽合成 (aevalsrc)、エンコード、ffprobe QA、ラウドネス、プラットフォームパッケージング | hyperframes コマンドの前にビルドディレクトリを PATH に追加 |
| bun | npm-global | 1.3.14 | — | hyperframes モノレポのビルド | npm i -g bun (brew cask は一度失敗したことがあります) |
| Node.js | nvm | 24.x (≥22 必須) | — | hyperframes CLI の実行 | — |
| Python | uv管理 | 3.12 | — | Kokoro 推論 | 常に venv (uv venv --python 3.12 .venv); black + pylint でクリーン |
| espeak-ng | Homebrew または apt | 任意 | GPL | Kokoro G2P フォールバック (必須) | macOS: brew install espeak-ng; Debian/Ubuntu: sudo apt install espeak-ng |
| gsap | ベンダーファイル | 3.x gsap.min.js | GSAP 標準ライセンス | 構成内のタイムラインアニメーション | video/assets/ にコピー — CDN <script src> は絶対に使用しないこと(オフラインでの決定論性のため) |
環境チェック(マシンごとに一度)
export FFMPEG_BUILD_DIR="${FFMPEG_BUILD_DIR:-$HOME/ffbuild}"
export HYPERFRAMES_DIR="${HYPERFRAMES_DIR:-$HOME/hyperframes}"
export PATH="$FFMPEG_BUILD_DIR:$PATH"
node "$HYPERFRAMES_DIR/packages/cli/dist/cli.js" doctor
FFmpeg、FFprobe、Node、Chrome はすべて本番環境前に合格している必要があります(その他の doctor 行はこのスキルが使用しないオプションコンポーネントです)。4つのいずれかが失敗した場合は、$PROMO_VIDEO_SKILL_DIR/references/install.md に従ってください。これは、マシンごとに一度の完全なブートストラップです。システムパッケージ(macOS/Linux)、ソースツールのクローン(OpenMontage リファレンスチェックアウトを含む)、libx264 を使用した FFmpeg のソースビルド、HyperFrames CLI のビルド、エージェント権限のヒントが含まれます。doctor がすでに合格している場合は、そのファイルをロードしないでください。
Chrome: CLI は ~/.cache/puppeteer/ の下にあるキャッシュされた chrome-headless-shell を自動的に使用します(存在しない場合は一度ダウンロードします)。最初の TTS 実行も同様に、Python パッケージと Kokoro-82M の重みをダウンロードします。これらの一度限りのダウンロード後は、合成、検証、レンダリング、パッケージングはオフラインパスを持ちます。
プロジェクトレイアウト(ビデオごとに作成、独自のgitリポジトリ、アトミックコミット)
<project>/
.venv/ # uv venv, python 3.12
.gitignore # .venv/ __pycache__/ video/snapshots/ .DS_Store
README.md # 作成方法 + 再現手順(最後に記述)
production/
research/research-brief.md # 出典付きの事実 + 正直な競合テーブル
proposal.md # コンセプト、シーンリスト、提供約束、実行時決定
script/script.md # 確定ナレーション
scene_plan/scene-plan.md # シーンごとのビジュアル + モーションノート
checkpoints/decision-log.json # 追記専用、(カテゴリ、対象) がキー
checkpoints/self-review.md # レンダリング後の証拠
assets/audio/ # kokoro WAV + durations.json
renders/ # マスター + プラットフォーム派生版
youtube/ # metadata.md + thumbnail.jpg
tools/tts_generate.py # このスキルからコピー、SCENE_NARRATIONS を編集
tools/tts_pronounce.py # scripts/ から、頭字語発音(編集不可)
tools/pronunciation.local.json # オプションのプロジェクト辞書オーバーレイ
video/
index.html # 構成ファイル
assets/gsap.min.js # ベンダーファイル
assets/audio/ # WAV のコピー(構成相対パス)
ステージ 1 — リサーチ(オフラインファースト)
対象のリポジトリ/README/ドキュメントを調査します。アーキテクチャではなく、成果と証拠(数値、採用実績、ビフォーアフターの結果)を探します。research-brief.md を作成します。
- ポジショニングブロック(必須 — スクリプトはこれなしでは確定できません)。 一文ずつ: 対象視聴者(誰のためか)、課題(彼らが現在何を感じているか)、変革(彼らに何が変わるか)、差別化要因(これを選ぶ唯一の理由)。これがメッセージです。以降のすべてのステージはこれに奉仕します。
- 製品の事実。それぞれ出典を確認し、ベネフィットをサポートするものだけを残します(「だから何?」テスト: 視聴者は何を得るのか?)。
- 競合テーブル(弱み → 対象がどのように対処するか)。正直ルール: 競合の真の長所を認める(競合の列の ✓ は、あなたの列の信頼性を高めます)。
- 「スクリプトで使用が許可された主張」リスト。
ステージ 2 — 提案 + 決定
proposal.md を記述します。ポジショニングブロックを再掲し、5〜7シーンのコンセプト、目標約60秒、提供約束(「モーション主導、スライドショーではない」)、ツールチェーンテーブルを含めます。decision-log.json に記録します。$PROMO_VIDEO_SKILL_DIR/assets/decision-log.json の空のスキーマから始めます(フィールド: category、subject、selected、options_considered、rejected_because、confidence)。パイプライン選択、レンダリングランタイム選択(Remotion を検討した代替案として記録 — OpenMontage はハードルール)、構成モード(主要作業にはアトリエ)、音声選択、音楽選択、出力プロファイル、承認ゲート、そして CTA選択 — 正確に1つの行動喚起(リポジトリへのスター / インストール / デモ視聴 / サイト訪問)など。プロモーションは機能リストで終わると死にます。「お願い」で終わらなければなりません。ユーザーのブリーフが選択肢を固定していた場合は、approved_by: "user brief" とマークします。後で決定を変更しましたか?同じ (category, subject) で新しいエントリを追加してください。古いエントリは決して編集しないでください。
ステージ 3 — スクリプト(確定ポイント)
約150〜155ワード ≈ kokoro af_heart スピード 1.1 で約60秒。プロモーションアーク(Whyを売る): ステーク/課題のフック → 明かし(名前 + 約束する変革) → 視聴者の世界におけるベネフィット → 証拠(正直な比較、実際の成果) → 願望 → 決定ログからの単一CTA。機能はベネフィットの証拠としてのみ登場します。すべての文は「だから何?」テスト(視聴者は何を得るのか?)に合格する必要があり、そうでなければ書き直します。「どのように動作するか」のテイクは最大1つ、それもユーザーが指示した場合のみです。仕組みを教えることは explainer-video スキルの役割です。声: 二人称、現在形、短くパンチの効いた文(「it enables faster shipping」ではなく「you ship faster」)。頭字語はスクリプトと画面テキストの両方で自然に記述します(「OID」、「MCP」、「AI」)。合成時に tools/tts_pronounce.py が辞書から発音形式(単語読み vs 一字ずつ)を導出し、未知の頭字語は一字ずつ綴られて報告されます。オプションの tools/pronunciation.local.json({"NASA": "word", "ECG": "letters", "NGINX": "engine X"})でドメイン用語を分類します。すべて大文字の強調を避けてください。すべて大文字の単語は頭字語として扱われます。シーンごとのワード予算 ≈ 60秒 × 2.55 words/s のうちのそのシーンの割合。
ステージ 4 — ナレーション + タイミング導出(負荷のかかるテクニック)
同梱の固定された直接依存関係からプロジェクトの Python 3.12 環境を作成し、汎用テンプレートをコピーし、SCENE_NARRATIONS のみを編集します。
uv venv --clear --python 3.12 .venv
uv pip install --python .venv/bin/python \
-r "$PROMO_VIDEO_SKILL_DIR/scripts/tts-requirements.txt"
mkdir -p tools
cp "$PROMO_VIDEO_SKILL_DIR/scripts/tts_generate.py" tools/tts_generate.py
cp "$PROMO_VIDEO_SKILL_DIR/scripts/tts_pronounce.py" tools/tts_pronounce.py
PYTORCH_ENABLE_MPS_FALLBACK=1 .venv/bin/python tools/tts_generate.py
mkdir -p video/assets
curl -fL https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js \
-o video/assets/gsap.min.js
最初の合成は Kokoro-82M の重み(約330 MB)をダウンロードします。以降の実行はキャッシュを使用し、オフラインで実行できます。シーンごとの WAV と durations.json を書き出します。実行時に未知の頭字語の WARNING が出力された場合は、それらのトークンを tools/pronunciation.local.json で分類し、タイミングを導出する前に再実行します。
約150〜155ワード、スピード1.1を開始時の推定値として扱い、較正値としては扱いません。合計 >62秒?スピードを上げるかワードを削減し、再測定します。
タイムラインを導出します(シーン間のパッド約0.3〜0.5秒):
n_start[0] = 0.5 # リードイン
scene_start[i] = n_start[i] - 0.15..0.4 # ビジュアルはVOの直前に着地
n_start[i+1] = n_start[i] + dur[i] + 0.3..0.35
TOTAL = last n_end + 0.6..0.9 tail (フェードアウト)
これらの数値は、一致しなければならない3箇所に配置されます。シーンの data-start/data-duration、<audio data-start> の値、そして JS の const S1..Sn タイムライン定数です。WAV を video/assets/audio/ にコピーします。
ステージ 5 — 音楽背景(ダウンロード不要)
ソースビルドされた FFmpeg を使用して、決定論的な合成パッドを生成します。TOTAL_SECONDS を導出された構成の合計に設定します。外部サンプルやプライベートリファレンスは必要ありません。
TOTAL_SECONDS=60.5
FADE_SECONDS=3.5
FFMPEG_BIN="${FFMPEG_BUILD_DIR:-$HOME/ffbuild}/ffmpeg"
FADE_OUT_START="$(awk -v total="$TOTAL_SECONDS" -v fade="$FADE_SECONDS" \
'BEGIN { printf "%.3f", total - fade }')"
mkdir -p production/assets/audio
"$FFMPEG_BIN" -hide_banner -y \
-f lavfi \
-i "aevalsrc=0.07*(sin(2*PI*110*t)+0.65*sin(2*PI*110.7*t)+0.45*sin(2*PI*164.81*t)+0.30*sin(2*PI*220.9*t)):s=48000:d=${TOTAL_SECONDS}" \
-af "highpass=f=55,lowpass=f=1200,afade=t=in:st=0:d=2,afade=t=out:st=${FADE_OUT_START}:d=${FADE_SECONDS},pan=stereo|c0=c0|c1=c0" \
-c:a pcm_s16le production/assets/audio/music-bed.wav
控えめに保ち、data-volume="0.12..0.16" でミックスすると雰囲気として読めます。プロモーションのエネルギーとしては範囲の上限に向けて設定し、ナレーションを超えないようにします。WAV を video/assets/audio/ にコピーし、別個のオーディオトラックを使用します。
ステージ 6 — 構成作成(HyperFrames 契約)
$PROMO_VIDEO_SKILL_DIR/assets/composition-skeleton.html(以下のすべてのルールを体現しています)から始めます。HTML を記述する前に、$HYPERFRAMES_DIR/skills/hyperframes-core/SKILL.md にざっと目を通します。
構造: スタンドアロンの index.html、ルート <div data-composition-id="X" data-start="0" data-duration="TOTAL" data-width="1920" data-height="1080">、シーンは <section class="clip" data-start data-duration data-track-index="1">、1つの gsap.timeline({paused:true}) が window.__timelines["X"] に同期的に登録され、トゥイーンの位置は S<i> + local_offset です。
実際のデバッグ時間を消費するルール — 事前に従うこと:
- すべての
<video>/<audio>には、明示的で空でないidが必要です。 HyperFrames では、id のないオーディオはブラウザプレビューで再生される可能性がありますが、レンダリング時には無音になります(lint がこれをエラーとして報告します。無視しないでください)。 - GSAP でトゥイーンする要素の CSS に初期の
transform:を決して配置しないでください。 —tl.fromTo(el, {y:24, opacity:0}, {y:0, opacity:1, ...})を使用します。GSAP が完全な transform を所有します。(CSS でのopacity: 0単独は、.to()のみの表示には問題ありません。) - 背景塗りつぶしは、全画面表示の子要素(
position:absolute; inset:0)に配置し、ルート要素には決して配置しないでください(ルートの背景は黒くレンダリングされる可能性があります)。 - メディア要素はルートの直接の子要素であり、
data-track-indexは別個のオーディオトラック(例: 7=VO、8=音楽)に設定します。 - 無限リピートなし(
repeat: -1禁止 — 有限のものを使用、例: カーソル点滅repeat: 9, yoyo: true)、Date.now/Math.randomなし、ネットワークフェッチなし、CDN スクリプトなし。 - すべての場所で一意の
id。transform/opacity(+ SVG 線描画のstrokeDashoffset)のみアニメーション化します。 - シーン途中で出現する要素: 非表示の初期状態 +
S<i>+offsetでのトゥイーン。fromToはimmediateRenderを持つため、トゥイーン前のシークは from 状態を表示します — これがシークセーフにする理由です。 :rootCSS 変数内のデザイントークン = テーマ。ground/ink/accent + タイプスタックを交換して再スキンします。レンダラーがバンドルするフォント(JetBrains Mono)を使用します — システムフォントはとにかく置き換えられます(lint info がマッピングを教えてくれます)。- セカンダリテキストは ground カラー上で WCAG AA 以上に保ちます —
checkはコントラストでビルドを FAIL にし、準拠する色を出力します。 - 意図的なオーバーラップ/スクロールアンダー: 発見事項を未説明のままにせず、
data-layout-allow-overflow/-overlap/-occlusionでマークします。
シーンモチーフのアドバイス: 結果の状態 — 改善された視聴者の世界(ビフォー/アフターの対比、消えた課題、画面上の結果)を表示します。対象の実際の UI/ドメイン(偽の検索パレット、ターミナル、ツリー、テーブル)から、仕組みのツアーではなく、ベネフィットの証拠としてサポートの垣間見を引き出します。ナラティブアークはテンプレート化します。モチーフはしてはいけません。
$PROMO_VIDEO_SKILL_DIR/assets/composition-skeleton.html には、被写体のロゴキーカラーに合わせた hsl(200, 40%, 6%) のダークテーマが含まれています。テーマの CSS 変数(--color-ground、--color-ink、--color-accent、--color-accent-muted、--color-button-glow)を編集するか、デフォルトのままで 5 分以内にテストレンダリングを実行し、動画が機能することを確認してからスタイルを調整します。
ステージ 7 — 構成開発ワークフロー
開発ループ:
# 1. HTML を編集
vim video/index.html
# 2. プレビューレンダリング(シーン 0、1〜3秒、スナップショット)
node "$HYPERFRAMES_DIR/packages/cli/dist/cli.js" render \
--trim-to-first --duration 3? \
video/index.html
open video/snapshots/preview.mp4
# 3. lint をチェック(すべてのレンダリング前に実行)
node "$HYPERFRAMES_DIR/packages/cli/dist/cli.js" check video/index.html
--trim-to-first は指定された時間だけレンダリングします。進行中は 3〜5 秒のセグメントで十分です。check は GSAP の競合、オーディオ ID の欠落、コントラスト違反、許可されていない JS、その他「高価なルール」違反の lint を実行します。すべてのチェックが合格するまでレンダリングしないでください。
ステージ 8 — フルレンダリング + QA
以下を実行する前に、すべてのアセットが準備できていることを確認します。WAV が video/assets/audio/ にあり、duration 属性が durations.json と一致していること。check が合格していること。決定ログにすべてのエントリが記録されていること。
node "$HYPERFRAMES_DIR/packages/cli/dist/cli.js" render \
video/index.html \
--out "production/renders/master-$(date +%Y%m%d).mp4"
次に、セルフレビューを実行します:
mkdir -p production/checkpoints
echo "# セルフレビュー" > production/checkpoints/self-review.md
# デュレーション
"$FFMPEG_BUILD_DIR/ffprobe" -v error -show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 "production/renders/master-*.mp4"
# 期待: Target seconds ±1s
# ラウドネス (ITU-R BS.1770-4)
"$FFMPEG_BUILD_DIR/ffmpeg" -i "production/renders/master-*.mp4" \
-af loudnorm=I=-16:LRA=1:TP=-1.5:print_format=json -f null -
# 期待: ナレーションが知覚ラウドネスを支配していること
# サイレンス検出 (オーディオドロップアウト)
"$FFMPEG_BUILD_DIR/ffmpeg" -i "production/renders/master-*.mp4" \
-af silencedetect=noise=-30dB:d=0.3 -f null - 2>&1
# 0.3秒を超えるサイレンスは意図的でなければならない
ステージ 9 — 最終パッケージング + README
プラットフォーム派生版を作成します:
# ショート (Shorts/Reels/TikTok) – 9:16 クロップ
"$FFMPEG_BUILD_DIR/ffmpeg" -i "production/renders/master-*.mp4" \
-vf "crop=1920:1080:0:0,scale=1080:1920:flags=lanczos" \
-c:a copy "production/renders/social-short.mp4"
# YouTube – バナーリーダーボードと自動チャプターマーカーはスキップ
# (現時点では外部ツールの対象外)
README.md を次のように構成します:
# [製品名] – プロモーションビデオ
## 作成方法
- **エージェント**: Claude Code (または使用したエージェント)
- **ツールチェーン**: Kokoro + HyperFrames + FFmpeg + OpenMontage
- **ターゲット長**: ~60s (実際: [ffprobe からの秒数]s)
- **ワード数**: ~155 (スクリプトから)
- **費用**: $0 (すべてのツールは無料/オープンソース、モデル重みはApache-2.0)
## 再現
PROMO_VIDEO_SKILL_DIR=/path/to/mhuot/promo-video-skill
HYPERFRAMES_DIR=/hyperframes
FFMPEG_BUILD_DIR=/ffbuild
このスキルのプロジェクト
このスキル自体は、自身のプロモーションビデオを生成するために設計されています。ツリーのルートにある video/ ディレクトリと tools/ ディレクトリは、実際のこのスキルのプロモーションビデオです。$PROMO_VIDEO_SKILL_DIR をこのリポジトリのルートに設定し、上記の手順に従ってレンダリングします。assets/composition-skeleton.html は最終的な video/index.html ではありません。これはスケルトンテンプレートであり、video/index.html はそれを実装したものです。
頭字語発音辞書
tools/tts_pronounce.py は、このスキルの多くの頭字語の発音を分類する組み込み辞書を維持しています。TTS スクリプトを変更したために未知の頭字語が報告された場合は、tools/pronunciation.local.json にエントリを追加してください。参照:
| 頭字語 | 発音タイプ | 例 |
|---|---|---|
| OID | 単語 | "oyd" |
| MCP | 字読み | "M-C-P" |
| AI | 字読み | "A-I" |
スタイルガイド
- ナレーション: 二人称、現在形、短くパンチの効いた文。
- 画面テキスト: 短い(各シーンに表示されるのは8〜12語のみ)。シーンの単一のアイデアを釘付けにします。
- タイミング: ビジュアルはナレーションのわずか前に着地します(約100〜250ms)。タイムラインはテキストに先んじます。
- CTA: 正確に1つ。スター / インストール / デモ視聴 / サイト訪問。「詳細を見る」や「今すぐ始める」などの弱いCTAは禁止。
- 品質管理: すべての主張は
research-brief.mdの事実に根ざしています。推測はありません。
既知の問題と制限
- Kokoro 速度の非線形性:
speedパラメータが高いほど、予想以上の短縮効果が得られることが知られています。スクリプト長の推定値として扱い、常にdurations.jsonで検証してください。 - HyperFrames
--trim-to-firstの動作:--durationで指定された値は時間ではなくシーン数として解釈される可能性があります。出力を確認してください。 - FFmpeg の
afadeは、レンダリングされた長さがTOTAL_SECONDSより短い場合、短いオーディオを生成します。フェードアウト時間は信頼できません。その場合は音楽ベッドを再生成してください。 - OpenMontage の
AGENT_GUIDE.mdは共通の参照です。最初に読み、指示があればこのスキルに取って代わります。