Communitygithub.com

Kreesten-hsh/video

このスキルは、ユーザーが「アニメーションキャプション追加」「TikTokキャプション作成」「カラオケキャプション」「単語ごとの字幕」「自動字幕」「アクティブワードのハイライト」「動画へのキャプション焼き込み」「ナレーションとキャプションの同期」「SRT/文字起こしのアニメーションテキスト化」などを依頼した際に使用します。Whisperからの単語レベルのタイミング、単語ごとのポップイン/スケールイン、アクティブワードのハイライト、9:16セーフエリア配置、読みやすい書体、焼き込みとサイドカーSRT/VTTの出力に対応します。

video とは?

このスキルは、音声認識や文字起こしから単語レベルのタイミングを取得し、それを元にアニメーション付きキャプションを生成するための包括的なパイプラインを提供します。 主にTikTok、Reels、Shortsなどのショート動画向けに設計されており、単語が次々にポップインし、アクティブな単語がハイライトされることで、視聴者の注目を集め、視聴時間を延ばす効果が期待できます。 このスキルはRemotionやWeb(CSS)上で動作し、WhisperやAssemblyAIで文字起こしした単語レベルのタイムスタンプを正規化し、`@remotion/captions`ライブラリの`createTikTokStyleCaptions()`関数を使って適切なページ分割を行います。 最終的に、焼き込み済みのMP4ファイルと、アクセシビリティやSEOに対応したサイドカーのSRT/VTTファイルの両方を出力します。 また、スクリプトを使用したレンダリング検証プロセスも含まれており、単語のタイミングや表示品質を保証します。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Kreesten-hsh/video/tree/HEAD/.agents/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つのフラットなトークン形状に正規化し、レンダラーが単語の出所を気にする必要をなくします。

// キャプショントークン — 下流のすべてが消費する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度に1~4語を表示します。Remotionの createTikTokStyleCaptions() を使用するか(手動でグループ化)、combineTokensWithinMilliseconds を調整します。真の単語ごとの表示なら約200~500ms、短く読めるフレーズなら約1000~1500ms。

import { createTikTokStyleCaptions } from "@remotion/captions";
const { pages } = createTikTokStyleCaptions({
  captions, combineTokensWithinMilliseconds: 1200,       // 1ページあたり約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)

プロパティ値理由
フォントBold/ExtraBold サンセリフ(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. サンプリングされたフレームで正しい単語がハイライトされている(frame/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.

関連スキル