wjs-overlaying-video について
wjs-overlaying-videoは、動画クリップにカバー、字幕、イラスト、CTAを加える後処理スキルです。完成済み動画や分割スキルから受け取った素材をHyperFramesで合成し、最終エンコード1回で出力します。
主な機能
- 01カバー、字幕、章表示、CTAの追加
- 02HTML/CSSによる字幕表示
- 03指定箇所へのイラストやモーショングラフィックスの配置
- 04オーバーレイを単一のHyperFramesプロジェクトに合成
- 05最終エンコード1回で動画を出力
活用シーン
- 01カット済みクリップから投稿用動画を作成
- 02単語ハイライト字幕や章カードを追加
- 03冒頭の見どころや終盤にグラフィックとCTAを追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jianshuo/ claude-skills --skill wjs-overlaying-videoインストール後、Claude Code で/wjs-overlaying-videoを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/build_hf_clips.pycustom_overlay_recipes.mdexample_spec.jsonillustration_patterns.mdillustrations.pyscripts/scaffold.py詳細
wjs-overlaying-video
動画クリップのポストプロダクション:カバー、字幕、イラスト、CTA、 カスタムモーショングラフィックスを、すべて1つのHyperFramesプロジェクトで構成し、 1回の最終エンコードでレンダリングします。デコード/再エンコードの 連続処理は行いません(処理のたびに品質が低下し、時間もかかります)。
使用する場面
/wjs-segmenting-videoの後工程 — セグメント化スキルから、トリミング済みクリップとクリップごとのSRTが渡されます。このスキルで、カバー/字幕/イラスト/CTAを加えた、アップロード可能なMP4に仕上げます。- 完成した動画にモーショングラフィックスを加えたい場合:オープニングのフック、重要な引用のコールアウト、締めのスローガン、チャプターカード、最初のフレームに使うAI生成カバー。
- 動画にHTML/CSS品質の字幕を加えたい場合(単語ごとの動的なハイライト、カスタムフォント、大きな縁取り文字、キューごとのシーク)。
- 特定のフック場面にイラストを重ねたい場合:図解、強調する大きな文字、フローチャート。
使用しない場面:
- 長い動画をクリップに分割する場合 →
/wjs-segmenting-videoを使用してください。 - 元となるSRTを作成する場合 →
/wjs-transcribing-audioを使用してください(別の言語が必要な場合は、その後に/wjs-translating-subtitlesを使用してください)。 - 元動画が固定されていないHyperFramesの本格的な制作 →
hyperframesを直接使用してください。 - 微信视频号/抖音へのアップロード(公開APIがありません) → このスキルでMP4を作成し、手動でアップロードします。
このスキルでできること/できないこと
| できること | できないこと |
|---|---|
| 動画クリップに重ねるすべての要素:カバー、字幕、チャプター、イラスト、CTA | 動画のカット/トリミング(その場合は /wjs-segmenting-video + /wjs-reframing-video を使用) |
| クリップごとに1つのHyperFramesコンポジションを作成し、1回の最終エンコードを実行 | デコード/エンコードを複数回繰り返す処理 |
cover は出力の文字どおり最初のフレームです(プラットフォームがサムネイルとして自動選択します) | ユーザーが別途アップロードするサムネイルファイル |
字幕はHTML/CSSです — 何に重ねても白文字を読みやすくするための -webkit-text-stroke | libassによる焼き込み(非推奨) |
イラスト:再利用可能な stack/hammer パターンと、カスタム対応の拡張手段 | 再利用せず、イラストごとに個別のHTML/CSSを作成 |
| ターゲットのネイティブアスペクト比でAIカバーを再生成(縦向きは1024×1792、横向きは1536×1024) | プラットフォーム上で余白が追加されたり、切り抜かれたりする、1024×1536の単一デフォルト |
パイプライン
clip.mp4 + clip.zh-CN.burn.srt (from /wjs-segmenting-video hand-off)
↓
1. (Optional) Generate AI cover via gpt-image-2
make_cover.py --segments S.json --out output/ --size 1024x1792
cover_NN_slug.png
2. Scaffold a HyperFrames project per clip
hf_clip_NN/1080/{index.html, clip.mp4, cover.png, captions.json}
3. Compose: cover scene + body video + caption track + chapter chip
+ 1-2 illustrations at hook moments + CTA scene
4. npm run check (lint + validate + visual inspect)
npm run render → upload-ready MP4
縦向きの1080×1920、2分間のコンポジションは、MシリーズのMacで約2~3分でレンダリングされます。
色:合成前にHLG/HDRソースをSDRにトーンマッピングする
トーンマッピングするのは、実際にHLG/HDRのソースだけです。 ボディクリップがすでにRec.709 SDRの場合(グレーディング済みのマルチカムレンダーや、S-Log3→709 LUTが適用済みのpolysync出力など)、HLGのトーンマッピング手順を適用すると、正しい色が白っぽくなったり暗くなったりします。build_hf_clips.py の tonemap_to_sdr は color_transfer(_is_hlg_hdr)を調べるようになりました。HLG/PQならトーンマッピングし、それ以外ならトーンマッピングなしで高密度キーフレームの再エンコードを行います。どちらの場合も、HyperFramesに必要な -g 30 の高密度キーフレームエンコードが適用されます。
iPhoneや最新のカメラで撮影した映像は、HLG HDR(bt2020 / arib-std-b67)であることがよくあります。そのままHyperFramesに入力すると、白っぽく(「发白」)レンダリングされるか、安易に --sdr を指定した場合は暗く(「发黑」)なります。また、HDRのx265パスではレンダラーがハングすることがあります。合成前にボディクリップを、zscaleでトーンマッピングした30fpsのh264 SDR(bt709)に変換してください。
検証済みの手順(build_hf_clips.py の tonemap_to_sdr())。npl=203 はmacOSネイティブ(qlmanage)のリファレンスと明るさを合わせ、hable はコントラストを維持します。元の見た目(自然な肌、植物、レンガ)を保ち、白っぽさや暗さが生じません。
# zscale-capable ffmpeg — Homebrew's lacks zscale/tonemap.
# imageio-ffmpeg ships one: .../imageio_ffmpeg/binaries/ffmpeg-macos-aarch64-v7.1
TONEMAP_VF = ("zscale=tin=arib-std-b67:min=bt2020nc:pin=bt2020:t=linear:npl=203,"
"format=gbrpf32le,tonemap=tonemap=hable:desat=0,"
"zscale=t=bt709:m=bt709:p=bt709:r=tv,format=yuv420p,fps=30")
# encode: libx264 -crf 18 -color_primaries/-trc/-colorspace bt709
# -g 30 -keyint_min 30 -movflags +faststart ← see gotcha below
高密度キーフレームに関する注意点。 HyperFramesはボディ動画をフレームごとにシークします。キーフレームが疎な(GOPが長い)クリップでは、古いフレームで停止することがあります。レンダーログに Video "video" has sparse keyframes という警告が表示されます。シークがすべて正しく行われるよう、SDRクリップは必ず -g 30 -keyint_min 30(1秒ごとにキーフレームを1つ)でエンコードしてください。
確認: レンダーログに No HDR sources detected — rendering SDR と表示されることを確認してください。HDRが検出されたと表示される場合、クリップはトーンマッピングされていません。まずその問題を修正してください。
バージョンスタンプ(すべての出力)
END/CTAシーンの表示中に、右下に 「skill名字 + 版本号」 をスタンプし、各レンダーを生成したパイプラインのバージョンを追跡できるようにします。パイプラインを変更するたびに、build_hf_clips.py の VERSION を更新してください。
#ver-stamp { position: absolute; right: 28px; bottom: 28px; z-index: 30;
font-size: 20px; color: rgba(150,150,156,0.55); letter-spacing: 0.06em; }
<div id="ver-stamp" class="clip" data-start="{cta_start}" data-duration="{cta_dur}"
data-track-index="2">wjs-overlaying-video v1.3</div>
標準オーバーレイの種類(6つの基本要素)
各クリップの最終的な合成は、これらをいくつか組み合わせて作成します。エージェントがクリップごとに適切なものを選びます。ポッドキャストのハイライトでは通常6つすべてを使い、注釈オーバーレイを1つだけ表示する場合は1~2個だけ使います。
1. cover — フレーム全体を使ったAI画像を最初のフレームにする
カバーは最初のフレームそのものです(アニメーションなし、ズームなし)。そのため、最初のフレームをサムネイルとして自動選択するプラットフォームでは、デザインしたカバーがデフォルトで使われます。必ず ffmpeg -ss 0 -vframes 1 で確認してください — フレーム 0 が黒だと、プラットフォームのサムネイルも黒くなります。
HTML:
<div id="cover" class="clip" data-start="0" data-duration="1.6"
data-track-index="1" data-layout-allow-overflow>
<img src="cover.png" alt="" data-layout-allow-overflow />
</div>
CSS:
#cover { position: absolute; inset: 0; background: #0c0d10; overflow: hidden; }
#cover img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
生成: /wjs-segmenting-video/scripts/make_cover.py を使用します
(中間フレームを参照画像として gpt-image-2 images edit を実行します):
# For 1080×1920 vertical output (视频号 / 抖音):
make_cover.py --segments S.json --out output/ --size 1024x1792 [--single N]
# For 1920×1080 horizontal output (YouTube / B站):
make_cover.py --segments S.json --out output/ --size 1536x1024
アスペクト比は出力フレームに合わせる必要があります。 --size 1024x1536(2:3、スクリプトのデフォルト値)は、9:16 の出力ではレターボックス化またはクロップされます。縦向きの場合は、必ず 1024x1792 を指定してください。カバー画像のアスペクト比が視聴者にフレーム全体で表示されるため、不一致は目立ちます。--single N で 1 枚だけ再生成してください。codex プロバイダーはバッチの途中で一時的に失敗することがあります。
Codex の認証が必要です:スクリプトは gpt-image-2-skill 経由で codex CLI を呼び出します。~/.codex/auth.json が存在しない場合、スクリプトはエラーになります。設定方法については gpt-image-2-skill を参照してください。
参照フレームは OUTPUT の向きに合わせる必要があります。 make_cover は output/frame_NN_slug.jpg を読み込み、保持する写真の背景として使用します。横向きの2人用素材から作成した縦向きクリップの場合、デフォルトの frame_NN は横向きの2ショットです。これを 1024x1792 のカバーに渡すと、2人が縦長の画面に不自然に詰め込まれます。まず、すでにクロップ済みの本編クリップから縦向きの話者1人のフレームを切り出し、frame_NN_slug.jpg と置き換えてから、make_cover を実行してください
(ffmpeg -ss <t> -i clip_vert.mp4 -frames:v 1 frame_NN_slug.jpg)。これでカバーの構図が本編と一致します。
タイトルを焼き込んだカバー ⇒ アニメーション付きの #hook オープナーを削除します。 make_cover はセグメントのタイトルをカバー画像に焼き込みます(白の塗りつぶし+太い黒の縁取り、顔にかからない位置)。このカバー自体がタイトルカードです。タイトルを二重に表示することになるため、アニメーション付きの #hook オープナーを重ねて実行しないでください(下記のオーバーレイタイプを参照)。どちらか一方を選んでください。make_cover でタイトルを焼き込んだカバーを使う場合は HOOK を空にするか、通常のビデオフレームのカバーとアニメーション付きの hook を組み合わせます。ユーザーが承認した標準設定は、make_cover でタイトルを焼き込んだカバー(AI による描画を加えず、タイトルを映像に焼き込んだクリーンなビデオフレーム)です。
2. caption — キーワード強調キャプション(字幕スタイル 03)、SRT に同期
王建硕に選ばれたスタイル(ユーザー承認済み):字幕スタイル 03「キーワード強調」+ 思源宋体 Noto Serif SC。 白いセリフ体の文字に黒い縁取りを付け、インパクトのある定量的なキーワード (倍数/大きな数量/パーセンテージ)を小さなゴールドのグラデーションブロックで囲みます。 キャプションは固定ゾーン内で垂直方向に中央揃えされるため、1 行と 2 行の キューが切り替わっても視覚的な中心が上下に跳ねません。
候補スタイルは 4 種類(縁取り白文字/質感のある帯/キーワード強調/1 文字ずつ点灯)でした。
ユーザーが03 キーワード強調とセリフ体 scフォントを選びました。これを使用してください。
通常の縁取りスタイル(-webkit-text-stroke: 5px #000、ゴールドブロックなし、サンセリフ体)は、
強調する定量的なキーワードがないクリップ用のフォールバックです。
フォント — <head> で Google Fonts から Noto Serif SC を読み込みます(
HyperFrames コンパイラーは指定された Google Fonts のファミリーを自動的に取得してインライン化します。
レンダリングログに Fetched … Noto Serif SC と表示されることを確認してください):
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@600;700;900&display=swap" rel="stylesheet">
HTML:
<div id="caption" class="clip" data-start="{body_start}"
data-duration="{body_dur}" data-track-index="4"></div>
CSS(縦型 1080×1920)— 字幕スタイル 03:
#caption {
position: absolute; left: 0; right: 0; bottom: 240px;
height: 240px; z-index: 10; overflow: visible;
}
#caption .bubble {
position: absolute; top: 50%; left: 50%;
display: inline-block; padding: 0 24px;
font-family: "Noto Serif SC", "Songti SC", "STSong", serif;
font-size: 52px; line-height: 1.32; font-weight: 700;
color: #fff; max-width: 980px; text-align: center;
-webkit-text-stroke: 2.5px rgba(0,0,0,0.9);
paint-order: stroke fill;
text-shadow: 0 2px 8px rgba(0,0,0,0.7), 0 0 2px rgba(0,0,0,0.9);
letter-spacing: 0.01em;
}
#caption .bubble .hot { /* gold keyword block */
color: #1a1206; -webkit-text-stroke: 0;
background: linear-gradient(180deg, #f3c877, #c79655);
padding: 2px 12px; border-radius: 9px; margin: 0 3px;
box-shadow: 0 3px 10px -3px rgba(232,176,99,0.6);
}
キーワードの自動選択(意図的に少なめ)。 ゴールドのブロックが意味のあるものとなり、
煩雑にならないよう、本当に強調すべき数量だけを囲みます。
一般的な「个」/「年」(「一个」、「20年」など)は意図的に除外します。千位区切りのカンマ
(「1,000万」)にも対応します。build_hf_clips.py の mark_keywords() で処理します:
_NUM = r"[0-90-9,,一二三四五六七八九十百千两零几]+"
_HOT_RE = re.compile(rf"(?:翻了?{_NUM}?[倍番]|{_NUM}\s*(?:[倍番]|万亿?|亿|%|%))")
# → highlights: 一倍 五六倍 十倍 10倍 50万 800万 1,000万 50% 翻一倍
# render the cue with b.innerHTML = g.html (HTML-escape the non-keyword text)
JS(キューごとに吹き出し 1 つ+GSAP でフェードイン/アウト、すべてコンテナーの中点に配置):
// SRT cues are loaded as inline JSON. Each cue's start/end is offset
// by the cover-scene duration (e.g., 1.5s) so the timing aligns with
// the composition timeline (not the body's own t=0).
const captionEl = document.getElementById("caption");
const groups = JSON.parse(document.getElementById("captions-data").textContent);
const bubbles = groups.map((g, i) => {
const b = document.createElement("span");
b.className = "bubble"; b.id = "cap-" + i;
b.innerHTML = g.html || g.text; // g.html has <span class="hot"> keyword blocks
b.style.opacity = "0";
captionEl.appendChild(b);
return b;
});
// GSAP xPercent/yPercent for centering (CSS transform would get
// overwritten the moment we tween y).
gsap.set(bubbles, { xPercent: -50, yPercent: -50 });
groups.forEach((g, i) => {
const el = bubbles[i];
tl.fromTo(el, { opacity: 0, y: 12 }, { opacity: 1, y: 0, duration: 0.18, ease: "power2.out" }, g.start);
const exitStart = Math.max(g.start + 0.18, g.end - 0.12);
tl.to(el, { opacity: 0, duration: 0.12, ease: "power2.in" }, exitStart);
tl.set(el, { opacity: 0 }, g.end);
});
ソース SRT — インライン化する前に切り出して時間をずらします。 /wjs-transcribing-audio で
作成した単語タイミング付きの .asr.srt を使用してください(火山ストリーミング
ASR → build_srt_from_asr.py)。単語ごとのタイミングにより、ずれなくキューを発話音声に
正確に合わせられます。各キューを解析し、すべての start/end にカバーの再生時間を加え、
mark_keywords() を実行して html フィールドを生成し、JSON として
<script id="captions-data" type="application/json"> ブロックにインライン化します。
MarginV/位置に関する注意事項:
- 縦型(1080×1920):
bottom: 240pxにすると、字幕が视频号/抖音の下部 UI オーバーレイ(いいね/コメント/共有ボタン)に重なりません。 - 横型(1920×1080):
bottom: 100px、font-size: 48px、-webkit-text-stroke: 4pxが妥当なデフォルト値です。
キャプションの長さ制限。 1080 ピクセル幅で 56px の場合、1 つのキューが約 18 文字を超えると、
不自然に 2 行に折り返されます。これは上流で対処してください。/wjs-translating-subtitles では、
単語間の空白で分割し、続けて句読点で分割して、キューを約 18 文字以内に収める必要があります。
それより長いキューを受け取った場合は、font-size を 48px に下げるか、折り返しを許容してください。
3. chapter — 左上のチャプターチップ(4 秒表示後にフェード)
セグメントを示す控えめなバッジです。本文の開始時に表示し、数秒後にフェードアウトするため、 他の要素と競合しません。
HTML:
<div id="chapter" class="clip" data-start="{body_start}"
data-duration="{body_dur}" data-track-index="3">
<span class="dot"></span>
<span class="text">第一段 · 自然语言才是新代码</span>
</div>
CSS:
#chapter {
position: absolute; top: 80px; left: 60px; z-index: 9;
display: inline-flex; align-items: center; gap: 12px;
padding: 12px 20px;
background: rgba(12,13,16,0.78);
border: 1px solid rgba(199,150,85,0.4);
border-radius: 999px;
}
#chapter .dot { width: 10px; height: 10px; border-radius: 999px; background: #e8b063; }
#chapter .text {
font-size: 24px; color: #f4f4f5; letter-spacing: 0.04em; font-weight: 600;
}
GSAP:
tl.from("#chapter", { x: -40, opacity: 0, duration: 0.5, ease: "expo.out" }, body_start + 0.4);
tl.to("#chapter", { opacity: 0, duration: 0.4, ease: "power2.in" }, body_start + 4.0);
4. stack イラスト — 右上の縦型リストカード
項目の一覧(例:言語の階層、ワークフローの手順、レベル)を、 右上の暗いカードに表示します。関連するレベル/手順を強調するため、 1 つの項目をアンバーで強調できます。
用途: 話者が説明している間、階層やリストを表示する場合に使用します。 カードは 8〜50 秒間表示します。
HTML:
<div id="ill-stack" class="clip" data-start="{start}" data-duration="{dur}" data-track-index="5">
<div class="ill-card">
<div class="ill-card-label">我们写的层级</div>
<div class="ill-row"><span class="ill-tag accent">自然语言</span></div>
<div class="ill-row"><span class="ill-tag">Python</span></div>
<div class="ill-row"><span class="ill-tag">C</span></div>
<div class="ill-row"><span class="ill-tag">Assembly</span></div>
</div>
</div>
CSS:(正式版 CSS 全体は references/illustration_patterns.md を参照してください。内容をそのままコピーしてください)
GSAP — 右からスライドイン+行を順番に表示:
tl.fromTo("#ill-stack", { x: 360, opacity: 0 }, { x: 0, opacity: 1, duration: 0.6, ease: "expo.out" }, start + 0.2);
tl.from("#ill-stack .ill-row", { y: 20, opacity: 0, duration: 0.4, stagger: 0.12, ease: "power2.out" }, start + 0.4);
tl.to("#ill-stack", { x: 360, opacity: 0, duration: 0.5, ease: "power2.in" }, end - 0.5);
5. hammer イラスト — 画面中央に大きな数式/テキストを重ねて表示
重要な主張を視覚的に「強く印象づける」、画面中央の大きなテキスト/数式です。 クリップ内で最も引用価値のある瞬間に最適です(例: 「LLM = 编译器」、「Token = 新 GDP」、「AI ≠ 更快的轿子」)。4〜8 秒間表示します。
HTML:
<div id="ill-hammer" class="clip" data-start="{start}" data-duration="{dur}" data-track-index="6">
<div class="ill-h-content">
<div class="ill-h-eq">
<span class="ill-h-left">LLM</span>
<span class="ill-h-equals">=</span>
<span class="ill-h-right">新编译器</span>
</div>
<div class="ill-h-foot">自然语言 → Python → 汇编</div>
</div>
</div>
GSAP — スケールポップで登場+各パーツを順番に表示+スケールしながらフェードアウト:
tl.fromTo("#ill-hammer", { scale: 0.85, opacity: 0 },
{ scale: 1.0, opacity: 1, duration: 0.45, ease: "back.out(1.6)" }, start);
tl.from("#ill-hammer .ill-h-left", { x: -40, opacity: 0, duration: 0.4, ease: "expo.out" }, start + 0.2);
tl.from("#ill-hammer .ill-h-equals", { scale: 0, opacity: 0, duration: 0.4, ease: "back.out(2)" }, start + 0.4);
tl.from("#ill-hammer .ill-h-right", { x: 40, opacity: 0, duration: 0.4, ease: "expo.out" }, start + 0.6);
tl.from("#ill-hammer .ill-h-foot", { y: 20, opacity: 0, duration: 0.4, ease: "power2.out" }, start + 0.8);
tl.to("#ill-hammer", { scale: 1.05, opacity: 0, duration: 0.45, ease: "power2.in" }, end - 0.45);
(正式版 CSS 全体は references/illustration_patterns.md を参照してください)
6. cta — チャンネルの CTA を含むエンドカード
最後の 3 秒間に表示する、ブランドを反映したアウトロです。チャンネル名には (全体の指示に従って)王建硕を使用してください。CTA 欄にゲストの名前を 入れてはいけません。
HTML:
<div id="cta" class="clip" data-start="{cta_start}" data-duration="3.24" data-track-index="1">
<div class="cta-line-1">关注王建硕</div>
<div class="arrow">↓</div>
<div class="cta-line-2">微信公众号 · 视频号</div>
<div class="cta-foot">聊 AI · 聊创业 · 持续更新</div>
</div>
CSS/GSAP: references/illustration_patterns.md を参照してください。
レガシータイプ(単一動画に一度だけオーバーレイを適用する場合)
spec.json + scaffold.py のワークフローでは、以下の旧式オーバーレイタイプもサポートしています。上記の完全なポストプロダクションワークフローを経ずに、既存の動画を1本だけ装飾したい場合に便利です。
quote— 全幅のキネティックタイポグラフィ。上部または下部にグラデーションを表示します。 オープニングのフックや重要な引用のコールアウトに最適です。slogan—position: bottomと大きめの文字を指定したquoteの別名です。 クロージングのスローガンに最適です。callout— 隅に表示する小さな注釈パネルです。チャプターラベル、ローワーサード、「掲載実績」などの注記に最適です。custom— 自由にカスタマイズできます。Claude がoverlays/<name>.htmlフラグメントファイル内にオーバーレイの HTML/CSS/GSAP を記述します。references/custom_overlay_recipes.mdを参照してください。
ワークフロー A — セグメンテーション後のプリセット(最も一般的)
/wjs-segmenting-video から直接作業を始めていて、各クリップに標準の cover + caption + chapter + illustrations + CTA の処理を適用したい場合に使用します。
ステップ 1 — 適切なアスペクト比で AI カバーを生成する
# For vertical 9:16 output (视频号 / 抖音):
python3 ~/.claude/skills/wjs-segmenting-video/scripts/make_cover.py \
--segments segments.json --out output/ --size 1024x1792 --single 1
# Verify segment 1's cover; then batch:
python3 ~/.claude/skills/wjs-segmenting-video/scripts/make_cover.py \
--segments segments.json --out output/ --size 1024x1792
ステップ 2 — 各クリップ用の HyperFrames プロジェクトをスキャフォールドする
hf_clip_NN/1080/ に以下を配置します。
index.html— コンポジション(テンプレートから作成。references/post_segmentation_template.htmlを参照)clip.mp4—output/clip_NN_slug.mp4からコピーcover.png—output/cover_NN_slug.pngからコピーcaptions.json—output/clip_NN_slug.zh-CN.burn.srtから生成。 各キューの開始時刻と終了時刻に +cover_duration を加算し、字幕を本文独自のタイムラインではなく、コンポジションのタイムラインに合わせます。
references/build_hf_clips.py のビルドスクリプトを使うと、すべてのセグメントを一度に処理できます。segments.json、ILLUSTRATIONS 辞書(クリップごとのイラスト。ステップ 3 を参照)、テンプレートを読み込み、レンダリング可能なプロジェクトを5つ出力します。
ステップ 3 — クリップごとにイラストを定義する
各クリップについて、フックとなる場面を1~2つ特定し、stack または hammer を選択します。
ILLUSTRATIONS = {
1: [
# The language hierarchy as a stack card during the opening
{"key": "stack", "pattern": "stack", "body_start": 0.3, "body_end": 9.0,
"label": "我们写的层级",
"rows": [
{"text": "自然语言", "accent": True},
{"text": "Python", "accent": False},
{"text": "C", "accent": False},
{"text": "Assembly", "accent": False},
]},
# The hammer at the most quotable moment
{"key": "hammer", "pattern": "hammer", "body_start": 10.8, "body_end": 14.6,
"left": "LLM", "equals": "=", "right": "新编译器",
"foot": "自然语言 → Python → 汇编"},
],
# ... clips 2-5
}
タイムスタンプは本文基準(カバーシーンの再生時間の後)です。ビルドスクリプトは GSAP の位置を出力する際にカバー分のオフセットを加算します。
ステップ 4 — ビルドしてレンダリングする
python3 references/build_hf_clips.py # scaffolds all projects
for n in 01 02 03 04 05; do
cd "hf_clip_$n/1080"
npx hyperframes lint
npx hyperframes validate
npx hyperframes render
cd ../..
done
2:30 のクリップは約3分でレンダリングされます。出力先:hf_clip_NN/1080/renders/*.mp4。
ワークフロー B — 単一動画にカスタムオーバーレイを適用する(レガシー spec.json)
既存の動画が1本あり、タイトルカードや注釈、ローワーサードなどのアドホックなオーバーレイをいくつか追加したい場合に使用します。
spec.json のスキーマ
{
"source_video": "../path/to/source.mp4",
"duration": 135.4,
"size": "1920x1080",
"name": "clip_01_animated",
"overlays": [
{"id": "o1", "type": "quote", "start": 8.0, "duration": 6.0,
"position": "top", "lines": ["代码不存在错误", "只存在意图错配"],
"accent": [false, true]},
{"id": "o2", "type": "callout", "start": 30.0, "duration": 5.0,
"anchor": "top-right", "text": "FRP 概念"},
{"id": "o3", "type": "slogan", "start": 122.0, "duration": 13.4,
"lines": ["改 prompt", "不改 AI 生成的代码"], "accent": [false, true]}
]
}
| フィールド | 必須 | 備考 |
|---|---|---|
source_video | はい | ソース MP4 へのパス。プロジェクト内に source.mp4 としてシンボリックリンクが作成されます。 |
duration | はい | コンポジション全体の長さ(秒)。ソース動画に合わせてください。 |
size | いいえ | WIDTHxHEIGHT(デフォルトは 1920x1080)。 |
overlays[].type | はい | quote、slogan、callout、または custom。 |
overlays[].start | はい | 開始時刻(秒)。 |
overlays[].duration | はい | オーバーレイの表示時間。 |
スキャフォールドとレンダリング
python3 ~/.claude/skills/wjs-overlaying-video/scripts/scaffold.py spec.json
cd <name> && npm run check && npm run render
出力チェックリスト
クリップの完成とする前に、以下を確認してください。
- フレーム 0 がカバーになっている(黒画面ではない) —
ffmpeg -ss 0 -vframes 1 out.mp4 - 字幕が音声と同期している(音声を再生しながら数秒分を確認)
- すべてのイラストが、対応する発話のタイミングに合わせて表示・終了している
- CTA が正しくレンダリングされている(ゲストの名前ではなく
关注王建硕) -
npx hyperframes lint && npx hyperframes validateの両方に合格する -
npx hyperframes inspectでレイアウトのはみ出しがない - 全体の長さがソースクリップ、カバー、CTA の各再生時間の合計と一致している
よくある間違い
- カバーのアスペクト比 ≠ 出力のアスペクト比です。
1024x1536(make_cover.pyのデフォルトサイズ)は 2:3 で、9:16 の出力ではレターボックス表示になるか、クロップされます。縦向きの場合は必ず--size 1024x1792を指定してください。 - 字幕の配置は行数によってずれます。 高さを固定したコンテナ内で中央を基準に配置(translate(-50%, -50%))すると、1 行と 2 行の字幕の視覚的な中央位置がそろいます。下端を基準に配置しないでください(上方向に広がります)。
- GSAP は CSS の transform による中央配置を上書きします。 CSS で
transform: translate(-50%, -50%)を設定してからyをトゥイーンすると、GSAP が transform を置き換え、中央配置が崩れます。代わりにgsap.set(el, { xPercent: -50, yPercent: -50 })を使ってください。こうすると、xPercent/yPercent が以降の y/x のトゥイーンと組み合わされます。 - HTML/CSS の字幕の上に libass の字幕を焼き付けること。 出力動画ごとに字幕システムは 1 つだけ選んでください。このスキルの HTML/CSS 字幕を使う場合は、
/wjs-segmenting-videoで字幕を焼き付けないでください。ハンドオフパッケージを使って未加工のクリップをリクエストしてください。 - フレーム 0 が黒くなります。 カバーシーンの不透明度が 0 からフェードインする場合、最初のフレームは文字どおり黒くなり、プラットフォームのサムネイルも黒くなります。カバーは静止状態で配置し(不透明度のトゥイーンを使わず)、
ffmpeg -ss 0 -vframes 1で確認してください。 - CTA のチャンネル名はゲストの名前です。 必ず
王建硕を使ってください。ゲスト名はメタデータ内の説明文に記載し、画面上の CTA には表示しないでください。 - アスペクト比が合わず、object-fit: cover によってカバー画像がクロップされること。 正しいアスペクト比でカバーを再生成する(ステップ 1 を参照)か、
object-fit: containと暗い背景を使ってレターボックス表示にしてください。
他のスキルとの連携
/wjs-segmenting-video— 通常はこれが前段のスキルです。カット、クロップ、SRT の分割が完了した後、このスキルで処理します。ハンドオフパッケージはclip_NN.mp4、clip_NN.zh-CN.burn.srt、segments.jsonです。/wjs-transcribing-audio+/wjs-translating-subtitles— SRT がない場合は、先にこれらを実行してください。正確な字幕タイミングには、単語単位の Whisper または Volcano/豆包 ASR の出力が推奨されます。hyperframes— 基盤となる合成フレームワークです。このスキルは、実証済みのポストプロダクションパターンをまとめた薄いラッパーです。hyperframesスキルの内容はすべて適用されます(プレビュー、レンダリング、トランジション、音声連動など)。customオーバーレイを作成するときは、必ずこちらを読んでください。hyperframes-cli— プロジェクトで使用する CLI コマンドです(init、lint、validate、inspect、render)。gpt-image-2-skill— カバー生成ツールです。make_cover.pyは codex CLI 経由でこれを呼び出します。~/.codex/auth.jsonの codex 認証が必要です。/wjs-uploading-video— このスキルで MP4 を生成した後の後続スキルです。メタデータファイルのタイトル、説明、タグを使って、レンダリング結果を YouTube にアップロードします。
ファイルと参照資料
scripts/scaffold.py— ワークフロー B のスキャフォルダー(アドホックなオーバーレイ向けのレガシーspec.json)references/post_segmentation_template.html— ワークフロー A のテンプレート:プレースホルダーを置換して使う、標準的なcover + caption + chapter + illustration + CTAの合成構成references/build_hf_clips.py— ワークフロー A の複数クリップビルダー。segments.jsonとクリップごとのイラスト辞書を読み込み、クリップごとにプロジェクトを 1 つずつスキャフォールドして内容を設定しますreferences/illustration_patterns.md—stackおよびhammerのイラストパターンに関する標準的な CSS / GSAPreferences/custom_overlay_recipes.md— 再利用可能なcustomオーバーレイのレシピ(ターミナルデモ、レイヤースタック図、矢印付きコールアウト)references/example_spec.json— ワークフロー B のサンプル