VBB 预览≡导出:时钟域与快切经验
面向后期改编辑器 / 导出 / GIF materialize 的人。实现主路径:
| 模块 | 文件 |
|---|---|
| 片源时间轴 | tools/lib/mp4-timeline.js |
| 修剪编辑器 | tools/lib/vtrim-editor.js |
| 黑盒面板 / GIF 物化 / 草稿 | tools/extra-panels/v2g-suite-src/40-vbb.js(改完跑 build-v2g-suite.cjs) |
相关:黑盒体积/帧率策略见 vbb-blackbox-optimization.md。
一句话结论
预览与导出差帧,根因几乎总是「时钟域」而不是「换一种导出器」。
校准可信 → ffmpeg frameRaw 快切;不可信 → chrome 逐帧(准但慢)。禁止再靠切换导出路径碰运气。
根因(不要再猜成别的)
- Probe CTS ≠ Chrome 显示时钟
probeMp4VideoTimeline的ctsSec(stts/ctts/elst)是容器媒体时钟;Android Chrome 实际显示常整体偏 X≈15–32ms(elst 空编辑被当成 0、µs 截断等都会贡献)。 - 精确 CTS seek 常显示 k−1
currentTime = cts[k]或cts[k]+1e-4在手机上经常仍停在上一帧。应用 帧内点frameSeekSec(默认落到帧长约 1/4 处)。 - chrome-VideoFrame「成功」不等于 keeps 算对了
成功 diag 与失败 diag 可有完全相同的 keep / startF / endF;成功只是因为采帧与预览同在 Chrome 时钟域,偏移被抵消。 - 校准后
ctsSec已平移到显示域
必须保留mediaCtsSec给 ffmpeg 秒切;有采样表时 禁止再回退onepass-sec(失败 diag 里大量ffmpeg-onepass-sec即此坑)。
复现锚点片源:2547.mp4(Android Chrome)。
禁止再做的事(历史坑)
- 在 chrome-VideoFrame ↔ ffmpeg-sec ↔ frameRaw 之间来回拨开关「当修 bug」——不修时钟,只会震荡。
- 用
captureStream录预览当导出——质量差,产品已否决。 - 校准中点复核去比
video.currentTime——seek 后它≈你刚设的值,复核恒过;必须比VideoFrame.timestamp(容差约 ¼ 帧)。 - 有
ctsSec时 ffmpeg 失败再吃sec/ss兜底——校准后秒数已不在容器时钟。 - GIF materialize 不传 timeline / 不校准 / 与下载用不同 keeps——下载对齐了 GIF 又偏。
- 停帧把
endParkAtSec记成 Chrome 帧内偏置点——播放截断会漂半帧;钉 成片 CTS,seek 才用帧内点。 - 以为「只能对齐预览帧」——慢的是每帧 seek,不是对齐本身;校准过关就该快切。
正确做法(检查清单)
打开片源 / 导出前:
probeMp4VideoTimeline(file)→ 得到ctsSec+mediaCtsSeccalibrateToElement(timeline, videoEl)→chromeOffsetSec/calibrateMethod(中点容差 1 帧;中点仍抖 → 仍应用 t0,标videoframe-t0-soft,不算失败)- 停帧 / 精确 seek:
seekSecForTime/frameSeekSec(勿裸 CTS) - keep:
keepRangesFromEdit(edit, duration, timeline)(半开采样窗) - 导出顺序(下载须先
ensureSourceTimelineForExport/ awaitsourceTimelineReady):- 默认
canFfmpegFirst(有ctsSec且 CTS 近似单调即可;校准 method 不挡)→exportKeepVideoframeRaw(「快速切片…」) - 异常才
exportKeepByChromeDisplay(无表 / CTS 真乱序 / 裁画面 / 快切挂了;toast 须带「异常」;进度「对齐预览帧」——绝不当默认) - 仍无 → ffmpeg 兜底(无采样表才允许 sec)
- 默认
- GIF:
materializeVbbKeepVideo与下载同一套matTimeline+matKeeps+canFfmpegFirst- 删中间(三者同表):
- 加载后
ensureVbbSourceTimeline→vbbSingleTimeline/item.timeline - 取表一律
resolveVbbTimelineForFile(file, hint):按 file 对齐。hint → 同 file 的批量项 → 单文件缓存;漏传 file 时不借用当前预览条 - 打开编辑:
editor.open({ timeline })克隆 cts。外预览表已可信校准(videoframe-t0/soft/already-aligned)则不再平移,避免完成回写把外预览带漂;未校准才在克隆上锚。完成回写__vbbTimeline(不进 LS / JSON 备份) - 外预览
enforceVbbEditPlaybackWindow/ 编辑器keepRangesLocal/ GIFmatKeeps都走keepRangesForEncode(..., timeline);matKeeps只算一次 - 手柄若已在采样 CTS 上,
alignTrimSecToSamples保留全精度;毫秒取整会让sampleIndexAtSec退回上一帧 nextStartAfterCutEnd=ctsSec[sampleIndexAtSec(cutEnd)+1];缺表才退回1/fps(接点会差帧)
- 加载后
- 删中间(三者同表):
- 诊断 JSON(ver≥6)必含:
calibrateMethod、chromeOffsetSec、exportOrder、exportMethod、previewKeeps
合入前:相关单测 + build-v2g-suite.cjs + bump-version.cjs。
快切 vs 预览帧
| 路径 | 速度 | 何时用 |
|---|---|---|
ffmpeg trim=start_frame/end_frame(frameRaw) |
快(一次滤镜) | 默认(canFfmpegFirst) |
chrome 逐帧 seek + VideoFrame + Encoder |
最慢 | 异常兜底(无表/乱序/未锚上/裁画面/快切挂) |
-c copy |
最快 | 不考虑(只能切关键帧) |
以后可演进(未做):WebCodecs 顺序解码按下标取帧;chrome 兜底改「播放抓帧」少 seek。先保证时钟与门槛,再谈换编码器。
诊断 JSON 怎么读
导出附带 *-export-diag.json:
| 字段 | 含义 |
|---|---|
exportOrder |
ffmpeg-first / chrome-first |
export.exportMethod |
期望快切:ffmpeg-onepass-frameRaw;慢路径:chrome-VideoFrame;若见 onepass-sec 且有 timeline = 回归 bug |
source.calibrateMethod |
诊断用;有表即应 exportOrder=ffmpeg-first;仅无表/乱序/快切挂 → chrome |
source.chromeOffsetSec |
显示相对媒体的 X(秒) |
previewKeeps / export.keeps |
比对是否同窗;同 keeps 仍偏画面 → 仍是时钟/seek,不是 keep 公式 |
编辑草稿恢复(附带坑)
点「完成」后刷新再选同片套不上,常见不是「没保存」,而是匹配键过严:
- 重选后
size/lastModified常变;Chromeduration可差约 1s - 正确:
contentSig(文件头 256KB)→ 名+大小 → 名+放宽时长 → 唯一文件名 - 「完成」须写指纹并 回读校验;失败提示备份编辑(隐私模式/配额)
- 键:
devtools-vbb-edit-drafts-json-v1;成功出片后故意清该片草稿
实现:findVbbEditDraftForItem / vbbFileContentSig / saveVbbEditDraftsSync(40-vbb.js)。
改代码时要同步动的文件
- 改时间轴 / 校准 /
canFfmpegFirst→mp4-timeline.js+tools/test/mp4-timeline.test.js - 改停帧 / 导出顺序 / diag →
vtrim-editor.js+tools/test/vbb-preview-encode-align.test.js - 改 GIF 物化 / 草稿 →
40-vbb.js→build-v2g-suite.cjs+vbb-preview-gif-align/vbb-edit-draft测试 - 功能合入必须
bump-version.cjs(北京时间戳)
会话交接(给下一个 Agent)
- 先读本文件 + 用户最新
*-export-diag.json,不要先改导出优先顺序碰运气。 - 若
exportMethod已是 frameRaw 仍偏:查calibrateMethod、帧内 seek、demuxfirstPresentIdx/ B 帧乱序,而不是再切回「永远 chrome」。 - 用户要快:默认 ffmpeg-first;chrome 是异常兜底,勿当常态路径。中点抖动用 soft,勿再因 mid-check 丢掉快切。