mcpField
W
wjs-overlaying-video

wjs-overlaying-video

github.com/jianshuo创意与媒体137170 次安装今天更新
代码仓库

关于 wjs-overlaying-video

wjs-overlaying-video 是用于为视频片段添加封面、字幕、插图和 CTA 的后期制作技能,适合处理已剪好的视频或分段技能交付的素材。它将各类图层整合进 HyperFrames 合成,并通过一次最终编码输出成片。

主要功能

  1. 01添加封面、字幕、章节标记与 CTA
  2. 02使用 HTML/CSS 呈现字幕
  3. 03在指定时刻叠加插图和动态图形
  4. 04将图层合成为单个 HyperFrames 项目
  5. 05一次最终编码输出视频

适用场景

  1. 01为已剪好的片段制作可上传成片
  2. 02为视频添加逐词高亮字幕和章节卡
  3. 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.py
custom_overlay_recipes.md
example_spec.json
illustration_patterns.md
illustrations.py
scripts/
scaffold.py

详细介绍

wjs-overlaying-video

视频片段后期制作:封面、字幕、插图、CTA、自定义动态图形——全部在一个 HyperFrames 项目中合成,并通过一次最终编码渲染。无需反复解码和重新编码(每次级联处理都会降低画质并耗费时间)。

适用场景

  • 用于 /wjs-segmenting-video 之后——片段分割技能会为你提供裁剪后的片段和每个片段对应的 SRT;此技能会将它们制作为可直接上传的 MP4,并添加封面、字幕、插图和 CTA。
  • 用户已有完成的视频,并希望用动态图形加以美化:开场钩子、重点引用提示、结尾标语、章节卡片、作为首帧的 AI 生成封面。
  • 用户希望在视频上使用 HTML/CSS 质量的字幕(逐词动态高亮、自定义字体、大号描边文字、每条字幕均可定位)。
  • 用户希望在特定钩子时刻叠加插图——图表、重点大字、流程图。

不要用于:

  • 将一段长视频拆分成多个片段 → 使用 /wjs-segmenting-video。
  • 创建源 SRT → 使用 /wjs-transcribing-audio(如果需要其他语言,再使用 /wjs-translating-subtitles)。
  • 制作源素材不是固定视频的完整 HyperFrames 作品 → 直接使用 hyperframes。
  • 上传到微信视频号 / 抖音(没有公开 API)→ 此技能会生成 MP4;上传需手动完成。

此技能的功能与非功能

功能非功能
视频片段上叠加的所有内容:封面、字幕、章节、插图、CTA剪辑 / 裁剪视频(请使用 /wjs-segmenting-video + /wjs-reframing-video)
每个片段对应一个 HyperFrames 合成项目 = 一次最终编码多步骤的解码 / 编码级联处理
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

在 M 系列 Mac 上,渲染一段 2 分钟、竖屏 1080×1920 的合成视频约需 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 色调映射,将主体片段预转换为 SDR(bt709)30fps h264,然后再合成 SDR 片段。

已验证的方案(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。始终使用 -g 30 -keyint_min 30 编码 SDR 片段(每秒一个关键帧),确保每次定位都准确。

验证渲染日志是否显示 No HDR sources detected — rendering SDR。 如果显示检测到 HDR,说明你的片段尚未进行色调映射——请先修复这一点。

版本标记(每个输出)

在右下角标注 「skill名字 + 版本号」,并在 END/CTA 场景中显示, 这样每次渲染都能追溯到生成它的流水线版本。每次流水线变更时,都要更新 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 个构建模块)

每个片段的最终合成都由这些类型中的若干种组合而成。 Agent 会为每个片段选择合适的类型——播客高光片段通常会用到全部 6 种, 单个注释叠加则可能只用 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 重新生成一张;codex provider 可能会在批量生成过程中临时失败。

需要 Codex 身份验证:脚本通过 gpt-image-2-skill 调用 codex CLI。如果缺少 ~/.codex/auth.json,脚本会报错。设置方法请参阅 gpt-image-2-skill。

