Hypit 是怎么把一句话变成视频的

用我们真实做过的「深夜下班开车回家」高速短片,从 Skill 知识到 CLI、Runtime,再到两版成片,走一遍完整链路。

Hypit CLI 0.3.11280×720 · 30fps · 22s6 个硬切镜头纯代码版 免费Grok 版 ≈ $2.1

01总览架构

Skill 是“导演手册”,CLI 是“制片调度”,Runtime 是“片场”。

👤 用户需求/参考视频 🤖 Coding Agent Hypit Skill(知识)SKILL.md:导演/制片方法references/:SVML 语法组件设计·builds·review→ 写出 .svml/.svs/.svrun ⌨ hypit CLIcheck · planruntime up · buildstatus · get ⚙ Runtime Worker html.local:无头 Chrome逐帧光栅化 HTML/Canvas media.local:FFmpegNormalize / 编码 / Mux 外部生成(本例在 Hypit 外调用)Sub2API → grok-imagine-video片段作为 asset:Video 引入 🎬 mp41280×720·30fps

Hypit Skill 做什么(给 Agent 的知识)

  • SKILL.md 把 Agent 定位为“导演兼制片”:理解需求与参考、做创作决策、对成片负责
  • 把作品理解为“随时间变化的对象与关系”:一条 Timeline + 一张 Canvas,场景用组件封装
  • references/ 提供 SVML/SVS/SVRun 语法、组件设计、媒体、构建与复查方法
  • 要求在花钱、连服务、重度装机等关键选择上把用户拉进来

hypit CLI 做什么(真正执行)

  • check 校验 Source 图;plan 列出请求与费用归属
  • runtime use/up/down 管理本地 Worker
  • build 提交持久 Build;status/inspect/builds 观察
  • get 把命名 Output 导出为文件

02我们实际走过的步骤

从参考分析到成片,每一步都在你的电脑 <project-dir>\highway 完成。

  1. 1

    参考视频分析

    在你电脑上用下载工具抓取参考片(部分网络环境被视频站拦截),抽帧做拼图,只用于分析,不嵌入、不复用。归纳出风格:扁平 2D / 低多边形卡通、低饱和配色、黄昏到夜晚的高速、驾驶位第一人称(圆润仪表台+方向盘)、车道虚线冲向消失点、成排橙色路灯、绿色起伏山丘剪影、粉紫天空、后视镜特写、对向远光、表情不爽的圆脸司机、底部居中白色小字幕。

  2. 2

    风格 → 镜头表

    6 个硬切镜头 / 22s / 1280×720 / 30fps;角色、文案、配乐全部原创(不使用任何歌词与原画面)。

  3. 3

    SVML 编写

    Skill 指导 Agent:把"随时间变化的对象与关系"写成组件。我们写了项目组件 @example/highway-scene(TS 包,htmlVisual + canvas 按帧绘制),在 SVML 里用 <hw:Scene>/<hw:Shot> 编排。

  4. 4

    hypit check

    校验 Source:+ Source is valid · Outputs 13(Grok 版 52)。

  5. 5

    hypit plan

    预览每一步由谁执行、是否收费——本地步骤全部显示 local, no Provider charge。

  6. 6

    hypit runtime up

    拉起本地 Worker(无头 Chrome + FFmpeg)。改了组件代码后必须 runtime down → up。

  7. 7

    hypit build

    提交持久化 Build:660 帧光栅化约 45–60s,status --watch 跟进。

  8. 8

    hypit get

    get <build-id> --output final.video --to raw.mp4 导出(目标存在会拒绝覆盖)。

  9. 9

    音频 Mux

    box 上 ffmpeg 合成原创引擎低鸣并 mux:-c:v copy -c:a aac,得到最终 mp4。

highway.svml(纯代码版,节选)
<time:Clock id="clock" frame-rate="30"/>
<time:Timeline id="drive" clock={clock} end="22s">
  <time:Instant id="s1" at="0s"/>
  <time:Instant id="s2" at="4.5s"/>  …  <time:Instant id="s6" at="19s"/>
</time:Timeline>
<spatial:Canvas id="canvas" width="1280" height="720"/>
<hw:Scene id="road" timeline={drive.timeline} within={canvas.bounds} during={drive.window}>
  <hw:Shot id="a" kind="road"   text="加完班,已经快零点了" at={drive.s1}/>
  <hw:Shot id="b" kind="mirror" text="后面那辆车,跟了我一路" at={drive.s2}/>
  …
</hw:Scene>
<film:Film id="main" canvas={canvas.canvas} timeline={drive.timeline} appearance={style.film.main}>
  <film:Track source={road.visual}/>
</film:Film>
<html:Video id="final" composition={main.composition} timeline={drive.timeline}/>
highway-grok.svml(Grok 版,节选)
<asset:Video id="src-road" src="./grok/road.mp4"/>
<mediaop:Normalize id="m-road" source={src-road} clock={clock}
  video="primary-moving" audio="none" span-authority="video"/>
