xiaoshuai 9b63424740 chore: 初始化仓库,提交 vibevideo 源码
- src/: HyperFrames 封装 CLI(hv)、组件、品牌样式、音频(TTS/混音)工具
- projects/: 各项目 project.yaml 与文案
- interactive/: 井字棋演示页
- .gitignore: 忽略 node_modules、构建产物与 HyperFrames 运行期缓存
2026-10-12 18:06:20 +08:00

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 });

就这一条。 满足它,组件就能在任何渲染器里跑。

两条铁律

  1. 画面必须是时间 t 的纯函数。 给定第 37 帧,直接画出第 37 帧长什么样,不依赖前面播过什么。 这是缓存、并行、秒级拖动预览的基础。

    • ✅ gsap 时间轴 + onUpdate 里重画
    • ✅ 3D 每帧手动清空重画
    • ❌ requestAnimationFrame 累积状态
    • ❌ CSS @keyframes(真实时间跑,抓到的是任意中间态)
  2. 不准 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。 你的组件不知道引擎的存在。哪天要换,改一个文件,组件全部原样保留。

S
Description
No description provided
Readme 137 KiB
Languages
HTML 56.1%
JavaScript 22.3%
Python 18.9%
CSS 2.7%