返回文章

Build Notes

Hypit 的完整生产流程:从一段参考视频到成片

拆开 Hypit 的端到端流程:六个阶段、三类源文件、唯一花钱的 build 命令,以及靠 build-record 显式复用的增量机制。并揭示一个意外事实——它的本地渲染器其实建构在 HeyGen 开源的 HyperFrames 引擎之上。

Hypit 的流程不是「生成视频」,而是「编译视频」:源文件先被规划成不可变的构建定义,再由 Runtime 逐个满足需求,最后由 Provider 渲染。理解这一点,整套命令与目录结构才自洽。

这篇拆解六个阶段、三类源文件的分工、唯一会花钱的 build 命令,以及靠 build-record 显式复用的增量机制——包括它没有隐式缓存这个容易踩的坑。

最后是一个意外发现:Hypit 的本地渲染器并不自研,它直接依赖 HeyGen 开源的 HyperFrames 引擎,而且锁在了一个落后的版本上。

一句话:Hypit 的流程不是「生成视频」,而是「编译视频」。

它把视频当成一个有依赖图的编译产物:源码(.svml / .svs / .svrun)先被规划成一张不可变的构建定义,再由 Runtime 逐个满足其中的「需求」,最后由 Provider 去渲染和生成。理解这一点,整套命令、目录结构和「改一句台词只重跑一部分」的能力就全都自洽了。

以下每条都标了来源:源码指仓库文件可查证(给出路径),文档指项目自带说明,实测指我亲自跑过或复核过。这是上一篇拆解的续篇,那篇讲「它是什么」,这篇讲「它怎么跑」。

01 / 全流程

六个阶段,责任方各不相同

最容易误解的一点:coding agent 不负责渲染,也不负责生成画面。它只负责「写源码 + 决定用哪些服务 + 提交工作」。真正的执行在 Runtime 和 Provider 里。

Hypit 六个生产阶段与责任方 流程图:1 参考解析(agent + Skill)→ 2 编写 Source(agent)→ 3 规划 BuildDefinition(Core 编译器)→ 4 满足 Need(Runtime + Provider)→ 5 渲染与合成(Provider 本地渲染器)→ 6 导出(get 命令)。 1 参考解析 agent + Skill 2 写 Source agent(.svml/.svs/.svrun) 3 规划 Core 编译器 4 满足 Need Runtime + Provider 5 渲染合成 本地 Provider 6 导出 hypit get Build Result(项目内 .hypit/results)——可被后续 Run 的 <build-record> 引用,实现增量复用
阶段 3–5 全自动,人只在阶段 1/2 与「花不花钱」的决策点上出现。虚线表示阶段 6 的产物会回流成阶段 3 的输入——这是这套设计最省钱的地方。
阶段责任方输入输出落盘位置
1 参考解析agent + Skill参考视频 / 一句话需求对参考的理解、镜头与节奏判断项目笔记
2 编写 Sourceagent上述理解 + 素材.svml / .svs / .svrun项目目录
3 规划CoreSource 闭包不可变 BuildDefinition(含 Need 列表)内存 / Runtime
4 满足 NeedRuntime + Provider单个 Need素材 / 计时 / 组件实例.hypit/runtimes/local
5 渲染合成本地 Provider合成图 + 时间轴帧序列 → 编码视频同上
6 导出CLIBuild id + 输出名成片文件你指定的路径

阶段 3–6 的产物统一记在 Build Result 里,项目内落在 .hypit/results(源码:packages/video-cli/src/distribution.ts:19),运行时数据在 .hypit/runtimes/local(同文件 :28)。实测

02 / 三类 Source

为什么要把视频拆成三个文件

这不是为了好看,是为了让「改一处」不牵动全局。

文件声明什么改它的后果
.svml内容与结构:Script 台词、画面组件、轨道、合成图牵动依赖它的组件,但已产出的素材可复用
.svs外观参数表:命名 Recipe(布局、锚点、字号、动效)只改外观,不动内容,可单独重渲染
.svrun这次要产出什么、复用什么不改变作品本身,只改变本次执行范围

一个真实的 .svrun 只有三行——实测(examples/interview/swap-host.svrun):

<?svml using="@hypit/run-markup@1"?>
<svrun version="1">
  <author source="./swap-host.svml"/>
  <target output="final.video"/>
</svrun>

而 .svs 是键值式的命名配方表,例如字幕框的位置与对齐都是一等参数——实测(examples/interview/recipes.svs):

caption.boy {
  stack-order: 70;
  x: 0.5;  y: 0.5;
  width: 0.88;  height: 0.22;
  anchor-x: center;  anchor-y: center;
  align: center;
}
这个拆分值得抄。把「内容」「外观」「本次执行范围」分成三种源文件,意味着换字体、换配色、换字幕位置这类高频改动完全不需要碰内容,也就不需要重新生成任何付费素材。多数视频工具把这三件事混在一个工程文件里,改一个颜色就得整体重渲染。
03 / 命令

