mcpbeat

Producing Video

sugarforever/producing-video

Turn a user-provided voiceover audio file + SRT subtitle into a finished, narration-synced MP4 using HyperFrames (HTML-to-video). The audio + SRT are the source of truth — scenes are timed to the SRT cues, content is read from the SRT, and the audio is muxed in automatically. Use when the user hands over an mp3/wav + srt and wants a video, says "把音频做成视频", "做一期视频", "audio + srt to video", "把这期早读做成视频", "render this narration into a video", or provides a recording + subtitles for an explainer / daily / 解读 / 口播. NOT for generating the voiceover (that's the user's job here) and NOT for slide decks (use a slides skill).

3k tokens
context cost
the whole folder, loaded on every use
2
files
ships runnable scripts
0
copies elsewhere
how many repositories repackaged it
130
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/sugarforever/01coder-agent-skills --skill producing-video

What comes with it

1 490 bytes besides the instruction
scripts/srt-cues.mjs

The instruction itself

10 sections, as written by the author

Producing-Video · 音频 + 字幕 → 成片

把"用户已经录好的口播音频 + SRT 字幕"做成一条画面跟着声音走的 MP4。用 HyperFrames(HTML 即视频)出片。

铁律:音频和 SRT 是唯一事实源。 画面的内容来自 SRT,画面的时间轴来自 SRT 的 cue 时间戳,音频作为一个 <audio> clip 直接挂进合成里、渲染时自动合流 —— 没有"先出视频再合音频"这一步

分工(很重要)

| 谁 | 做什么 |

|---|---|

| 用户 | 写稿 → 录音/合成音频 → 生成 SRT → 把 audio.mp3 + audio.srt 交给你 |

| 本 skill(你) | 选 frame/品牌 → 按 SRT 搭 HyperFrames 合成 → 校对 → 渲染成片 |

用户指望你生成配音(那是上游)。如果用户还没有音频、问的是"怎么配音",那不是本 skill —— TTS / 声音克隆是另一条线(见下方"超出范围")。

依赖检查(pre-flight)

npx hyperframes doctor      # 需要 Node ≥ 22 · FFmpeg · Chrome

需要 hyperframes / hyperframes-cli 两个 skill 在场(编写合成 + 跑 CLI)。缺了就让用户 npx hyperframes skills 安装后重来。

确认用户给了两个文件:音频(mp3/wav/m4a)+ SRT。只给音频没给 SRT → 本 skill 需要 SRT 拿时间轴;可让用户补 SRT(很多录音工具/剪辑软件能导出),不要默认去跑 Whisper 转写(用户没给 SRT 往往是有意的,先问)。


工作流

Step 1 · 选 frame / 品牌

画面的风格 = 一个 frame.md / visual-style / 既有系列品牌。三种来源,按情况选:

  • 延续系列品牌(推荐用于日更/系列)。如果这期属于一个已有系列(如"AI 早读"),去翻该系列的封面/历史成片,沿用同一套 token(颜色、字体、栅格、页眉页脚),让视频和封面一脉相承。
  • HyperFrames frame.md 模板。用户可能直接点名,例如 creative-mode / biennale-yellow / cobalt-grid。取 token:
  • curl -sSL https://www.hyperframes.dev/design/<slug>.md (站点是 JS 渲染,多半取不到正文);
  • 更靠谱:在本机 open-design 仓库里找 design-templates/*<slug>*/template.json,里面有精确的 palette / typography(hex + 字体名)。
  • 8 个内置 visual-style(Swiss Pulse / Velvet Standard / Shadow Cut / Maximalist 等)。在 hyperframes skill 的 visual-styles.md 里,直接抄 YAML token。

字体一律本地 woff2(见 Gotcha "字体")。中文必须配 Noto Sans SC(400/500/700/900 视用量);英文 display 按 frame 选(Archivo Black / Manrope / Oswald…);标签数字常用 JetBrains Mono。从 fontsource CDN 下到项目的 fonts/

curl -sSL -o fonts/<name>.woff2 "https://cdn.jsdelivr.net/fontsource/fonts/<family>@latest/<subset>-<weight>-normal.woff2"
# 中文:subset 用 chinese-simplified;拉丁:latin

Step 2 · 起项目

