Communitygithub.com

caption-animation

トランスクリプトやナレーションから、単語ごとにタイミングの合ったアニメーションキャプションを生成します。TikTok、Reels、Shorts向けの、目を引くキャプションを作成するためのスキルです。

caption-animation とは?

このスキルは、音声トランスクリプトから単語レベルのタイムスタンプ付きアニメーションキャプションを生成するための完全なパイプラインを提供します。WhisperやAssemblyAIなどのツールで書き起こしたトランスクリプトを、RemotionやCSS/Webを使って単語ごとにポップイン表示するキャプションに変換します。特にショートフォーム動画(9:16)向けに最適化されており、アクティブワードのハイライト、1~4単語のページ分割、安全領域内への配置といった機能を備えています。出力はMP4への焼き込み(オープンキャプション)とSRT/VTTのサイドカーファイルの両方をサポートし、ソーシャルメディアとアクセシビリティの両方に対応します。このスキルは、単語レベルの正確なタイミング、可読性を重視したスタイリング、そしてフレーム駆動のアニメーション(`Date.now()`やタイマーを使用しない)を非交渉ルールとして厳守します。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/iart-ai/tiktok-video-skills/tree/main/skills/caption-animation

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

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

ドキュメント

キャプションアニメーション

トランスクリプトやナレーションを、単語ごとにタイミングが合った、スクロールが止まるキャプションに変換します。各単語が音節に合わせてポップインし、アクティブな単語がハイライトされ、テキストはナレーションにぴったりと固定されます。TikTok、Reels、Shortsで視聴時間を伸ばすようなキャプションを構築します — 読みやすく、ビートに乗って、安全領域内に収まります。

使用するタイミング

  • ショートフォーム縦型動画(9:16)における単語ごと、またはカラオケ形式のキャプション。
  • トランスクリプトからナレーション/音声トラックへのオープンキャプションの焼き込み。
  • Whisper/SRTトランスクリプトをRemotionやWeb上でアニメーションテキストに変換する。
  • 自動生成された字幕をブランド化された「Hormoziスタイル」の表示にリスタイリングする。

パイプライン

段階ジョブツール
書き起こし音声 → 単語レベルのタイムスタンプWhisper (@remotion/install-whisper-cpp), AssemblyAI
正規化→ { text, startMs, endMs }[] トークン@remotion/captions Caption 型
ページ分割単語を1~4語の「ページ」にグループ化createTikTokStyleCaptions()
アニメーション単語ごとのポップイン + アクティブワードのハイライトRemotion spring() / CSS
配置安全領域、読みやすいタイポグラフィ以下のレイアウトルール
エクスポート焼き込み(MP4)および/またはサイドカーSRT/VTTRemotionレンダリング / ファイル出力

2つの絶対ルール

  1. 単語レベルのタイミングであり、行レベルではないこと。 カラオケは各単語が音節に正確に合ったときにのみ魔法のように機能します。常に単語タイムスタンプに書き起こし、行をその長さで均等に分割して偽装しないでください — ずれはすぐに目に見えます。
  2. 可読性がスタイルに優先すること。 キャプションはスマートフォンで、日光の下で、ミュートされた状態で読まれます。太字のサンセリフ、太いストロークまたはシャドウ、高いコントラストが第一で、装飾はその後です。誰も読めないキャプションは装飾であり、キャプションではありません。

トランスクリプトからの単語タイミング

Whisperは直接単語レベルのタイムスタンプを出力します。すべてのソースを1つのフラットなトークン形状に正規化し、レンダラーが単語の出所を気にしないようにします:

// キャプショントークン — 下流のすべてが消費する単一の形状
type Token = { text: string; startMs: number; endMs: number };

// SRTキュー「00:00:01,200 --> 00:00:01,640」をmsにパース
const toMs = (t: string) => {
  const [h, m, rest] = t.split(":");
  const [s, ms] = rest.split(",");
  return ((+h * 60 + +m) * 60 + +s) * 1000 + +ms;
};

文レベルのキューしか存在しない場合は、単語タイミングのために再書き起こしを行ってください — 補間しないでください。Whisperのmedium.enはlargeよりも約2倍高速で、クリアな音声に対しては十分な精度があります。