命令级流程:只有 build 会花钱

官方文档对边界说得很直白(docs/quickstart/run.md):“Only build submits work.”——其他命令要么只读、要么只做诊断。文档

hypit runtime use hypit.runtime.json   # 选定执行环境(一次)
hypit plan  build.svrun                # 只展示将要执行的工作,不执行
hypit build build.svrun --follow       # 提交构建(这一步才花钱)
hypit get <build-id> --output final.video --to output/final.mp4

围绕这四步的辅助命令(packages/cli/src/output.ts:983-1022,源码):

  • check 校验单个 Source 自洽;pricing 读当前 Provider 的价目(只读,用来在花钱前算账)。
  • builds / history / status --watch / logs / inspect:从存量 Result 里找东西、看进度、读失败证据。
  • activity / cancel:查活跃构建、撤回构建。
  • doctor:诊断外部环境(我实测过,见文末)。

一个重要的设计细节:提交与观察是解耦的。文档明确说「终端关闭或观察中断,与 Build 内部 Provider 请求失败是两件不同的事」(skills/hypit/references/production/builds.md)——文档。也就是说 --follow 断了不代表构建失败,用 hypit builds 还能把结果找回来。这对长时间渲染很实用。

04 / 增量复用

最省钱的部分:Target、Candidate 与 build-record

这是整套流程里我认为工程价值最高的一节。

Run 源区分两个概念——文档(skills/hypit/references/production/runs.md):

  • Target:这次要交付的输出(例如 final.video)。
  • Candidate:途中需要、且可以外部提供的输出。

关键在于 Candidate 可以从历史 Build 结果里取:

<author source="./production.svml"/>
<target output="final.video"/>
<build-record id="kept-take" build="bld_..." output="opening-semantic.take"/>
<satisfy output="opening-semantic.take" candidate="kept-take"/>

官方对它的解释是:“changing a title can keep the performance and its timing while producing a new final video”——改标题可以保留已拍好的表演和它的时间,只产出新的成片。文档

为什么这比「缓存」高级,但也更麻烦。普通缓存按文件哈希命中,改一个字就整体失效。这里复用的是语义对象(SemanticTake ——「这段表演及其词级时间」)。文档特别说明:只要 Script 的身份仍然匹配,就能同时保留媒体和时间,让下游字幕与动效重新计算而不是重新生成。付费的素材调用被省掉,而免费的排版计算重跑——这个取舍方向是对的。

但要提醒一个容易误判的点:Hypit 没有隐式缓存。源码里资源 id 是 res_<uuid> 形式,生成物的身份只做规范化(canonicalize),并不参与跨构建的自动命中;想复用就必须由作者在 Run 里显式写 build-record,而且 Forward 不复制字节。源码

含义很直接:省钱的开关是手动的。忘了写 build-record,改一个标题就会把整片重新生成一遍。这是这套设计里最需要养成习惯的地方,也是它相比「自动脏标记」方案的一个真实缺陷。

05 / 渲染

渲染:按帧区间求值,而不是「导出整片」

渲染也是可切片的——文档(skills/hypit/references/production/rendering.md):

<render:Video id="review" composition={main.composition} timeline={speech.timeline}
  start-frame="240" end-frame-exclusive="360"/>

两个边界都指向原始节目的帧时钟,且必须在帧边界结束:240 到 360(不含)即 120 帧,30fps 下是第 8–12 秒。只渲染一个区间时,帧截图与源帧提取的开销都变小——这对「只看一眼第 8 秒」的审片场景很实用。