cd <repo>/studio/videos                       # 仓库约定:成片放这里
npx hyperframes init <YYYYMMDD-slug> --example blank --non-interactive
cd <YYYYMMDD-slug> && mkdir -p fonts audio
cp <user-audio> audio/narration-full.mp3
cp <user-srt>   audio/narration.srt

Step 3 · 解析 SRT → 切场景

读完整 SRT(scripts/srt-cues.mjs 可打印每条 cue 的开始秒数 + 文本,方便规划)。然后:

  • 按内容把 cue 归成"幕/场景"。一条 SRT cue 通常是一句话;把讲同一件事的几条 cue 合成一个场景(scene)。一支 6~7 分钟的日更,大约 12~16 个场景比较舒服(平均 ~30s/场,画面不至于久不动)。
  • 场景开始时间 = 它第一条 cue 的开始时间戳(秒)。
  • 场景时长 = 下一场开始 − 本场开始 + ~0.5s(这 0.5s 重叠让转场 wipe 能盖住上一场,避免穿帮)。最后一场到音频结束。
  • 场景内的逐步出现(sub-reveal)= 对应子 cue 的开始时间。让标题/要点/数字"在被念到的那一刻"出现 —— 这是同步感的关键。
  • 画面文案从 SRT 来,且不能和口播打架。可以精炼成海报式短句,但不能说的是 A、画面写 B。顺手核对事实(数字、专有名词、人名)—— 用户很在意准确。

> 把场景开始时间放进一个 JS 数组 const B = [...],所有 tween 用 B[i-1] + 局部偏移 定位。日后微调时间轴只改数组,不用逐条改 tween。

