chore: 初始化仓库,提交 vibevideo 源码

- src/: HyperFrames 封装 CLI(hv)、组件、品牌样式、音频(TTS/混音)工具
- projects/: 各项目 project.yaml 与文案
- interactive/: 井字棋演示页
- .gitignore: 忽略 node_modules、构建产物与 HyperFrames 运行期缓存
This commit is contained in:
2026-10-12 18:06:20 +08:00
commit 9b63424740
42 changed files with 8004 additions and 0 deletions
+261
View File
@@ -0,0 +1,261 @@
# vibevideo
用 HTML 写视频。一个视频 = 一个 `project.yaml`;组件、模板、素材全站共享。
渲染引擎是 [HyperFrames](https://github.com/heygen-com/hyperframes)(Apache-2.0,HeyGen 开源),
**但它只占整个系统约 5% 的代码**,全部关在 `src/cli/hv.mjs` 里,随时可换。
---
## 30 秒上手
```bash
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`:
```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` 即可,**组件代码一行不用动**。
```yaml
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`,只有一个硬性要求:
```js
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/
```
---
## 音频
配音、点击音、背景音都归这条链路管。**核心设计:场景时长 = 配音时长**,
所以你不用手工对轴——写完台词,跑一条命令,时间轴自己就对了。
### 用法
```bash
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` 即可:
```yaml
- component: codedemo
duration: 8
narration: "第二步拉伸,把这条轮廓沿 Z 轴扫成实体。" # 时长会被这句覆盖
sfx: "pop@1.2" # 第 1.2 秒一个音效
```
跑完 `配音` 后,时长会变成 `朗读时长 + 0.35s 留白`,并回写 `project.yaml`。
### 换 TTS
Provider 是抽象的,换服务只写一个类:
```python
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`。
你的组件不知道引擎的存在。哪天要换,改一个文件,组件全部原样保留。