跳到主要内容
Video2Any
2026-08-22

八个看起来像算法问题的 bug

Video2Any 提取幻灯片的方式是:在浏览器里解码帧,逐帧和上一帧比较,变化够大的留下来。结果不对的时候,第一个被怀疑的总是这个比较。但通常不是它。

下面是我们一周里追过的八个问题。只有两个真的在检测器里。剩下的是:一个没清掉的守卫、一个骗人的测试脚本、一个虚拟滚动列表、一次和接口的竞态、一道内存天花板,以及一条把自己唯一有用的部分丢掉了的错误日志。

八个

一、那个点了没反应的控件

有人反馈一段短视频选 Dense 只出三张,和 Default 一样。我们对着阈值公式看了半天,才回头确认最基本的一件事:这个设置到底有没有生效?

没有。首次扫描被一个 ref 守住,每个文件只跑一次。切换密度确实重建了回调,但守卫把 effect 原地弹了回去,真正能触发重跑的只有另一处提示里的按钮。切换之后你看到的还是上一次的结果,连时间戳都一模一样。修完之后,同一段片子 Sparse / Default / Dense 分别是 3、6、13 张。之前三档都是 6 张。

二、测试脚本骗了我们两次

为了解释一个反常结果,我们在 Node 里把管线离线复刻了一遍,喂同一个文件,结果两次都和线上对不上,而且方向相反:先是脚本说 1 张、线上 6 张,改完又变成脚本 15 张、线上 6 张。

两个原因。脚本用 ffmpeg 抽的是灰度帧,而浏览器交给引擎的是 canvas 上的 RGBA,两边的缩放滤波差异足以把标定阈值从 0.618 挪到 0.650。另外它跳过了决定要不要启用活动遮罩的那个函数,也就是说它复刻的根本不是线上那条路。一个只复刻了算法、没复刻管线的脚本,会非常自信地给你一个错答案。

三、DOM 里只有屏幕上那些

有个测试要验证 32 分钟的视频能出 30 分钟之后的幻灯片。它遍历所有缩略图读时间戳,最后一张停在 27:00 —— 看起来就是被截断了。

那个网格是虚拟滚动的,只有可见行才是真实元素。先滚到底再读,真正的最后一张在 32:00。这个断言一直在量的是视口,不是数据。

四、每个 worker 各自去重

长视频会切成多个分片并行扫描。每个 worker 会把自己那段里重复的画面去掉 —— 讲者翻回上一页、会议镜头切回同一个视角 —— 但它们互相看不见。

于是一个反复出现的画面,在几个分片里出现就被保留几次。一段 29 分钟的会议录像,9 张里有 3 张是重复的。更麻烦的是,这让最终结果取决于切了几片,也就是取决于用户机器有几个核。现在去重挪到了主线程,每张幻灯片到达时就比一次,所以重复的那张根本不会先显示出来再消失。

五、更正确的设计反而更差

每个分片在自己那一小段上各自标定阈值,这显然不对:一个视频应该只有一个分布、一个阈值。于是我们就这么改了 —— worker 上报自己测到的比值,主线程汇总、统一标定、把结果发回去。

它确实把 4 分片和 8 分片的差距从 3 张收窄到 1 张。但它同时让结果变差了。还是那段 29 分钟的会议,ffmpeg 自己的场景检测数出 7 处真实转场;分片各自标定加上面那个去重是 6 张,统一标定是 4 张。汇总之后分布变宽,median + 2·MAD 被抬高,真实的换页就被压下去了。我们把它 revert 了。一个设计可以更有道理,同时又打不过你手上这个;而唯一能判断的办法,是手里得有一个你自己代码之外的参照。

六、false 不是因为答案是否,是因为还没问

一个付费订阅用户传了一段长视频,只解析出前三十分钟。点一下重新采样就是完整的 —— 这正是它看起来像检测问题的原因。

编辑器在还不知道你是谁的时候就开工了。权限标记初始是 false,不是因为答案是否,而是因为请求还在路上;而长度上限读到这个 false,就按免费档砍了。等重新采样跑起来,套餐信息已经到了。第一版修法是等它到;后来干脆让它不需要等 —— worker 返回页面时本来就握着 session,直接把套餐写进文档,首次渲染就知道。

七、所有错误都叫 “Error”

语音转写的失败次数比成功还多,6 比 5,而每一条日志记下来都是字符串 “Error”。19 次提取失败也一样。

代码记的是 err.name,而任何人手写的 Error,这个值永远是 “Error”。真正有信息的 message 被丢掉了。改成记 message 之后,转写失败终于有了机制:decodeAudioData 会按音频上下文的采样率重采样,所以在硬件的 48 kHz 立体声下解一段 55 分钟的课,光是浮点采样就要先摊出约 630 MB,两小时是 1.4 GB。改成在一个本来就是 16 kHz 的上下文上解码,模型拿到的音频完全一样,内存大约只要六分之一。

八、它很快,但用起来还是像卡住了

一段 92 分钟的会议录像,52 秒扫完。第一张幻灯片出现在第 50 秒。

没有哪一步慢。阈值要从整段视频的差分分布里标定出来,所以在读完之前一张都选不出来 —— 而这 50 秒里,屏幕上只有一个百分比。一个孤零零往前爬的百分比,读起来就是卡住了。现在它会说正在做哪一半、已经读到了多少,同样的 52 秒就不再是个投诉了。

如果能跟一周前的自己说两句

在推敲代码之前,先确认输入真的到了代码里。这八个里有一半是:设置没送到、答案还没回来、或者测试量错了东西。

还有,手上要留一个你自己管线之外的参照。ffmpeg 的场景检测不是我们在做的东西,也不需要是,它只需要独立。要不是有它,我们会把那次标定重写发上线 —— 因为它是更好的设计 —— 然后悄悄地在 7 张里丢掉 3 张真的幻灯片。

转换视频博客