| 1 | # RFC-04:Storyboard-first 工作流(产品形态修订) |
| 2 | |
| 3 | > **Status**: Draft v0.1 · **Supersedes部分前 3 份 RFC 假设** |
| 4 | > **Date**: 2026-05-26 (after Joey clarification mid-session) |
| 5 | > **Scope**: 修订 html-video 的核心用户工作流,从"agent 填 vars 一步 render"改为"资产 → HTML 分镜 → 用户审 → MP4"两段式 |
| 6 | |
| 7 | --- |
| 8 | |
| 9 | ## 关键澄清(来自 Joey) |
| 10 | |
| 11 | > 产品内支持上传各种图文资产,整合起来,生成 HTML 的分镜,用户可以先看分镜,确认分镜效果,之后再导出为 MP4。 |
| 12 | |
| 13 | 这条话改变了 html-video 三层关键定位: |
| 14 | |
| 15 | | 维度 | 前 3 份 RFC 假设 | 修订后 | |
| 16 | |---|---|---| |
| 17 | | **入口** | 用户已有 template id + 结构化 vars | 用户上传**散落资产**(图片 / 文字 / 数据) | |
| 18 | | **中间产物** | 无(直接 render MP4) | **HTML Storyboard**(多个 HTML 分镜,可在浏览器审) | |
| 19 | | **审批 gate** | 无 | Storyboard 阶段,用户必须确认才能 render MP4 | |
| 20 | | **创作姿态** | 工程师 fill schema | 创作者上传素材 + agent 编排 | |
| 21 | | **跟 HF / Remotion 差异** | meta-aggregator + 模板池 | **+ asset-to-storyboard 创作链路**(HF/Remotion 都没有) | |
| 22 | |
| 23 | **这是 html-video 真正的护城河**:HF / Remotion / Motion Canvas 都假定用户已经知道想做什么;html-video 假定用户**只有素材 + 一句话意图**。 |
| 24 | |
| 25 | --- |
| 26 | |
| 27 | ## 修订后的核心数据流 |
| 28 | |
| 29 | ``` |
| 30 | ┌─────────────────────────────────────────────────────────────────┐ |
| 31 | │ Stage 1:资产上传 │ |
| 32 | │ 用户: 图片 N 张 + 文字段落 + 数据表 + 音频(可选) + 一句话意图 │ |
| 33 | │ 输出: AssetBundle (项目目录下结构化存放) │ |
| 34 | └──────────────────────────────┬──────────────────────────────────┘ |
| 35 | ↓ |
| 36 | ┌─────────────────────────────────────────────────────────────────┐ |
| 37 | │ Stage 2:分镜生成(agent 主导) │ |
| 38 | │ agent: 根据意图 + 资产 → 选 templates → 编排 scenes │ |
| 39 | │ 输出: Storyboard(一组 HTML 分镜 + scene metadata) │ |
| 40 | └──────────────────────────────┬──────────────────────────────────┘ |
| 41 | ↓ |
| 42 | ┌─────────────────────────────────────────────────────────────────┐ |
| 43 | │ Stage 3:分镜审核(用户主导) │ |
| 44 | │ 用户: 浏览器里逐 scene 看,文字/图片/顺序/时长可改 │ |
| 45 | │ 工具: html-video preview-storyboard 启 dev server │ |
| 46 | │ 操作: inline 编辑 / 替换图 / 删 scene / 调时长 / 调顺序 │ |
| 47 | └──────────────────────────────┬──────────────────────────────────┘ |
| 48 | ↓ 确认 |
| 49 | ┌─────────────────────────────────────────────────────────────────┐ |
| 50 | │ Stage 4:MP4 导出 │ |
| 51 | │ 输入: 确认后的 Storyboard │ |
| 52 | │ 操作: 对每个 scene 调对应 EngineAdapter.render() │ |
| 53 | │ 后处理: 跨 scene 拼接(ffmpeg concat)+ 整体音轨 mux │ |
| 54 | │ 输出: 最终 MP4 │ |
| 55 | └─────────────────────────────────────────────────────────────────┘ |
| 56 | ``` |
| 57 | |
| 58 | --- |
| 59 | |
| 60 | ## 新核心概念 |
| 61 | |
| 62 | ### Asset |
| 63 | |
| 64 | 用户上传的原始物料。**不**经 agent 解读就先入库。 |
| 65 | |
| 66 | ```ts |
| 67 | // core/types.ts (additions) |
| 68 | |
| 69 | export interface Asset { |
| 70 | id: string; // sha1(content),content-addressed |
| 71 | type: 'image' | 'text' | 'data' | 'audio' | 'video' | 'reference-link'; |
| 72 | path?: string; // 本地文件路径(image/audio/video) |
| 73 | content?: string; // text / data 内联(小文件) |
| 74 | metadata: { |
| 75 | filename?: string; |
| 76 | mimeType?: string; |
| 77 | sizeBytes?: number; |
| 78 | width?: number; // image/video |
| 79 | height?: number; |
| 80 | durationSec?: number; // audio/video |
| 81 | /** Joey 给的 caption / 用户标注 */ |
| 82 | userCaption?: string; |
| 83 | }; |
| 84 | /** 用户上传时的意图标签(自由文本) */ |
| 85 | userTags: string[]; |
| 86 | } |
| 87 | |
| 88 | export interface AssetBundle { |
| 89 | /** Bundle 唯一 id,对应 project workDir 子目录 */ |
| 90 | id: string; |
| 91 | /** 用户的一句话意图("做个 25 天涨 5 万的曲线视频") */ |
| 92 | intent: string; |
| 93 | /** 用户偏好(aspect / duration target / mood / 商用否) */ |
| 94 | preferences: UserPreferences; |
| 95 | /** 全部资产 */ |
| 96 | assets: Asset[]; |
| 97 | /** 上传时间 */ |
| 98 | createdAt: string; |
| 99 | } |
| 100 | |
| 101 | export interface UserPreferences { |
| 102 | aspect?: '16:9' | '9:16' | '1:1' | string; |
| 103 | durationTargetSec?: number; |
| 104 | format?: 'mp4' | 'webm'; |
| 105 | resolution?: { width: number; height: number }; |
| 106 | fps?: number; |
| 107 | mood?: string; // 自由文本:'energetic' / 'calm' / 'corporate' / 'playful' |
| 108 | brandColors?: string[]; |
| 109 | fontFamilies?: string[]; |
| 110 | language?: string; // 'zh-CN' / 'en-US' |
| 111 | commercial?: boolean; |
| 112 | } |
| 113 | ``` |
| 114 | |
| 115 | ### Storyboard / Scene |
| 116 | |
| 117 | 分镜是**多个 scene 拼接**而成的"半成品 HTML 序列",每个 scene 是 RFC-01/02 的 template 渲染产物(HTML 形态而非 MP4)。 |
| 118 | |
| 119 | ```ts |
| 120 | export interface Storyboard { |
| 121 | id: string; |
| 122 | bundleId: string; // 来自哪个 AssetBundle |
| 123 | intent: string; // 复制 bundle.intent 备查 |
| 124 | scenes: Scene[]; |
| 125 | /** 跨 scene 全局音轨(背景乐 / 旁白) */ |
| 126 | globalAudio?: AudioTrack[]; |
| 127 | /** 全局 transitions 偏好 */ |
| 128 | defaultTransition?: TransitionId; |
| 129 | /** 总预计时长(所有 scene durationSec 之和) */ |
| 130 | estimatedDurationSec: number; |
| 131 | /** 状态机 */ |
| 132 | status: 'draft' | 'ready-for-review' | 'approved' | 'rendered'; |
| 133 | createdAt: string; |
| 134 | updatedAt: string; |
| 135 | } |
| 136 | |
| 137 | export interface Scene { |
| 138 | id: string; |
| 139 | /** 这个 scene 用哪个 template + 哪个 engine(RFC-02) */ |
| 140 | template: { id: string; engine: EngineId }; |
| 141 | /** 填好的 inputs(RFC-02 inputs.schema 兼容) */ |
| 142 | variables: Record<string, unknown>; |
| 143 | /** 用了哪些 asset (id 引用) */ |
| 144 | assetRefs: string[]; |
| 145 | /** 在 storyboard 时间线上的位置(秒) */ |
| 146 | startSec: number; |
| 147 | /** 时长(秒) */ |
| 148 | durationSec: number; |
| 149 | /** 跟下一个 scene 之间的过渡 */ |
| 150 | transitionToNext?: TransitionId; |
| 151 | /** Agent 给这个 scene 的解释(用户审稿时看得到) */ |
| 152 | agentNote: string; |
| 153 | /** scene-level 静态预览 */ |
| 154 | previewHtmlPath: string; // 渲染好的 HTML 文件 |
| 155 | previewPosterPath?: string; // 截图 |
| 156 | } |
| 157 | |
| 158 | export interface AudioTrack { |
| 159 | assetId: string; // 指向 AssetBundle.assets 中的 audio asset |
| 160 | startSec: number; // 在最终视频中的开始时刻 |
| 161 | fadeInSec?: number; |
| 162 | fadeOutSec?: number; |
| 163 | volumeDb?: number; |
| 164 | } |
| 165 | |
| 166 | export type TransitionId = 'cut' | 'fade' | 'slide-left' | 'slide-right' | 'zoom' | string; |
| 167 | ``` |
| 168 | |
| 169 | --- |
| 170 | |
| 171 | ## 修订 EngineAdapter 接口(RFC-01 增量) |
| 172 | |
| 173 | 每个 adapter 需要新增一个能力:**把单 scene 渲染成 HTML 而不是 MP4**。这是 storyboard 阶段的快速预览。 |
| 174 | |
| 175 | ```ts |
| 176 | // 增量加到 RFC-01 的 EngineAdapter interface |
| 177 | |
| 178 | export interface EngineAdapter { |
| 179 | // ... 之前的 id / capabilities / validate / render / preview ... |
| 180 | |
| 181 | /** 新增:渲染单 scene 到 HTML(不是 MP4),用于 storyboard 阶段 */ |
| 182 | renderToHtml?(input: RenderInput, ctx: RenderContext): Promise<HtmlSceneOutput>; |
| 183 | } |
| 184 | |
| 185 | export interface HtmlSceneOutput { |
| 186 | /** 主 HTML 文件路径 */ |
| 187 | htmlPath: string; |
| 188 | /** 引用的资产(agent 可以用这些做 "替换图片" 操作) */ |
| 189 | referencedAssets: { assetId: string; usagePath: string }[]; |
| 190 | /** 单帧静态截图(用作 storyboard grid 缩略) */ |
| 191 | posterPath: string; |
| 192 | /** 这个 scene 在浏览器里的预估时长(用户调整能改) */ |
| 193 | durationSec: number; |
| 194 | } |
| 195 | ``` |
| 196 | |
| 197 | **fallback**:如果 adapter 没实现 `renderToHtml`,core 提供 default 实现——直接 render MP4 然后 ffmpeg 抽 1 帧 + 包一个 video tag 的 HTML。这条路径慢但能用,鼓励 adapter 自己优化(HF / Remotion 都能直接出 HTML 不出 MP4)。 |
| 198 | |
| 199 | --- |
| 200 | |
| 201 | ## 修订 CLI Contract(RFC-03 增量) |
| 202 | |
| 203 | ### 新增命令 |
| 204 | |
| 205 | ```bash |
| 206 | # 1. 上传资产,建 bundle |
| 207 | html-video assets upload \ |
| 208 | --intent "做个 25 天涨 5 万的曲线视频" \ |
| 209 | --aspect 16:9 \ |
| 210 | --commercial true \ |
| 211 | --files "assets/*.png" \ |
| 212 | --files "data/stars.csv" \ |
| 213 | --json |
| 214 | # → 输出 bundle_id |
| 215 | |
| 216 | # 2. agent 主导生成 storyboard |
| 217 | html-video storyboard generate \ |
| 218 | --bundle <bundle_id> \ |
| 219 | --json |
| 220 | # → 输出 storyboard_id + scenes 列表 |
| 221 | |
| 222 | # 3. 修改 storyboard(agent 受用户指令调用) |
| 223 | html-video storyboard edit <storyboard_id> \ |
| 224 | --op add-scene --template <id> --vars-file scene-vars.json --at 30 |
| 225 | html-video storyboard edit <storyboard_id> \ |
| 226 | --op remove-scene --scene <scene_id> |
| 227 | html-video storyboard edit <storyboard_id> \ |
| 228 | --op reorder --scenes <id1>,<id2>,<id3> |
| 229 | html-video storyboard edit <storyboard_id> \ |
| 230 | --op set-duration --scene <scene_id> --duration-sec 8 |
| 231 | html-video storyboard edit <storyboard_id> \ |
| 232 | --op replace-asset --scene <scene_id> --old-asset <id> --new-asset <id> |
| 233 | |
| 234 | # 4. 启 storyboard 浏览器预览(不是单 scene preview,是整个 timeline) |
| 235 | html-video storyboard preview <storyboard_id> --json |
| 236 | # → 输出 url(页面里有 timeline + 每 scene 内嵌 iframe + 编辑控件) |
| 237 | |
| 238 | # 5. 用户确认 → render 全片 |
| 239 | html-video storyboard render <storyboard_id> \ |
| 240 | --output ~/Desktop/final.mp4 \ |
| 241 | --stream-progress --json |
| 242 | ``` |
| 243 | |
| 244 | ### 修订原来的 `render` 命令 |
| 245 | |
| 246 | `html-video render --template ... --vars-file ...` 仍保留,但定位改为 **"开发者直跑单模板"**(template 测试 / CI 用)。**用户主流程不该走这个**——agent 应该用 `storyboard generate / edit / render` 三件套。 |
| 247 | |
| 248 | --- |
| 249 | |
| 250 | ## 修订 SKILL.md 工作流(RFC-03 增量) |
| 251 | |
| 252 | 新版 SKILL.md 的"Standard workflow"段落: |
| 253 | |
| 254 | ```markdown |
| 255 | ## Standard workflow |
| 256 | |
| 257 | ### 1. Initial check |
| 258 | `html-video doctor --json` → 处理依赖缺失 |
| 259 | |
| 260 | ### 2. Collect assets + intent |
| 261 | - If user already has files: `html-video assets upload --files ... --intent ...` |
| 262 | - If user gives a vague intent: ask **one batched question**: aspect / mood / duration target / commercial usage |
| 263 | - Always extract intent into one sentence to feed into the bundle |
| 264 | |
| 265 | ### 3. Generate storyboard |
| 266 | `html-video storyboard generate --bundle <id>` |
| 267 | |
| 268 | The CLI internally: |
| 269 | - Searches templates per scene need (intro / data viz / CTA / outro etc) |
| 270 | - Selects engines based on RFC-01 capabilities + license filter |
| 271 | - Fills inputs using user's assets |
| 272 | - Returns scene list with agent_note for each |
| 273 | |
| 274 | ### 4. Show user the storyboard |
| 275 | Open `html-video storyboard preview <id>` URL in browser. Tell user: |
| 276 | - Estimated total duration |
| 277 | - Number of scenes + 1-line agent_note per scene |
| 278 | - "Click each scene to view, edit text inline, or tell me what to change" |
| 279 | |
| 280 | ### 5. Iterate on storyboard (multi-turn) |
| 281 | User: "把第二个 scene 里的图换成 logo.png" |
| 282 | → `html-video storyboard edit <id> --op replace-asset --scene <scene_id> --new-asset <asset_id>` |
| 283 | |
| 284 | User: "缩短到 30 秒以内" |
| 285 | → Calculate which scenes to trim, run multiple edit commands |
| 286 | |
| 287 | User: "我觉得 OK 了" |
| 288 | → Move to step 6. |
| 289 | |
| 290 | ### 6. Final render to MP4 |
| 291 | `html-video storyboard render <id> --output <path> --stream-progress` |
| 292 | |
| 293 | Surface progress every 25%. On done, paste output path + open in default player. |
| 294 | |
| 295 | ### Anti-patterns |
| 296 | - ❌ Don't render MP4 before user approves storyboard |
| 297 | - ❌ Don't skip the storyboard preview link — even if user says "你看着办" |
| 298 | - ❌ Don't quietly add scenes the user didn't mention — propose first, edit after confirm |
| 299 | - ❌ Don't render same storyboard twice without explicit user request (it's slow) |
| 300 | ``` |
| 301 | |
| 302 | --- |
| 303 | |
| 304 | ## Storyboard 文件存储约定 |
| 305 | |
| 306 | ``` |
| 307 | project-root/ |
| 308 | └── .html-video/ |
| 309 | ├── bundles/ |
| 310 | │ └── <bundle_id>/ |
| 311 | │ ├── bundle.json # AssetBundle metadata |
| 312 | │ └── assets/ |
| 313 | │ ├── <asset_id>.png # 内容寻址 |
| 314 | │ ├── <asset_id>.csv |
| 315 | │ └── <asset_id>.txt |
| 316 | ├── storyboards/ |
| 317 | │ └── <storyboard_id>/ |
| 318 | │ ├── storyboard.json # Storyboard metadata |
| 319 | │ └── scenes/ |
| 320 | │ ├── <scene_id>/ |
| 321 | │ │ ├── scene.json |
| 322 | │ │ ├── preview.html |
| 323 | │ │ ├── poster.png |
| 324 | │ │ └── source/ # adapter-rendered intermediates |
| 325 | └── outputs/ |
| 326 | └── <storyboard_id>-<timestamp>.mp4 |
| 327 | ``` |
| 328 | |
| 329 | `.html-video/` 放在用户项目根,跟 `.git/` 平级。`.gitignore` 默认 ignore(避免大文件入 git)。 |
| 330 | |
| 331 | --- |
| 332 | |
| 333 | ## 跟 RFC-01/02/03 的关系(明确) |
| 334 | |
| 335 | | 修订项 | RFC-01 | RFC-02 | RFC-03 | |
| 336 | |---|---|---|---| |
| 337 | | EngineAdapter 加 `renderToHtml` | ✅ 修订(增量) | — | — | |
| 338 | | Template metadata 加 `scene_role`(intro / data / cta / outro) | — | ✅ 修订(v0.2 加) | — | |
| 339 | | Template metadata 加 `assets_consumed`(声明吃哪类 asset) | — | ✅ 修订(v0.2 加) | — | |
| 340 | | CLI 加 `assets upload` / `storyboard generate / edit / preview / render` | — | — | ✅ 修订 | |
| 341 | | SKILL.md 的工作流改成 storyboard-first | — | — | ✅ 修订 | |
| 342 | | `html-video render --template ...` 降格为 dev-mode 命令 | — | — | ✅ 修订 | |
| 343 | |
| 344 | 后续会话写代码时按这份 RFC-04 的优先级实现。 |
| 345 | |
| 346 | --- |
| 347 | |
| 348 | ## v0.1 MVP 范围(修订后) |
| 349 | |
| 350 | 按"两段式工作流"重排优先级: |
| 351 | |
| 352 | - ✅ `@html-video/core`:Asset / AssetBundle / Storyboard / Scene 数据结构 + sqlite 存储 + content-addressed asset store |
| 353 | - ✅ `@html-video/adapter-hyperframes`:实现 `renderToHtml` + `render` |
| 354 | - ✅ `@html-video/cli`:`doctor` / `assets upload` / `storyboard generate` / `storyboard preview` / `storyboard render` |
| 355 | - ✅ `@html-video/storyboard-ui`:浏览器里的 storyboard preview 页面(**新增**,原 RFC-03 没考虑) |
| 356 | - timeline 视图 |
| 357 | - scene grid + 内嵌 iframe |
| 358 | - inline edit 文字 / 替换图 / 调时长 / 删 / 重排 |
| 359 | - 全屏播放 mock(每个 scene HTML loop 起来串) |
| 360 | - ✅ 5 个 reference templates(覆盖 intro / data-chart / image-pan / text-card / outro 五种 scene role) |
| 361 | - ✅ Claude Code SKILL.md(按 storyboard-first 工作流) |
| 362 | |
| 363 | **不**进 MVP(推到 v0.2+): |
| 364 | - ❌ adapter-remotion / motion-canvas / revideo(先把 HF 路径 + storyboard 闭环跑通) |
| 365 | - ❌ AI 智能编排(v0.1 agent 是 LLM 即兴选 template,v0.2 加规则 + ML 优化) |
| 366 | - ❌ 协作(多人审稿) |
| 367 | - ❌ 云端 storyboard 持久化(先纯本地) |
| 368 | |
| 369 | --- |
| 370 | |
| 371 | ## 给 Joey 的话 |
| 372 | |
| 373 | 这条澄清把 html-video 从"meta-aggregator + agent CLI" 升级成 **"asset-to-video 创作流水线"**——后者是真正有差异化的产品形态,HF / Remotion 都没做这件事,因为他们假设用户是开发者。**新护城河 = 资产理解 + storyboard 编排 + 跨引擎渲染**,三层一起才是 html-video。 |
| 374 | |
| 375 | 下一步如果你点头,我会按修订后的 v0.1 MVP 范围动手写代码骨架(core 数据结构 + adapter-hyperframes + CLI 几个命令 + storyboard-ui 草稿)。 |
| 376 | |
| 377 | --- |
| 378 | |
| 379 | ## Open Questions(v0.2 待定) |
| 380 | |
| 381 | 1. **资产理解** —— agent 拿到一堆图,怎么知道哪张是 logo / 产品 / 人物头像?v0.1 用 mime + 文件名启发;v0.2 接 vision model |
| 382 | 2. **AI 写文案** —— 用户没给文字只给图,agent 是否自动生成 caption?v0.1 提示用户补;v0.2 LLM 生成 |
| 383 | 3. **音频自动选** —— 用户没传 BGM,是否从 license-free 库自动选?v0.1 不做;v0.2 接 Pixabay / FreePD API |
| 384 | 4. **数据自动可视化** —— 用户上传 CSV,agent 直接选 chart 类型?v0.1 让用户选;v0.2 用 grammar-of-graphics 启发 |
| 385 | 5. **Storyboard 模板**(meta-template) —— 不只是单 scene,整片节奏("先 intro 再 3 个 data 最后 cta")也能模板化?v0.2 加 `storyboard-template` 概念 |
| 386 |