<time:Window id="w1" from="0s" for="4.5s"/>
<spatial:Frame id="full-frame" within={canvas.bounds} left="0%" top="0%" right="100%" bottom="100%"/>
<visual:Track id="picture" timeline={drive.timeline}>
  <visual:Clip id="c-road" media={m-road.media} during={drive.w1}
    frame={full-frame} z="10" fit="cover"/>  …×6
</visual:Track>
<hw:Scene id="subs" …>  <!-- kind="sub-*":透明背景,只画字幕,present z=100 -->
<film:Film …>
  <film:Track source={picture.visual}/>
  <film:Track source={subs.visual}/>
</film:Film>
highway-grok.svrun
<?svml using="@hypit/markup/run@1"?>
<svrun version="1">
  <author source="./highway-grok.svml"/>
  <target output="final.video"/>
</svrun>
hypit.runtime.json
{
  "format": "hypit.runtime-local@1",
  "dataRoot": ".hypit/runtimes/local",
  "endpoints": {
    "media.local": { "use": "@hypit/media-local", "config": { "defaultConcurrency": 2 } },
    "html.local":  { "use": "@hypit/provider-html-local", "config": { "workers": 2, "defaultConcurrency": 1 } }
  }
}
hypit plan(成本预览)
$ hypit plan highway-grok.svrun
+ Build plan is valid      Requests 15 · Local requests 15 · Preflight ready
  @hypit/html-program#rasterize-visual   html.local  · local, no Provider charge
  @hypit/media-operations#normalize-media ×6  media.local · local, no Provider charge
  @hypit/media-operations#mux-program-media  media.local · local, no Provider charge
Grok 生成(Hypit 之外,经 Sub2API;密钥不展示)
POST /v1/videos/generations   (API 网关, Bearer ●●●●)
{ "model":"grok-imagine-video", "prompt":"Flat 2D low-poly cartoon … + 镜头描述",
  "duration":5, "aspect_ratio":"16:9", "resolution":"720p" }
→ { "request_id":"…" }
GET  /v1/videos/{id}          → status:"done", usage.cost_in_usd_ticks: 3.5e9 (≈$0.35)
GET  /v1/videos/{id}/content  → mp4 (1280×720, 24fps, ~5.04s)

03两个版本对比

同一份时间线与字幕,只替换画面来源。

纯代码版拼图

A · 纯代码渲染

Canvas 按帧绘制:车道虚线滚动、路灯透视、镜头轻微摇晃、远光眩光、原创圆脸司机眨眼。

Grok 版拼图

B · Grok 生成片段

6 段 grok-imagine-video(5s/720p),统一风格前缀提示词;Hypit 负责归一化到 30fps、剪辑、叠字幕。

A 纯代码B Grok 片段
成本¥0(全本地)6 次 × ≈$0.35 ≈ $2.1(按 tick=1e-10 USD 估算)
耗时渲染约 45s每段生成 45–55s + 渲染约 70s
可控性逐像素可改、可复现需重新生成,结果有随机性
质感扁平几何、偏简单光影/角色表演更丰富
Hypit 节点hw:Scene → film:Trackasset:Video → mediaop:Normalize → visual:Clip ×6 + 字幕层

04图层与时间线

22s 时间线上的 6 个窗口;字幕组件透明背景、present z=100,压在 z=10 的视频 Clip 之上。

0s2s4s6s8s10s12s14s16s18s20s22sz=10 视频 Clip道路·黄昏后视镜司机特写夜路·远光疾驰·路灯司机叹气z=100 字幕层加完班,已经快零点了后面那辆车,跟了我一路明天还得早起……对面的远光,晃得人睁不开眼路灯一盏一盏往后退算了,到家再说吧音频:ffmpeg 合成引擎低鸣(55Hz+82Hz 正弦 + 棕噪声,tremolo,淡入淡出)音轨

05踩过的坑

都已解决,记录下来下次少走弯路。

⚠ Node 版本

Hypit 要求 Node ≥ 22.15,系统是 22.14 → 另放便携版 <tools>\node22,每个终端先 prepend PATH。

⚠ "hypit":{"project":true}

项目 package.json 必须声明,否则 check 找不到项目。

⚠ workspace: 依赖

示例里的 workspace: 引用脱离 monorepo 无法安装,改成 npm 发布版本。

⚠ runtime down/up

Worker 常驻并缓存组件模块,改代码后不重启就会渲染旧版本(我们的仪表盘时钟改了却没生效)。

⚠ tsc --noCheck

组件编译时 tsc 会去类型检查 @hypit 依赖源码并报错不出产物;用 --noCheck 只做转译。

⚠ get 不覆盖

Export destination … already exists:先删旧文件再 get。

⚠ .svrun 与 runtime use

plan/build 需要自描述的 Run Source(.svrun),并先 hypit runtime use hypit.runtime.json。

⚠ z 顺序

字幕组件 present 默认 z=0,被 z=10 的视频 Clip 盖住 → 字幕层提到 z=100,并把背景改为透明。

⚠ 中文字体

字幕用 CSS 系统字体(Microsoft YaHei),换机器可能变;正式可用 asset:Font 打包。