并发不是写死的。Provider 在自己的预留上限内按当前任务动态调整截图并发(文档原话:“the Provider adjusts capture concurrency within its reserved ceiling using the current job's work”),上限公式在 packages/provider-hyperframes-local/src/concurrency.ts:9-14:min(CPU-2, 内存/2/1.5GiB)——源码(上一篇文章里我证伪了「64 个 Chromium」,那正是这个公式在作者大机器上的取值)。

06 / 三条主线

字幕、口播、动效各自怎么走

字幕:词锚点 → 计时 → 排版 → 呈现

链路是:Script 里的词级锚点(@name / @name! 等)→ 对齐产生时间 → 字幕包负责排版 → 屏幕呈现。相关包按职责分层:script(解析台词与锚点)、temporal / timeline-author(时间轴)、caption 与 caption-fine(常规与精细字幕)、whisperx(词级对齐)、fonts-open(字体)。源码

字幕的呈现参数走 Recipe,而不是内联写在内容里——上面 caption.boy 那段就是证据:字幕框的归一化坐标与对齐方式是可复用、可换主题的数据。

口播:A-roll 提供表演与时间

文档把 A-roll 定义为「提供一段口播表演及其本地计时的素材」(“A-roll is the performance supplying a spoken passage and its local timing”),并强调它的画面可以切走、可以缩进画中画,而声音与语义角色继续存在——文档(skills/hypit/SKILL.md)。语音生成走 Provider(内置厂商映射里包含 fishaudio、elevenlabs 等)。

动效:由词锚点事件触发,而非按秒触发

这是「词锚定」真正的消费侧。上一篇文章里我逐行验证过:@manifest! 一个锚点被音效、图标、闪光三处消费,三处都没有秒数。改台词、换语言,这三处跟着词走。实测

动效本身由 Recipe 的 appearance / motion 参数驱动(例如 recipes.motion.card),属于「声明式」而非「脚本式」动画。源码

07 / 实现真相

它的本地渲染器,其实是别人的引擎

这是我做完流程拆解后最意外的一条发现,也修正了「Hyperframes 是 Hypit 内部件」的直觉。

Hypit 有四个名字里带 hyperframes 的包,很容易误以为是自研全家桶。实际是三层自研 + 一层上游引擎:

包角色归属
@hypit/hyperframes把 Composition 编译成 Hyperframes 文档Hypit 自研
@hypit/render-hyperframesSurface / 能力层(自述「is not a renderer」)Hypit 自研
@hypit/provider-hyperframes-local本地执行层Hypit 自研
hyperframes · @hyperframes/engine · @hyperframes/producer真正干活的渲染引擎HeyGen 开源

证据是硬的——Hypit 的本地 Provider 直接依赖外部包(packages/provider-hyperframes-local/package.json,实测):

"hyperframes": "0.7.101"
"@hyperframes/engine": "0.7.101"
"@hyperframes/producer": "0.7.101"

并且源码直接从上游引擎导入捕获会话:import { getCdpSession } from "@hyperframes/engine"(src/opaque-capture.ts:2,实测)。也就是说,Hypit 阶段 5 的渲染能力建构在 HeyGen 的 HyperFrames 之上。

上游是谁

我一手核实了这个项目(实测,2026-09-16):

  • 仓库 heygen-com/hyperframes,Apache-2.0 许可,50,496 星 / 4,607 fork,2026-03-10 创建,当日仍在推送。
  • 自我描述只有一句:“Write HTML. Render video. Built for agents.”
  • npm 包 hyperframes 最新 0.8.41,维护者邮箱 vance@heygen.com(HeyGen 官方)。
两个可操作的结论。① 版本落差:Hypit 锁在 0.7.101,上游已到 0.8.41——想自己玩这套渲染,直接对着 HeyGen 的引擎更划算。② 许可落差很大:HyperFrames 是干净的 Apache-2.0,而 Hypit 自身的修改版 Apache 禁止多租户与商业再分发。同样想「HTML 渲染成视频」,走上游没有这些限制。下一篇会把这个对比算清楚。
08 / 取舍

这套设计的优点与代价

设计好处代价
视频即编译产物可切片、可复用、可校验概念多(Run/Target/Candidate/Need/Build),上手陡
内容 / 外观 / 执行范围三分改样式不重生成素材要维护三类源文件与 Recipe 表
词锚定时间改稿不用重对时间轴语义锚点无法表达纯装饰性动效
Provider 抽象可换模型 / 可本地 / 可 BYOK接入成本高,无 *_API_KEY 约定
组件包按精确版本装可复现首次上手要逐个装,报错不告诉你装哪个

这是一套「为可维护性付费」的设计:它用更高的概念成本,换来了「改一处不用重做全片」。如果你只做一次性的视频,这套抽象是负担;如果你要做成百上千条变体,它是目前我见过最认真的回答。

顺带一提,它的 Studio 也只能在构建之后介入:hypit studio --run <build.svrun>——实测(hypit --help)。也就是说预览的对象是「已产出的构建」,不是「正在编辑的工程」。这是编译式架构的必然结果。

09 / 边界

我没能验证的部分

  • 没有跑过真实构建。本机的 hypit doctor 只报 0 diagnostics,但前提是先选 Runtime Profile;缺 ffmpeg 与付费模型,所以阶段 3–6 我只有源码与文档证据,没有端到端验收。实测
  • doctor 有个小坑:未选 Profile 时它仍报「No problems found」,因为它只检查已选中的 Profile。别把它当成环境完整的证明。实测
  • 「Need」的完整类型清单我只在文档与包结构中确认了存在与职责,没有穷举。
  • Build Result 清单文件的精确字段没有逐个核对。

下一篇我会把这三条主线拆开,和 Remotion、Hyperframes 等工具放在一起,研究一套通用、可落地的自动化剪辑流水线。