视频解说生成器 是做什么的?
你像动态设计师一样制作视频,但使用的是代码:每个场景是一个小型 JS 模块,构建 DOM/SVG 元素,并在暂停的 GSAP 时间线上添加补间动画。渲染器逐帧播放该时间线(t = frame / fps),截取每一帧,并将帧序列传递给 ffmpeg。输出具有确定性:相同项目总是渲染出相同的帧。
SKILL_DIR 指本技能的基础目录(包含此文件的文件夹)。
设置(每台机器一次)
cd SKILL_DIR/scripts && npm install && npx playwright install chromium
你还需要将 ffmpeg 添加到 PATH。如果缺少依赖,每个脚本都会打印此命令。
工作流程
- 先设计分镜。 商定主题、受众、目标时长以及是否需要旁白,然后在
video.json中编写场景列表。每个场景包含narration(旁白内容)和visual(屏幕显示内容)。阅读reference/storyboard.md。在分镜自行合理之前,不要编写场景代码。 - 创建项目骨架:
node SKILL_DIR/scripts/new.mjs <project-dir> --title "...",然后编辑video.json,并在scenes/<id>.js中为每个场景编写一个模块。参考reference/animation-patterns.md获取模式,参考reference/design.md获取布局、排版和颜色。如果有旁白,将场景时长设置为"auto",并使用cue('短语')来同步动画。两个完整的配音视频示例:examples/kubernetes-overview/(ElevenLabs)和examples/how-dns-works/(Gemini)。 - 旁白(如果需要): 遵循
reference/voiceover.md。简言之:在video.json中添加"voice"块,运行node SKILL_DIR/scripts/narration.mjs status <project-dir>,对每个列出的场景调用一次 OpenRouter MCP 工具generate-speech(使用它打印的确切参数),并使用narration.mjs add注册每个保存的文件。在检查(步骤 4)之前完成此操作,因为真实的音频会改变场景时长和cue()的时间。 - 检查:
node SKILL_DIR/scripts/lint.mjs <project-dir>。它会在 Chromium 中加载每个场景,并报告加载错误、动画时长超过场景、在tl外部创建的动画、超出 frame 的元素、被裁剪的文本以及超出场景时长的旁白。修复每一个警告,除非是故意的。 - 预览:
node SKILL_DIR/scripts/render.mjs <project-dir> --contact-sheet,然后查看 PNG(<project-dir>/out/contact-sheet.png)。检查层次结构、重叠、可读性、空白帧、时间。如需细节,渲染特定时刻:--stills 12.5,30。反复修改直到视觉正确,然后在最后一次编辑后再进行一次联系表渲染。 - 渲染:
node SKILL_DIR/scripts/render.mjs <project-dir>→<project-dir>/out/video.mp4。旁白片段会自动混入。将路径、时长和场景列表提供给用户。他们可以使用node SKILL_DIR/scripts/serve.mjs <project-dir>进行交互式预览(拖拽进度条、播放/暂停、旁白播放、c切换字幕、m静音)。
有用的渲染标志:--out file.mp4,--from 10 --to 20(部分渲染),--scale 0.5(快速草稿),--workers N,--crf 18,--audio track.mp3(使用自定义音轨代替旁白),--no-voice(静音),--audio-codec aac(默认是 MP3,也可以在 VS Code 中播放)。
项目结构
my-video/
video.json # 标题、宽度、高度、帧率、种子、过渡、主题、场景[]
scenes/<id>.js # 每个场景一个模块
assets/ # 图片/SVG,引用为 '/assets/x.svg'
audio/ # 旁白片段 + manifest.json(来自 narration.mjs;保留它们,因为付费生成)
style.css # 可选,如果存在则自动加载
out/ # 渲染输出(contact-sheet.png, stills/, video.mp4)
video.json 场景字段:id(必需,也是默认文件名 scenes/<id>.js),duration(秒,或 "auto":匹配旁白音频时长,或在音频存在前基于阅读速度估算,且永远不会短于动画时长),narration,visual,可选的 file,以及可选的 transition("fade",默认,或 "cut")。顶层 "voice" 块在 reference/voiceover.md 中描述。
场景契约
import { h, reveal, pop } from '/__runtime/helpers.js';
export default function ({ el, tl, gsap, theme, width, height, random, onFrame, cue, voice }) {
const title = h('div', { class: 'h1' }, 'Hello');
el.append(h('div', { class: 'center' }, title));
reveal(tl, title, { at: 0.3 }); // tl 时间 0 = 本场景开始
tl.to(title, { color: 'var(--accent)', duration: 0.5 }, cue('the key idea')); // 当说出该短语时
}
el:此场景的容器。它覆盖完整的 1920×1080 舞台,并会自动显示和隐藏。tl:此场景的时间线。所有动画必须位于tl上(或添加到tl的其他时间线上)。切勿直接使用gsap.to(...)、CSSanimation/transition、setTimeout、setInterval、requestAnimationFrame、<video>或任何依赖挂钟时间的内容。渲染器仅移动时间线播放头,因此其他任何内容在 MP4 中都会冻结或不同步。cue(phrase, offset = -0.15)返回场景中说出phrase(不区分大小写,必须出现在本场景的旁白中)时的场景时间。一旦音频存在,它会与片段测量的停顿对齐,因此从句开头的短语会落在口语单词上。如果未找到该短语则抛出异常。对所有需要与旁白同步的at使用它。voice提供本场景语音的{ start, duration, end, estimated }(例如,保持循环效果运行直到voice.end)。- 对于逐帧绘制(画布、程序化运动),使用
onFrame((sceneTime) => draw(sceneTime))。该函数在场景可见的每一帧被调用。 - 覆盖层(可选): 在
video.json中设置"overlay": "scenes/_overlay.js"会在整个视频的每个场景上方绘制一个模块,用于显示时间码、进度条、徽标或帧边框。它接收{ el, tl, onFrame, scenes, duration, ... },其中onFrame((t) => ...)接收全局时间,scenes列出每个场景的start/end。 random()按场景播种,Math.random也被播种,因此随机布局是可重现的。- 该函数可以是
async(例如,用于fetch('/assets/data.json'))。字体在场景运行前加载,因此测量文本是可行的。 - 主题颜色为 CSS 变量:
var(--bg) --surface --surface2 --fg --muted --accent --accent2 --accent3 --accent4 --danger,外加--font和--mono。在video.json→"theme": { "accent": "#ff3366" }中覆盖它们。 - GSAP 插件已全局注册:DrawSVG, MotionPath, MorphSVG, SplitText, Text, ScrambleText, CustomEase。
质量标准
- 每个场景一个想法。屏幕上至少每 2-3 秒应有内容移动或变化,并且每个动画应与它所说明的旁白行同步(使用
cue();旁白语速约 2.5 字/秒)。 - 文字要大:在 1920×1080 舞台上,正文 ≥ 40px,标签 ≥ 26px。保持在安全区域内(
.safe,边距 96/128px)。 - 逐步构建图表,而不是直接展示完整的图表。
- 在每个场景结束、淡出前,留下短暂的停顿,以便最终状态能被看清。
旁白
音频来自 OpenRouter MCP 服务器(generate-speech);本技能仅负责规划与录制。如果这些工具不可用,告知用户旁白需要连接 OpenRouter MCP 服务器,并在此时无旁白渲染。reference/voiceover.md 包含完整流程、模型与语音选择以及成本。