Step 4 · 编写合成(index.html)

  • 持久 chrome:页眉/页脚/栅格/边框这类每一场都在的元素,放在场景之外(直接挂 #root,不是 clip),整片不动 —— 营造"节目"感。
  • 音频一条连续 clip("音频即时钟"):
  <audio id="vo" src="audio/narration-full.mp3" data-start="0" data-duration="<总时长>" data-track-index="20" data-volume="1"></audio>

媒体元素不需要 class="clip";给它独立的 data-track-index

  • 每个场景一个全画布 .scene.clip,各自独立 data-track-index(重叠的 wipe 需要不同 track),z-index 递增(后面的盖前面的)。
  • 转场只用"incoming 场景自身的 clip-path 揭幕"(见 Gotcha「转场」):
  function wipe(sel, at){ tl.fromTo(sel,{clipPath:"inset(0 100% 0 0)"},{clipPath:"inset(0 0% 0 0)",duration:0.5,ease:"power3.inOut"}, at); }
  • 入场动画gsap.from(),定位在对应 cue 时间。短(0.3–0.7s),错峰,变化 ease。
  • 复用一套 class 化的 CSS 工具件(kicker / headline / note / stat / chip-xform / cards / numbered-list / accent-mark),15 个场景共享,别每场写一套 id 样式。
  • 品牌纪律:严格按 frame 的 token;强调色当"标点"不当"填充"(深色系:强调色只给小而实的标记;浅色系:强调色做色块、文字用墨色压在色块上)。

Step 5 · 校对(每次改完都跑)

npx hyperframes lint        # 0 error 才继续(var(--x) 字体告警是误报,可忽略)
npx hyperframes validate    # WCAG AA 对比度;改掉过暗的次级灰
npx hyperframes inspect --samples 30   # 版面溢出,带时间戳;场景多就多采样

装饰元素故意出血到画外 → 标 data-layout-ignore。真实溢出 → 改容器/字号/padding。

Step 6 · 渲染 + 验收

  • 先 standard 跑一版自检(快),用 ffmpeg 抽几帧验证同步:在"你知道这一刻在讲什么"的时间点抽帧,确认画面对得上。
  ffmpeg -y -ss <秒> -i renders/x.mp4 -frames:v 1 /tmp/f.png   # 然后看图
  • 成片渲染(master):
  npx hyperframes render --resolution landscape-4k --quality high --output renders/<slug>-4k.mp4

--resolution landscape-4k 是把同一合成按 2× DPR 真·超采样到 3840×2160(不是放大);4K master 即使观众看 1080p 也更耐平台二压。4K + 长片渲染较久(几分钟到十几分钟),可后台跑。

  • 验收:ffprobe 确认有 video(h264) + audio(aac) 两条轨且时长对得上;抽帧确认每场落在它的 cue 上。

成片留在 studio/videos/<slug>/renders/不要自动提交(除非用户明确要)。


Gotchas(血泪,务必遵守)

这些是踩过的坑,违反任何一条都会出废片:

  • 音频即时钟。场景时长从 SRT 量出来,不要凭感觉定时长再硬塞音频。
  • 有 SRT 就别转写。用户给了 SRT = 精确时间轴免费拿到,不需要 Whisper。(没 SRT 想自己转写前先问用户。)
  • 转场只用场景自身 clip-path 揭幕绝不用单独的全屏色块/幕布/刀闸/砸场板去做转场 —— 在渲染引擎里它会"扫进来盖住下一场后卡住不走",整场变成纯色/黑屏。揭幕揭的是 incoming 场景本体。
  • 不要给 .pad 容器套整体 opacity 的 "pushIn" 包装。容器级 opacity 动画在 seek 渲染里可能留在 0,把整场变黑。用每个元素各自的 gsap.from()
  • 配对 tween 不要加 overwrite:"auto"。它会把配对的另一条 tween 杀掉(比如"扫入"在、"扫出"没了)。lint 的 overlapping_gsap_tweens 是无害告警,宁可留着。
  • 字体必须本地 woff2。Google Fonts <link> 会被 lint 标记、且 sandbox 渲染里不可靠。中文配 Noto Sans SC;中文字在彩色 accent 色块里要给足竖直 padding/line-height(CJK 字形比 em 框高,padding 太紧 inspect 会报 text_box_overflow,给到 ~0.2em 竖直 padding + line-height ~1.12)。
  • 深色主题别信亮度探测。1×1 平均亮度对深底+稀疏文字永远偏低,会把正常场景误判成黑屏。靠抽帧看图确认。
  • 浅色主题的对比度误报。validate 在固定几个时间戳采样所有 DOM 文字,包括当时未激活的场景。浅底上"浅色文字"(白字、奶油字)一旦不在自己场景的激活时刻被采到,就报低对比度——这是误报。规避:accent 色块上一律用墨色(深)文字,未激活时墨字压奶油底仍是高对比,零误报。
  • 确定性。禁止 Date.now() / Math.random()(破坏可复现渲染);要随机用种子化 PRNG。

10. 每个场景独立 track-index;音频单独高 track-index。装饰出血标 data-layout-ignore

11. 画质:原生 1080p 在 Retina 上看会发虚(被放大 + H.264 4:2:0 软化彩色字缘);master 用 --resolution landscape-4k --quality high

SRT → 场景时间轴(配方)

场景[i].start    = cue[第一条].start                 (秒)
场景[i].duration = 场景[i+1].start − 场景[i].start + 0.5   (末场到音频末尾)
场景内某元素入场 = 它对应 cue 的 start
音频 clip        = data-start=0, data-duration=总时长
root data-duration = 音频末句之后留 ~3s 收尾

JS 里:

const B = [0, 38.63, 53.96, /* ...每场 start... */];   // 从 SRT 量
const at = (i, off) => B[i-1] + off;                   // i 是 1-based 场号
function wipe(sel, i){ tl.fromTo(sel, {clipPath:"inset(0 100% 0 0)"}, {clipPath:"inset(0 0% 0 0)", duration:0.5, ease:"power3.inOut"}, B[i-1]); }

超出范围

  • 生成配音 / 声音克隆:本 skill 只吃用户给的音频。要让它"听起来像我"用云端 TTS(MiniMax / ElevenLabs,支持中文 + 克隆),克隆只换"出声那一步",下游时间轴/挂载/渲染不变。HyperFrames 自带的 Kokoro TTS 做不了中文(CLI 传 zh、espeak 要 cmn,且质量差);本机临时方案 macOS say -v Tingting
  • 幻灯片 deck:要的是横向翻页 deck 而非视频 → 用 slides 类 skill。

输出清单

  • [ ] lint 0 error · validate 全过 · inspect 0 issue
  • [ ] 抽帧确认每场落在它的 cue 上(尤其数据页/转折页)
  • [ ] ffprobe:video + audio 两轨、时长 = 音频时长
  • [ ] master 用 4K high(除非用户另说)
  • [ ] 成片在 studio/videos/<slug>/renders/,未自动提交

How to use it

Copy the folder

Take sugarforever/producing-video from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.

Install what it needs

The instructions reference npx. Without those the skill loads but fails at the first command.