単語ごとのポップイン(Remotion)

各単語のスケールと不透明度を、自身のstartMsにアンカーされたスプリングで駆動します。マイクロオーバーシュートが、キャプションを「生きている」ように感じさせます。

import { useCurrentFrame, useVideoConfig, spring, interpolate } from "remotion";

const Word: React.FC<{ token: Token; active: boolean }> = ({ token, active }) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();
  const enter = (token.startMs / 1000) * fps;            // 単語自身の入場フレーム
  const p = spring({ frame: frame - enter, fps, config: { damping: 12, mass: 0.6 } });
  const scale = interpolate(p, [0, 1], [0.6, 1]);        // 60% → 100% からポップイン(オーバーシュートあり)
  return (
    <span style={{
      display: "inline-block",
      transform: `scale(${scale})`,
      opacity: interpolate(p, [0, 1], [0, 1]),
      color: active ? "#FFE45E" : "#FFFFFF",             // アクティブワードのハイライト
      transition: "color 80ms linear",
    }}>{token.text}&nbsp;</span>
  );
};

activeは、frameを各トークンの[startMs, endMs]に対してテストすることで計算します。ハイライトは単一のアクセントカラー、またはライブワードの背後にある塗りつぶしボックスに保ち、すべての単語の色を一度にアニメーションさせないでください。

ページ分割:テキストの壁を作らない

一度に1~4単語を表示します。RemotionのcreateTikTokStyleCaptions()を使用するか(手動でグループ化)、combineTokensWithinMillisecondsを調整します: 真の単語ごとには200~500ms、短い読みやすいフレーズには1000~1500ms。

import { createTikTokStyleCaptions } from "@remotion/captions";
const { pages } = createTikTokStyleCaptions({
  captions, combineTokensWithinMilliseconds: 1200,       // ページあたり約2~3単語
});

Web/CSSポップイン(Remotionなし)

DOM/ライブプレーヤーの場合、各単語にstartMsと等しいanimation-delayを指定します:

.word { display:inline-block; opacity:0; animation: pop .26s cubic-bezier(.34,1.56,.64,1) forwards; }
@keyframes pop { from { opacity:0; transform:translateY(.18em) scale(.7) } to { opacity:1; transform:none } }
.word.active { color:#FFE45E; }
words.forEach(w => { const el = mk(w.text); el.style.animationDelay = `${w.startMs}ms`; track.append(el); });

読みやすいタイポグラフィと安全領域への配置(9:16、1080×1920)

プロパティ値理由
フォント太字/極太サンセリフ(Montserrat、Inter、Helvetica)小さく混雑した画面で読める
サイズ56~80px(フレーム高の約8%)、最小45pxスマートフォンでミュート状態でも判読可能
ストローク2~6pxの黒一色のアウトラインあらゆる背景に対応
シャドウストロークのバックアップとしてソフトドロップシャドウ明るいフレームでの分離
ケース大文字または文形式、高コントラストの塗りつぶしインパクト+スキャン容易性
垂直位置中央バンド、フレーム上端から約62~70%UIの上、アクションの下
下部安全領域下部約280px / 15%を空けるキャプション/CTA/音声UIを避ける
側面安全領域中央80%幅内に収める右側のアイコンを避ける

キャプションは中央バンドに配置します — プラットフォームUI(ユーザー名、音声、ボタン)がある最下部に固定しないでください。テキストは最大約2行になるようにパディングします。

ナレーション/音声への同期

トランスクリプトはすでに正確な音声のタイミングを持っています。その同じ音声をコンポジションの下に配置し、タイムスタンプを音声のタイムベースに保ちます — キャプションと音声は手動で調整することなくロックされます。VOが再録音された場合は、再書き起こしを行います。オフセットを手動でシフトしないでください。

焼き込み vs サイドカーSRT/VTT

焼き込み(オープン)サイドカーSRT/VTT(クローズド)
場所MP4内のピクセル別の.srt/.vttファイル
ソーシャル(TikTok/Reels)必須 — 保証、スタイル可能プラットフォームで無視されることが多い
アクセシビリティ(ADA/WCAG)単独では満たさない必須(切り替え可能)
ベストプラクティスソーシャル用にアニメーションキャプションを焼き込むWeb/SEO用にサイドカーも同梱する

両方を出力します: ソーシャル用にアニメーションキャプションを焼き込み、アクセシブル/SEO再生用に同じトークンからプレーンなSRT/VTTを書き出します。

出力チェックリスト

  • 単語レベルのタイムスタンプ(リアル、書き起こしから) — 均等分割の偽装なし。
  • マイクロオーバーシュートを伴う単語ごとのポップイン、アクティブワードには1つのアクセントカラー。
  • 1ページあたり1~4単語、テキストの壁は決して作らない。
  • 太字サンセリフ、56~80px、2~6pxストローク+シャドウ、高コントラスト。
  • 中央バンド、下部約280pxと側面を空ける。
  • コンポジションの下に音声を配置、タイムスタンプは音声タイムベース。
  • ソーシャル用に焼き込み、アクセシビリティ用にサイドカーSRT/VTTを同梱。

納品と検証(レンダリング静止画 → MP4)

パッケージ化されたヘルパー(scripts/): scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png で静止画をタイル表示し、scripts/probe-mp4.sh out.mp4 [WxH] [fps] でエンコードを検証します。scripts/README.md を参照してください。

キャプションはRemotionコンポジション(<Composition> + zod schema + defaultProps)として出荷されます — すべての単語の動きは useCurrentFrame() によってフレーム駆動され、Date.now() / Math.random() / タイマーは使用しません。成果物 = out/*.mp4(焼き込み)+ プロジェクト + サイドカーSRT/VTT。デフォルトは9:16縦型(1080×1920)。

検証ループ — 静止画レンダリング → 検査 → エンコード。 単語のタイミングが壊れやすい部分です。エンコードする前に正確なフレームで確認してください。

# 開始 / サンプリングされたアクティブワードフレーム / 終了の静止画 — 出荷プロパティ(実際のトークン+音声)を使用
npx remotion still Captions out/f-start.png --frame=0   --props='{"captionsSrc":"vo.json"}'
npx remotion still Captions out/f-mid.png   --frame=90  --props='{"captionsSrc":"vo.json"}'
npx remotion still Captions out/f-end.png   --frame=N   --props='{"captionsSrc":"vo.json"}'  # N = durationInFrames-1

# 各PNGを検査:
#  - フレーム90でハイライトされている単語は、その[startMs,endMs]が90/fpsを含む単語であること(ずれなし)
#  - 焼き込みが判読可能: 太字サンセリフ、ストローク+シャドウが保持され、クリッピングがないこと
#  - 9:16安全領域: キャプションが中央バンドにあり、上部約12%と下部約20~35%(キャプション/CTA/音声UI)および右側アクションレールを空けていること

npx remotion render Captions out/captions.mp4 --props='{"captionsSrc":"vo.json"}'   # 静止画が正しくなったらエンコード
npx remotion render Captions out/demo.gif --codec=gif                                # README証明クリップ

npx remotion compositions を使用して durationInFrames/fps を読み取り、アクティブワードフレームと最終フレームを選択します。

終了前に:

  1. フレーム0、中間のアクティブワードフレーム、最終フレームで静止画がクリーンにレンダリングされること — フォント/音声の欠落なし。
  2. サンプリングされたフレームで正しい単語がハイライトされていること(フレーム/fpsがそのトークン内にある)、均等分割の偽装なし。
  3. 焼き込みが判読可能(ストローク+シャドウ)で、キャプションがチェックされたすべてのフレームで9:16安全領域内に完全に収まっていること。
  4. フレーム駆動のみ — Date.now() / Math.random() / タイマーなし。
  5. 出荷プロパティ(実際のトークン、defaultPropsだけでなく)が正しくレンダリングされること。MP4 + サイドカーSRT/VTTが出力され、GIFはオプション。

参考ファイル

  • references/word-timed-captions.md — エンドツーエンドの構築: Whisper書き起こしとCaption型、完全なSRTパーサー、アクティブハイライト付きRemotion単語タイミングコンポーネント、手動ページング、SRT/VTT出力、プラットフォームごとの安全領域マップ、および可読性タイポグラフィ仕様シート。

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

関連スキル