vid-sizzle-reel
根据品牌资产和关键信息,生成高能量的精彩集锦或宣传视频。 流程:HyperFrames HTML 合成(GSAP 时间线)→ 无头 Chromium → FFmpeg H.264 MP4。 无需 Runway、Pika 或任何 AI 视频 API。除了 Node.js + FFmpeg 之外,零运行时成本。
与 vid-product-launch 的区别:
vid-product-launch讲述一个叙事故事——一次应有的揭秘、五个章节、张力弧线vid-sizzle-reel创造能量和兴奋感——快速剪辑、音乐优先、无单一故事节奏
关键规则(每次生成前必读)
-
您必须亲自撰写关键信息,或要求用户撰写。 这些是最终显示在屏幕上的确切短语。让“AI 自己想办法”只会产生空洞内容。对于精彩集锦,3-5 条精炼语句就是全部内容。
-
品牌名仅出现在“落地”章节。 “冷开场”和“构建”章节通过不提及品牌名来制造悬念。“落地”章节才是最终的回报时刻。
-
在编写任何 HTML 之前,需在脚本顶部以注释形式声明节奏模式。 格式:
// RHYTHM: flash-sequence | Cold[0-5s]: stat | Build[5-40s]: msg(1.5s)|flash|... | Peak[40-55s]: ... | Land[55-60s]: logo+CTA。每个时间决定都必须对照此声明进行检查。 -
先在 CSS 中构建最终状态布局——不要一开始就使用 GSAP。 将每个元素在其最显眼的时刻定位到静态 CSS 中。然后使用
gsap.from()添加 GSAP 入场动画,使用gsap.to()添加出场动画。切勿定位在动画起始状态。 -
每个带时间控制的元素都必须有
class="clip"。 HyperFrames 强制要求。没有这个类,元素对播放器不可见。 -
每个剪辑元素都必须有
data-start、data-duration和data-track-index。 无一例外。相同轨道的剪辑不能重叠——需使用不同的轨道索引。 -
禁止使用
Math.random()。 HyperFrames 需要确定性合成。对于任何伪随机值,请使用种子化 PRNG (mulberry32)。 -
所有 GSAP 时间线必须以
{ paused: true }开始,并注册到window.__timelines["comp-id"]。 HyperFrames 播放器控制播放——永远不要调用tl.play()。 -
.scene-content容器上永远不要使用position: absolute。 应使用width:100%; height:100%; padding:Npx; display:flex。position:absolute仅保留用于装饰性元素。 -
音乐:使用带有
data-track-index的独立<audio>元素。 视频必须设置为muted playsinline。永远不要将音频放在<video>元素中。 -
在生成任何 HTML 之前,必须阅读
references/cut-patterns.md和references/tone-presets.md。 -
切勿直接在聊天中输出 HTML。 保存到文件,仅显示摘要。
第一步:信息收集
必需项:
key_messages(关键信息)—— 3-5 条能瞬间抓住眼球的短语,用于屏幕显示(用户必须自行撰写)
可选参数及默认值:
| 参数 | 默认值 | 描述 |
|---|---|---|
| brand_assets(品牌资产) | 无 | Logo URL/路径、品牌色(十六进制)、关键截图 |
| tone(基调) | energetic(动感) | energetic(动感)/ cinematic(电影)/ emotional(情感)/ professional(专业) |
| music(音乐) | 无 | 文件路径,或 BPM/流派字符串(如 "128bpm electronic") |
| duration(时长) | 60 | 30 / 60 / 90 秒 |
| aspect_ratio(宽高比) | 9:16 | 9:16(1080x1920)/ 16:9(1920x1080) |
| cut_style(剪辑风格) | auto | fast(快切,1-2秒)/ cinematic(电影感,3-5秒)—— auto 会从基调中推导 |
| end_card(结尾卡片) | auto | 用于“落地”章节的 Logo + 标语 + CTA URL |
cut_style 自动默认值:energetic → fast;professional → fast(2秒);cinematic → cinematic;emotional → mixed
如果缺少 key_messages,请精确询问:
"要生成精彩集锦视频,我需要您提供关键信息——即即将显示在屏幕上的确切短语。请给我 3-5 条精炼的句子。这是您为这个视频写的最重要内容。
示例:‘被 500+ 增长团队使用’ / ‘从数天到数分钟’ / ‘兼容 Claude、Codex、Gemini’
同时,如果方便,请提供:基调(动感/电影/情感/专业)、时长(30/60/90 秒)、以及任何品牌色或 Logo URL。"
如果关键信息已提供 → 立即进入第二步。
第二步:安装 HyperFrames
安装 HyperFrames 技能(仅在首次使用时进行——如果已安装则跳过):
npx skills add heygen-com/hyperframes
验证环境:
node --version # 必须 >= 22
ffmpeg -version # 必须存在
创建项目脚手架:
npx hyperframes init sizzle-[slug] --example kinetic-type --non-interactive
cd sizzle-[slug]
Slug:根据 end_card 中的品牌名或第一条关键信息生成 kebab-case 格式,最长 25 个字符。
第三步:内部架构(从不向用户展示)
1. 根据 duration + cut_style 推导剪辑时间点:
| 章节 | 30 秒 | 60 秒 | 90 秒 |
|---|---|---|---|
| 冷开场(Cold Open) | 0-5 秒 | 0-5 秒 | 0-8 秒 |
| 构建(Build) | 5-25 秒 | 5-40 秒 | 8-65 秒 |
| 高潮(Peak) | 25-28 秒 | 40-55 秒 | 65-80 秒 |
| 落地(Land) | 28-30 秒 | 55-60 秒 | 80-90 秒 |
将每个边界转换为毫秒。根据 cut_style 计算每场戏的持续时间:
fast:每条信息 1.2-1.8 秒(默认 1.5 秒)cinematic:每条信息 3.5-5.0 秒(默认 4.0 秒)mixed:根据章节能量在 2.0-4.0 秒之间变化
2. 将关键信息映射到场景:
- 冷开场(1 个场景):一个能产生冲击力的统计数据或问题——不能出现品牌名,建立悬念
- 构建:每条关键信息对应一个场景
- 高潮:以双倍比例重复最强的信息,或者将两条信息快速闪现组合
- 落地:品牌名 + 标语 + CTA URL
3. 如果提供了音乐,检测节拍同步:
npx hyperframes tts --analyze-beats [music-file]
如果不可用,则从 BPM 计算:beat_ms = 60000 / BPM。将剪辑对齐到每 2 拍。
4. 选择基调预设——阅读 references/tone-presets.md 中的 CSS 令牌。
5. 声明节奏模式——在脚本顶部写入注释:
// RHYTHM: flash-sequence
// Cold Open [0-5000ms]: stat
// Build [5000-40000ms]: msg-1(1500ms)|flash|msg-2(1500ms)|flash|msg-3(1500ms)|flash|msg-4(1500ms)|flash|build-accum(10000ms)
// Peak [40000-55000ms]: msg-strongest(1200ms)|flash|msg-2nd(1200ms)|flash|HOLD(5600ms)
// Land [55000-60000ms]: logo+tagline+cta
6. 确定像素尺寸:
9:16→ W=1080, H=1920(默认——适用于 Instagram Reels、TikTok、YouTube Shorts)16:9→ W=1920, H=1080
第四步:HyperFrames HTML 合成
在编写任何代码之前,请阅读 references/cut-patterns.md 和 HyperFrames 技能 (/hyperframes)。
所需的 HTML 骨架:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
[来自基调预设的字体 CDN 链接]
<style>
:root {
[来自基调预设的所有 CSS 令牌]
}
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
</style>
</head>
<body>
<div
data-composition-id="sizzle-[slug]"
data-width="[W]"
data-height="[H]"
style="position:relative;width:[W]px;height:[H]px;overflow:hidden;background:var(--bg)"
>
[如果 cut-flash: true]
<div id="cut-flash"
style="position:absolute;inset:0;background:var(--cut-flash-bg);opacity:0;pointer-events:none;z-index:200"></div>
[end if]
[如果 film-grain: true]
<canvas id="grain-overlay"
width="240" height="135"
style="position:absolute;inset:0;width:[W]px;height:[H]px;pointer-events:none;opacity:0.022;mix-blend-mode:overlay;z-index:50"></canvas>
[end if]
[如果 vignette: true]
<div id="vignette-overlay"
style="position:absolute;inset:0;background:radial-gradient(ellipse at center,transparent 35%,rgba(0,0,0,0.65) 100%);pointer-events:none;z-index:51"></div>
[end if]
<!-- 场景——每个场景一个 div -->
<div id="scene-cold"
class="clip"
data-start="0"
data-duration="5"
data-track-index="0"
style="position:absolute;inset:0;opacity:0"
>
<div class="scene-content" style="width:100%;height:100%;padding:120px;display:flex;flex-direction:column;align-items:center;justify-content:center;box-sizing:border-box">
[冷开场内容——一个统计数据或有冲击力的短语,不能出现品牌名]
</div>
</div>
[每个构建场景一个 div,data-start 根据剪辑持续时间递增]
[高潮场景]
[落地场景——品牌名 + 标语 + CTA]
[如果提供了音乐]
<audio id="music"
class="clip"
data-start="0"
data-duration="[总时长]"
data-track-index="10"
data-volume="0.85"
src="[音乐路径]"
></audio>
[end if]
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script>
// ── 节奏声明 ───────────────────────────────────────────────────────────────
// RHYTHM: [flash-sequence | cinematic-hold | beat-sync | mixed]
// Cold Open [0-Nms]: ...
// Build [N-Nms]: ...
// Peak [N-Nms]: ...
// Land [N-Nms]: ...
// ── 种子化 PRNG(仅用于颗粒效果——不能用于其他随机值) ────────────────────
function mulberry32(seed) {
return function() {
seed |= 0; seed = seed + 0x6D2B79F5 | 0;
let t = Math.imul(seed ^ seed >>> 15, 1 | seed);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
[如果 film-grain: true]
// ── 胶片颗粒(仅限电影/情感基调) ──────────────────────────────────────────
const grainCanvas = document.getElementById('grain-overlay');
const grainCtx = grainCanvas ? grainCanvas.getContext('2d') : null;
// 颗粒效果由 GSAP ticker 更新(不是逐帧捕获——HyperFrames 处理此操作)
[end if]
// ── GSAP 时间线 ────────────────────────────────────────────────────────────
const tl = gsap.timeline({ paused: true });
// ── 冷开场 ────────────────────────────────────────────────────────────────
[冷开场的入场和出场补间动画——具体模式见 cut-patterns.md]
// ── 构建章节 ──────────────────────────────────────────────────────────────
[每条信息的补间动画——见 cut-patterns.md 中的 flash-sequence 或 cinematic-hold]
// ── 高潮章节 ──────────────────────────────────────────────────────────────
[高潮补间动画——与构建相同模式,但时间更紧凑]
// ── 落地章节 ──────────────────────────────────────────────────────────────
[落地入场动画——Logo、标语、CTA。无出场——保持到结束]
// ── 时间线注册 ────────────────────────────────────────────────────────────
window.__timelines = window.__timelines || {};
window.__timelines["sizzle-[slug]"] = tl;
</script>
</div>
</body>
</html>
设计质量标准:
- 冷开场统计数据:16:9 时字号 ≥160px,9:16 时 ≥100px
- 关键信息:字号 ≥80px —— 一眼可读
- 落地章节品牌名:16:9 时 ≥120px
- 所有颜色均来自预设令牌变量——除了
var(--accent)对应的字面十六进制(用于 canvasfillStyle) scene-content始终使用padding + flex——内容容器上永远不要使用position:absolute- 每个带有
data-start的元素都必须有class="clip"
第五步:自我质量检查(在进入第六步之前修复所有问题)
内容:
-
key_messages—— 每个短语恰好出现一次,无重复 - 品牌名仅出现在“落地”章节(不能出现在冷开场、构建或高潮章节)
- 冷开场不包含任何品牌名或产品名——仅包含统计数据或有冲击力的短语
- 落地章节包含:品牌名 + 标语 + CTA URL(至少三个元素)
- 最终帧上存在 end_card CTA URL
HyperFrames 协定:
- 每个带有
data-start的元素都有class="clip" - 每个剪辑元素都有
data-start、data-duration、data-track-index - 根合成 div 上有
data-composition-id - 已注册
window.__timelines["sizzle-[slug]"] = tl - 所有时间线以
{ paused: true }开始 - 无
Math.random()—— 仅使用种子化 PRNG - 无
tl.play()或tl.resume()调用
GSAP / 布局:
- 所有入场使用
gsap.from()(从屏幕外 TO CSS 位置) - 所有出场使用
gsap.to()(从 CSS 位置 TO 屏幕外) -
.scene-content容器上无position:absolute - 脚本顶部以注释形式声明了节奏模式
剪辑时机:
-
fast基调:平均剪辑时长 ≤ 2.0 秒 -
cinematic基调:平均剪辑时长 ≥ 3.0 秒 - 对于
energetic基调,每次剪辑边界触发闪白效果(60 毫秒,透明度 0→1→0) - 相同轨道的剪辑不重叠(检查所有
data-track-index分组)
音效:
- 存在
window.__sfxTimeline并放置在时间线注册之前 - 每条关键信息触发
word-hit事件 - 在“构建”章节到“高潮”章节的过渡边界触发
whoosh事件 - 在“落地”章节开始时触发
cta-chime事件 - 前 500 毫秒内无音效触发
第六步:校验、渲染、输出
npx hyperframes lint
npx hyperframes inspect --json
修复所有校验错误。修复所有检查错误(文字溢出、剪辑元素超出容器)。然后渲染:
npx hyperframes render --output sizzle/[slug]/sizzle-reel.mp4
输出:
## 精彩集锦: [来自 end_card 的品牌名]
基调:[基调] | 时长:[N]秒 | 剪辑风格:[cut_style] | 宽高比:[ratio]
节奏:[已声明的模式]
章节
冷开场 [0-N秒]: [有冲击力的统计数据或短语]
构建 [N-N秒]: [N 条关键信息已列出]
高潮 [N-N秒]: [最强信息以最大比例呈现]
落地 [N-N秒]: [品牌名] + "[标语]" + [CTA URL]
文件
源文件:sizzle/[slug]/index.html
输出:sizzle/[slug]/sizzle-reel.mp4
音效时间线(嵌入每个合成)
与 vid-product-launch 相同的 FFmpeg 合成音效系统。将 window.__sfxTimeline 放在 GSAP 时间线注册之前。
精彩集锦的音效类型参考:
| 类型 | 声音 | 时长 | 位置 |
|---|---|---|---|
word-hit | 低音冲击(50Hz)+ 瞬态咔嚓声(2.2kHz)+ 噪音爆发 | 180毫秒 | 每条关键信息场景开始时 |
whoosh | 双频带噪音扫频(1.1kHz 主体 + 4-8kHz 空气感) | 700毫秒 | 在构建→高潮的边界处 |
cta-chime | A 大调和弦(440+554+659+880Hz)+ 声学钟响 shimmer | 1.2秒 | 在落地章节开始时 |
window.__sfxTimeline = [
// 每条关键信息一个 word-hit(根据场景开始时间计算)
{ ms: MSG_1_START_MS, sfx: 'word-hit', vol: 0.50 },
{ ms: MSG_2_START_MS, sfx: 'word-hit', vol: 0.50 },
{ ms: MSG_3_START_MS, sfx: 'word-hit', vol: 0.50 },
// ... 每条信息重复
{ ms: PEAK_START_MS - 700, sfx: 'whoosh', vol: 0.55 },
{ ms: LAND_START_MS, sfx: 'cta-chime', vol: 0.65 },
];
从已声明的章节边界计算所有 ms 值。