- src/: HyperFrames 封装 CLI(hv)、组件、品牌样式、音频(TTS/混音)工具 - projects/: 各项目 project.yaml 与文案 - interactive/: 井字棋演示页 - .gitignore: 忽略 node_modules、构建产物与 HyperFrames 运行期缓存
vibevideo
用 HTML 写视频。一个视频 = 一个 project.yaml;组件、模板、素材全站共享。
渲染引擎是 HyperFrames(Apache-2.0,HeyGen 开源),
但它只占整个系统约 5% 的代码,全部关在 src/cli/hv.mjs 里,随时可换。
30 秒上手
node src/cli/hv.mjs 体检 # 环境体检
node src/cli/hv.mjs 新建 ep001 # 建项目
$EDITOR projects/ep001/project.yaml # 写内容
node src/cli/hv.mjs 生成 ep001 # 生成 index.html
node src/cli/hv.mjs 看帧 ep001 # 抽几帧看效果(快)
node src/cli/hv.mjs 渲染 ep001 # 出片
所有子命令都有英文别名(hv new / hv build / hv render …),中文只是习惯问题。
一个项目长什么样
projects/ep001/
├── project.yaml ← 你唯一要写的文件
├── BRIEF.md ← 选题、受众、一句话信息
├── script.md ← 逐场景台词
├── assets/project/ ← 私有素材(录屏、截图)
└── out/ ← 成片
project.yaml:
title: 三个 Cursor 实用技巧
aspect: 横屏 # 横屏 1920x1080 / 竖屏 1080x1920 / 方形 1080x1080 / 超宽
fps: 60 # 24 / 30 / 60
lang: zh-CN
scenes:
- component: intro
duration: 3.5
props:
kicker: 技巧合集
title: 三个 Cursor 实用技巧
subtitle: 让你写代码少一半时间
- component: codedemo
duration: 8
props:
filename: fib.py
language: python
highlight: "3,4,5" # 高亮这几行
dimOthers: "true" # 压暗其他行
code: |
def fib(n):
if n < 2:
return n
跑 hv 生成 后,它会编译成时间轴、挂载组件、同步资源。
命令
| 命令 | 作用 |
|---|---|
hv 新建 <名> [--template 模板] [--aspect 画幅] |
建项目 |
hv 生成 <名> |
project.yaml → index.html |
hv 检查 <名> |
校验参数/时长 + 运行时/对比度/布局检查 |
hv 渲染 <名> [--quality 草稿|正式|交付] |
出片到 out/ |
hv 预览 <名> |
浏览器实时预览 |
hv 看帧 <名> [--时间 1,3,5] |
抽帧,比整条渲染快得多 |
hv 说明 <组件> |
看某组件有哪些参数 |
hv 列表 [项目|组件|模板] |
列出已有内容 |
hv 体检 |
依赖/字体/组件检查 |
组件库
| 组件 | 说明 |
|---|---|
intro |
片头:标题、副标题入场 |
outro |
片尾:引导关注 |
codedemo |
代码演示:逐行打字、语法高亮、重点行高亮 |
terminal |
终端:命令逐字输入、输出分类着色 |
lowerthird |
下方字幕条 |
textblock |
文字块,支持 **加粗** 和 [[强调色]] |
cursor |
鼠标指针:移动、点击涟漪 |
cad3d |
三维钩子建模全过程:草图→拉伸→切除→圆角 |
stress |
自由度压测:9 项超纲特性的渲染探针 |
hv 说明 codedemo 看参数。组件的参数表直接写在组件自己的
data-composition-variables 里,带中文标签,hv 检查 会校验你传错的名字。
分辨率与画幅
改 project.yaml 的 aspect 即可,组件代码一行不用动。
aspect: 竖屏 # 1080x1920
# 或自定义
width: 2560
height: 1080
原理:所有尺寸都写成 calc(N * var(--u)),--u = 短边 / 1080,
所以 1080p 横竖都是 1px,4K 自动放大一倍。
⚠️ 组件里写
width: var(--stage-w)是非法的(缺单位,浏览器会整条丢弃)。 无单位数字用在width/height上必须补* 1px。这个坑踩过一次。
写新组件
src/components/<名字>/<名字>.html,只有一个硬性要求:
window.__timelines["你的组件名"] = gsap.timeline({ paused: true });
就这一条。 满足它,组件就能在任何渲染器里跑。
两条铁律
-
画面必须是时间 t 的纯函数。 给定第 37 帧,直接画出第 37 帧长什么样,不依赖前面播过什么。 这是缓存、并行、秒级拖动预览的基础。
- ✅
gsap时间轴 +onUpdate里重画 - ✅ 3D 每帧手动清空重画
- ❌
requestAnimationFrame累积状态 - ❌ CSS
@keyframes(真实时间跑,抓到的是任意中间态)
- ✅
-
不准 import 渲染引擎的私有 API。 组件只允许依赖:浏览器 + GSAP + 上面那条注册约定。 违反这条,将来换渲染引擎就得重写。
当前能力边界(实测结论)
能做:WebGL / Three.js、真实布尔运算(CSG)、mix-blend-mode、clip-path、 mask-image、3D 变换、SVG 描边动画、canvas 逐帧重绘、字体排版、音频混流。
约束只有一条:确定性渲染(见上面铁律 1)。 这不是某个框架的限制——Remotion 也一样,只是它把这个约束藏在你写 React 组件里, 你察觉不到。
本机的性能实测(12 核 i5,1080p):
- 10s @ 30fps → 11.8s(5 个并行 worker,约等于实时)
- 16s @ 30fps → 22s
- 60fps 帧数翻倍,耗时也翻倍
目录
├── src/
│ ├── cli/hv.mjs ★ 适配层:CLI + 时间轴编译(约 600 行)
│ ├── cli/shot.mjs 抽帧
│ ├── components/ 组件库
│ ├── brand/ 设计规范:色板、字体、基准单位
│ ├── templates/ 视频结构套路
│ ├── audio/ Python:TTS 接口 + 程序化音效
│ └── vendor/ gsap / three / highlight.js
├── projects/ 视频项目
├── tools/ 构建 vendor 的脚本
└── node_modules/
音频
配音、点击音、背景音都归这条链路管。核心设计:场景时长 = 配音时长, 所以你不用手工对轴——写完台词,跑一条命令,时间轴自己就对了。
用法
python3 src/audio/cli.py 检查 # 音频侧体检
python3 src/audio/cli.py 配音 <项目> # 合成配音 + 对齐场景时长
python3 src/audio/cli.py 音效 <项目> # 合成音效
python3 src/audio/cli.py 混音 <项目> --bgm bgm.mp3 # 混成一条轨
node src/cli/hv.mjs 渲染 <项目> # 音轨自动挂上
python3 src/audio/verify_sync.py <项目> # 验证音画真的对齐
在 project.yaml 的场景里写 narration 即可:
- component: codedemo
duration: 8
narration: "第二步拉伸,把这条轮廓沿 Z 轴扫成实体。" # 时长会被这句覆盖
sfx: "pop@1.2" # 第 1.2 秒一个音效
跑完 配音 后,时长会变成 朗读时长 + 0.35s 留白,并回写 project.yaml。
换 TTS
Provider 是抽象的,换服务只写一个类:
from tts import TTSProvider, AudioClip, register
class MyTTS(TTSProvider):
name = "我的服务"
def synthesize(self, text, voice="", out_dir=None) -> AudioClip:
path = call_your_api(text, voice)
return AudioClip(path=path, duration=real_duration,
text=text, voice=voice, provider=self.name)
register("mine", MyTTS)
然后 配音 <项目> --provider mine。画面侧一行都不用动。
已内置:
| Provider | 说明 |
|---|---|
dummy |
不发声,但产出精确时长的合法 wav。链路先跑通,不是 mock |
kokoro |
本地离线 Kokoro-82M,不联网、结果可复现 |
⚠️
duration必须真实,不能估算——整条时间轴的对齐都依赖它。 DummyTTS 的价值就在于:现在就能验证音画链路,不用等真实 TTS。
音效是算出来的,不是找来的
src/audio/sfx/synth.py 用 numpy 程序化合成:点击、提示音、转场、落位、气泡。
体积为零、参数随便调、能跟着画面的强调色走。
验证对齐
verify_sync.py 会在每个场景起点铺一个可闻的标记音,渲染成片后逐点测能量峰。
长度对但偏移了照样是错的,所以必须这样测。
为什么用 HyperFrames
| HyperFrames | 自己造 / Remotion | |
|---|---|---|
| 成熟度 | Apache-2.0,HeyGen 在用 | 从零三个月起步 |
| 许可证 | 免费 | Remotion 按人头 $25/月,含 agent 写码 |
| 并行渲染 | 5 worker 自带 | 自己实现 |
| 对比度/布局检查 | check 自带 |
自己实现 |
但真正的理由是隔离:引擎的全部知识只存在于 src/cli/hv.mjs。
你的组件不知道引擎的存在。哪天要换,改一个文件,组件全部原样保留。