vid-motion-graphics について
vid-motion-graphicsは、複数シーンのモーショングラフィックス動画を作るクリエイター向けのスキルです。HTML/CSSアニメーションをフレーム単位で描画し、FFmpegでMP4にまとめて、決定的な出力を行います。
主な機能
- 01複数シーンのMP4動画生成
- 021~6シーンに対応
- 035種類のビジュアルスタイル
- 04画面比率、フレームレート、音楽の指定
- 05ChromiumとFFmpegによる書き出し
活用シーン
- 01コンテンツ概要から動画を作成
- 02データや要点を含む短編動画の作成
- 03用途に合わせた画面比率での書き出し
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ varnan-tech/ opendirectory --skill vid-motion-graphicsインストール後、Claude Code で/vid-motion-graphicsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdevals/evals.jsonreferences/scene-library.mdstyle-presets.mdscripts/capture-frames.mjsexport-video.sh詳細
vid-motion-graphics
複数シーンのモーショングラフィックスを MP4 として生成します。Playwright を介してヘッドレス Chromium で HTML/CSS アニメーションをレンダリングし(Web Animations API によるフレームシーク)、PNG フレームを FFmpeg で組み立てます。React、AI API、Python は不要です。graphic-gif ファミリー以外に新たな依存関係はありません。
CDN フォントのみを使用します。HTML 内で外部ライブラリは使用しません。
重要なルール(生成のたびに事前にお読みください)
- シーン遷移には
window.renderFrame(t)を使用してください。CSS の@keyframesは使わないでください。 Chromium では、CSS アニメーションのcurrentTimeを使ったシークは、後ろ向きのシーク時に黙って無視されます。renderFrame の方法では、純粋な JS 関数がミリ秒単位の値からopacity/transformを直接計算します。Playwright は各フレームでこの関数を 1 回呼び出します。決定論的で、競合状態が発生しません。 - どの要素にも
animation-delayを設定しないでください。 renderFrame を使えば不要です。animation-delayを書こうとしていることに気付いたら、そこで止めてください。アーキテクチャが間違っています。 window.__videoReady = trueを設定するのは、document.fonts.ready.then(...)の内部だけにしてください。 同期的に設定しないでください。Playwright が 1 フレーム目をキャプチャする前にフォントを読み込む必要があります。そうしないと、テキストが代替フォントでレンダリングされます。window.__stopPreview()を公開してください。 ブラウザーの rAF プレビューループと、Playwright の evaluate/スクリーンショット呼び出しが競合します。capture-frames.mjsはフレームループの前に__stopPreview()を呼び出します。必ず含めてください。- シーン境界の確認には
t <=ではなくt < startMsを使ってください。 フレーム 0 でt <= 0とすると、シーン 1 が黒くなります。正しいガードはif (t < startMs || t >= endMs) return hiddenです。 - body の寸法はピクセル単位の正確な値にしてください。 幅と高さは整数(
1080px、1920px)にします。%、vw/vh、レスポンシブ単位は使わないでください。 - 10% の登場オーバーラップを除き、複数のシーンを同時に表示しないでください。 すべてのシーンは renderFrame の表示対象時間以外では
opacity: 0にします。 - トランジションには
opacityのみを使用してください。displayの切り替えやvisibilityは使わないでください。GPU 合成された opacity はフレーム単位で正確です。 - チャットに HTML を貼り付けないでください。 ファイルに保存し、概要のみを表示します。
- タイトルにはトピックではなくメッセージを示してください。 「Q4 2024 Performance Video」ではなく、「Q4 に目標を達成した 3 つの理由」とします。
- HTML を生成する前に、必ず
references/scene-library.mdを読んでください。 そのファイルに記載された HTML 構造と CSS クラス名をそのまま使用します。
Step 1:情報の確認
必須: content_brief
省略可能なパラメーターとデフォルト値:
| パラメーター | デフォルト値 | 説明 |
|---|---|---|
| content_brief | — | 動画で伝える内容を説明するテキスト(必須) |
| scenes | auto | シーン数(1~6)。auto の場合はブリーフから決定します。 |
| duration_per_scene | 3s | シーンごとの長さ(秒、1~8 秒) |
| style | kinetic-dark | kinetic-dark / editorial-light / data-pulse / bold-type / minimal-clean |
| aspect_ratio | 1:1 | 1:1(1080×1080) / 16:9(1920×1080) / 9:16(1080×1920) |
| fps | 30 | 1 秒あたりのフレーム数(24、30、または 60) |
| music | none | バックグラウンドトラックに使用する音声ファイルのパス(mp3/m4a/wav) |
| source | — | 最終フレームのフッターに表示する出典 |
content_brief がない場合は、次のとおり正確に尋ねてください:
"動画を作成するには、コンテンツブリーフが必要です。動画で何を伝えますか?
例:「Q4 の売上が 85% 増加した 3 つの理由を示してください。新規エンタープライズ契約、解約率の低下、値上げです。目を引く数字を使ってください。スタイル:data-pulse。」
省略可能:スタイル(デフォルト:kinetic-dark)、アスペクト比(デフォルト:1:1)、シーンごとの秒数(デフォルト:3s)"
content_brief がある場合は、すぐに Step 2 に進みます。
ステップ 2:内部アーキテクチャ(ユーザーには表示しない)
1. ブリーフをシーンに分割します(最大 6 シーン):
- シーン 1 = フックまたはタイトル(必ず含めます)
- シーン 2〜N-1 = 補足ポイント、指標、またはストーリーの展開
- シーン N = CTA または締めくくりの要約(シーンが 2 つ以上の場合は必ず含めます)
- 1 シーンにつき重要なアイデアは 1 つにします。ブリーフにアイデアが 7 つ以上ある場合は、重要度の低いものをまとめます
2. references/scene-library.md を読み、各シーンのタイプを選びます:
- フック/オープニング →
title-card - 単一の指標 →
stat-reveal - 2〜4 個のポイントのリスト →
bullet-list - ビフォー/アフターまたは 2 つの値 →
split-screen - お客様の声/引用 →
quote-card - 最終シーン/CTA →
cta-card
3. references/style-presets.md を読み、選択したスタイルの CSS トークンとアニメーションの個性を読み込みます。
4. タイミングを計算します:
totalDuration = sceneCount × duration_per_scene (seconds)
totalFrames = totalDuration × fps
各シーンは、@keyframes の全範囲のうち (100 / sceneCount)% を占めます。
| シーン | 開始 % | 終了 % |
|---|---|---|
| 1 | 0% | (100/N)% |
| 2 | (100/N)% | (200/N)% |
| … | … | … |
| N | ((N-1)×100/N)% | 100% |
各シーンの範囲内:
- 表示:シーン範囲の最初の 10%
- 維持:シーン範囲の 10% から 85%
- 非表示:シーン範囲の 85% から 100%
5. ピクセル寸法を決定します:
1:1→ W=1080, H=108016:9→ W=1920, H=10809:16→ W=1080, H=1920
ステップ 3:HTML 生成
コードを書く前に、references/scene-library.md と references/style-presets.md を読みます。
必須の HTML 構造:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
[font CDN link from style preset]
<style>
:root {
[all CSS tokens from style preset]
}
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: [W]px; height: [H]px;
overflow: hidden;
background: var(--bg);
font-family: var(--font-body);
position: relative;
}
.scene {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px;
opacity: 0;
will-change: opacity, transform;
}
.scene-inner {
width: 100%;
max-width: 960px;
}
/* Scene-type CSS from scene-library.md */
[paste scene-type CSS here]
</style>
</head>
<body>
<div class="scene scene-1">
<div class="scene-inner">
[scene 1 HTML from scene-library.md template]
</div>
</div>
[repeat for each scene]
<script>
window.__videoReady = false;
window.TOTAL_DURATION_MS = [totalDuration * 1000];
// ── Animation helpers ─────────────────────────────────────────────────────────
function lerp(a, b, p) { return a + (b - a) * p; }
function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }
function easeOutCubic(t) { return 1 - Math.pow(1 - clamp(t, 0, 1), 3); }
function sceneState(t, startMs, endMs) {
if (t < startMs || t >= endMs) return { opacity: 0, ty: 0 };
const prog = (t - startMs) / (endMs - startMs);
if (prog < 0.10) {
const p = easeOutCubic(prog / 0.10);
return { opacity: p, ty: lerp(24, 0, p) };
}
if (prog < 0.85) return { opacity: 1, ty: 0 };
const p = (prog - 0.85) / 0.15;
return { opacity: 1 - p, ty: lerp(0, -12, p) };
}
function applySceneState(el, state) {
el.style.opacity = state.opacity;
el.style.transform = state.ty !== 0 ? `translateY(${state.ty.toFixed(2)}px)` : '';
}
// ── Main render function — called by Playwright once per frame ────────────────
window.renderFrame = function(t) {
// Scene 1: 0ms – [D]ms
applySceneState(document.querySelector('.scene-1'), sceneState(t, 0, [D]));
// Scene 2: [D]ms – [2D]ms
applySceneState(document.querySelector('.scene-2'), sceneState(t, [D], [2D]));
// ... repeat per scene ...
};
// ── Preview loop — stopped by Playwright before frame capture ─────────────────
let __previewActive = false;
let __previewRafId = null;
window.__stopPreview = function() {
__previewActive = false;
if (__previewRafId !== null) { cancelAnimationFrame(__previewRafId); __previewRafId = null; }
};
document.fonts.ready.then(() => {
window.renderFrame(0);
window.__videoReady = true;
__previewActive = true;
const startTime = performance.now();
function previewTick() {
if (!__previewActive) return;
const elapsed = performance.now() - startTime;
if (elapsed < window.TOTAL_DURATION_MS) {
window.renderFrame(elapsed);
__previewRafId = requestAnimationFrame(previewTick);
} else {
window.renderFrame(window.TOTAL_DURATION_MS - 1);
__previewActive = false;
}
}
__previewRafId = requestAnimationFrame(previewTick);
});
</script>
</body>
</html>
デザイン品質のルール:
- 見出しのフォントサイズは 60px 未満にしないでください。モバイルのサムネイルサイズでも文字が読める必要があります
- 数字や統計値は、画面上で常に最も大きな要素にします(120〜200px)
- ディスプレイ用書体では、文字間隔を詰めます:
-0.02em〜-0.04em - 1 シーンにつきアクセントカラーは 1 色にします。複数の要素にアクセントを散らさないでください
- ダークプリセット:区切り線は
rgba(255,255,255,0.10)にし、不透明な単色は使いません - ライトプリセット:区切り線は
rgba(0,0,0,0.10)にし、不透明な単色は使いません transformを使用するすべての要素にtransform-origin: center centerを設定します.scene内のパディングは最小 80px にします。テキストがビューポートの端に触れないようにしてください
ステップ 4:セルフ QA(ステップ 5 に進む前にすべての不備を修正します)
renderFrame の正確性:
-
window.renderFrame(t)が定義されている。純粋関数であり、スタイルの書き込み以外の副作用がない - シーン境界で
t < startMsを使う(t <= startMsではない)。フレーム 0 が黒くなるのを防ぐ - シーン遷移に
animation-delayまたは@keyframesを使用していない - 2 つのシーンの
opacity: 1の表示期間が重複していない(表示時の 10% の重複を除く) -
window.__stopPreview()が公開されており、プレビューの rAF ループが__previewActiveを確認している
準備完了シグナル:
-
document.fonts.readyの前にwindow.__videoReady = falseが宣言されている -
window.__videoReady = trueの設定はdocument.fonts.ready.then(...)の内部のみで行われている -
window.renderFrame(0)がdocument.fonts.ready.then(...)の内部で、__videoReady = trueの設定前に呼び出されている
レイアウト:
-
html, bodyに正確なピクセル寸法([W]px、`[H]px)が指定されている -
bodyの幅/高さに%、vw、vh、rem単位を使用していない -
html, bodyにoverflow: hiddenが設定されている - すべてのシーンに
position: absolute; inset: 0が設定されている
デザイン:
- すべての色がスタイルプリセットのトークンを使用している。独自の hex 値は使用していない
- すべてのフォントがスタイルプリセットのものを使用している。独自の font-family 文字列は使用していない
- 見出しのフォントサイズが 60px 以上である
-
sourceパラメーターが指定されている場合、最終シーンにソースがある
ステップ 5:エクスポート
ブリーフの内容からスラッグを決定します(ケバブケース、30 文字以下):
mkdir -p chart/[slug]
HTML を保存します:chart/[slug]/video.html
ブラウザでプレビューします:
open chart/[slug]/video.html
エクスポートを実行します([skill-root] をこのスキルのディレクトリのパスに置き換えます):
bash [skill-root]/scripts/export-video.sh \
chart/[slug]/video.html \
chart/[slug]/video.mp4 \
--duration [totalDuration] \
--fps [fps] \
--width [W] \
--height [H] \
[--music path/to/audio.mp3]
初回実行時にスクリプトが Playwright をインストールし(Chromium は約 200MB)、すべてのフレームをキャプチャしてから、FFmpeg で MP4 を組み立てます。
ステップ 6:出力の概要
## Video: [title from brief]
Date: [YYYY-MM-DD] | Scenes: [N] | Style: [style] | Aspect: [ratio]
Duration: [N]s | FPS: [fps] | Frames: [N]
Files
Source: chart/[slug]/video.html
Output: chart/[slug]/video.mp4
Size: [X] MB
Checklist
- [ ] All scenes appear in sequence with no blank frames
- [ ] Text legible at mobile thumbnail size
- [ ] Scene transitions smooth (no jump cuts)
- [ ] Final scene includes CTA or closing message
- [ ] Source attribution present in final frame (if provided)
プロンプトのヒント(ユーザーがガイダンスを求めたときに表示)
「コンテンツの概要を提示してください。各シーンで伝える内容を箇条書きにしてください。」
「インサイトを直接示してください。『第4四半期に85%成長した3つの理由』のようにすると、動画の軸が定まります。」
「希望するスタイルがあれば指定してください。キネティック・ダーク(デフォルト)、エディトリアル・ライト、データ・パルス、ボールド・タイプ、ミニマル・クリーン。」
「プラットフォームに合わせてアスペクト比を指定してください。LinkedIn/Instagramのフィードは1:1、ストーリーズ/リールは9:16、YouTube/プレゼンテーションは16:9です。」
✅ 良い例:「9秒の動画を作成してください。第4四半期の売上は420万ドル(85%成長)でした。成長要因:エンタープライズ案件、解約率1.2%、値上げ。CTA:acme.com/q4。スタイル:データ・パルス。アスペクト比:1:1。」
❌ 悪い例:「当社についての動画を作って」