参考帧的方向必须与输出方向一致。make_cover 会读取 output/frame_NN_slug.jpg,并将其作为保留的摄影背景。对于由横向双人素材裁切而来的竖屏片段,默认的 frame_NN 是横向双人镜头——将其用于 1024x1792 封面会把两个人都挤进竖幅画面,显得很不协调。先将 frame_NN_slug.jpg 替换为从已裁切的主体片段中提取的竖屏单人画面 (ffmpeg -ss <t> -i clip_vert.mp4 -frames:v 1 frame_NN_slug.jpg),再运行 make_cover。这样封面就会与主体画面的构图一致。

封面已嵌入标题 ⇒ 移除动画 #hook 开场。make_cover 会将片段的标题印在封面图上(白色填充+粗黑描边,并避开人脸)。这张封面本身就是标题卡。不要再叠加动画 #hook 开场(下文的叠加类型)——否则标题会重复出现。二选一:使用嵌入标题的 make_cover 封面(此时将 HOOK 留空),或使用普通视频帧封面+动画 hook。用户认可的默认方案是make_cover 嵌入标题的封面(干净的视频帧上烧录标题,不使用 AI 绘制)。

2. caption — 与 SRT 同步的关键词高亮字幕(字幕风格 03)

为王建硕选定的样式(用户已确认):字幕风格 03「关键词高亮」+ 思源宋体 Noto Serif SC。 白色衬线文字带黑色描边,并将醒目的定量关键词(倍数 / 大数量级 / 百分比)置于小型金色渐变色块中。 字幕在固定区域内垂直居中(这样 1 行和 2 行字幕不会导致视觉中心上下跳动)。

共有 4 种候选样式(描边白字 / 质感底条 / 关键词高亮 / 逐字点亮); 用户选择了 03 关键词高亮和 serif sc 字体。使用此样式。 纯描边样式(-webkit-text-stroke: 5px #000,无金色块,使用无衬线字体)是没有定量关键词可高亮时的备用方案。

字体 — 在 <head> 中从 Google Fonts 加载 Noto Serif SC(HyperFrames 编译器会自动获取并内嵌请求的 Google 字体系列;请确认渲染日志显示 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(每条字幕提示一个气泡 + 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 时,如果单条字幕提示超过约 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 插图 — 右上角纵向列表卡片

在右上角的深色卡片中列出一组项目(例如语言层级、工作流步骤、级别)。其中一项可以用琥珀色突出显示,以强调相关层级/步骤。

**适用场景:**演讲者讲解时展示层级或列表。卡片保持显示 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 工作流也支持以下较旧的叠加层类型——如果你想为一个现有视频美化画面,而不采用上面的完整后期制作工作流,这些类型会很有用:

  • quote — 全宽动态排版,带顶部或底部渐变。适合开场钩子和重点引言提示。
  • slogan — quote 的别名,默认使用 position: bottom 和更大的字号。适合结尾口号。
  • 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)

如果你有一个现成视频,想添加几个临时叠加层(标题卡、注释、下三分之一字幕),请使用此工作流。

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 的变换居中设置。 如果在 CSS 中设置 transform: translate(-50%, -50%),然后再对 y 设置补间,GSAP 会替换该变换,导致居中失效。改用 gsap.set(el, { xPercent: -50, yPercent: -50 }),这样 xPercent/yPercent 会与后续的 y/x 补间组合生效。
  • 不要在 HTML/CSS 字幕上再叠加烧录的 libass 字幕。 每个输出视频只能选用一种字幕系统。如果使用本技能的 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 + 每个片段的插图字典,为每个片段搭建并填充一个项目
  • references/illustration_patterns.md——stack 和 hammer 插图模式的规范 CSS / GSAP
  • references/custom_overlay_recipes.md——可复用的 custom 叠加层方案(终端演示、图层堆叠图、带箭头的标注)
  • references/example_spec.json——工作流 B 示例

评论

支持 Markdown