利益相关声明:作者与文中产品有直接的利益相关(开发者、自家产品等)

Videosays 是我做的一个视频文案提取工具。它不要求用户先把视频下载到电脑里:入口是一条公开视频分享链接,结果是完整文字和分段时间轴。

2026 年 6 月 10 日,它的第一版代码进了仓库。

那一版已经有语音识别、媒体解析、积分扣费和一个能提交任务的网页。如果目标只是做个演示,事情到这里差不多已经结束:给它一段音频,等一会儿,页面上出现文字。

但 Videosays 接收的不是一段规规矩矩的音频,而是用户从抖音、小红书、B站、YouTube 等平台复制来的一段分享内容。这里面可能有一句推荐语、几个表情和一个短链接。链接打开后还会跳转,真正的音频地址可能临时生成,也可能根本没有单独的音频。

语音识别反而成了整条链路里相对清楚的一段。

我翻了一遍 Git 记录。从 6 月 10 日到 7 月 24 日,仓库里一共有 309 次提交。很多提交不是在调识别模型,而是在处理这些听起来很琐碎的事:

6 月 22 日:重试 B站音频镜像
6 月 29 日:尝试所有 YouTube 音频流
6 月 30 日:重试抖音媒体下载超时
7 月 15 日:修复视频号媒体暂存
7 月 20 日:修复转写队列的公平调度
7 月 24 日:修复 TikTok 的 ASR 媒体传递

分享链接不是媒体文件

我最早把“解析视频”想成了一个函数:输入链接,返回音频地址。

这个抽象没有完全错,错的是我低估了返回结果的差异。

有的平台会给出单独音频,有的平台只有视频;有的平台同时返回几条不同清晰度的线路;有的地址直接交给识别服务就能读取,有的必须带来源请求头;还有些地址能在我的电脑里打开,交给云端服务却会失效。

当前代码的第一步,是从整段分享文字里取出第一个 URL,再根据域名判断平台。后面不再走一个巨大的条件分支,而是分别进入九个平台适配器。每个适配器只负责一件事:尽可能拿到标题、作者、封面、时长,以及可用的音频、视频或字幕候选。

这里有个后来才补上的选择:媒体到底应该怎么送去识别。

小文件可以直接传。需要下载处理的文件,要限制连接时间、空闲时间和体积,避免一个坏链接拖住整个 Worker。更大的媒体则先以流的方式暂存到对象存储,再生成有时效的访问地址交给识别服务。任务结束后,暂存文件会被删除。

这套流程不是一开始设计出来的。6 月底那串围绕 YouTube 的提交——暂存音频、尝试不同音频流、补下载诊断——就是我在“本地能取到,线上却不一定能交付”之后一点点补出来的。

有字幕,不代表应该直接拿来用

接平台时,我还走过另一段弯路:既然平台返回了字幕,直接用字幕不是比重新识别更快吗?

实际拿到的数据没有这么整齐。

一条视频可能没有字幕,也可能同时有原语言字幕、自动字幕和翻译字幕;有些返回纯文本,有些带时间轴;有些字段叫“source”,但不一定足以证明它就是发布者上传的原始字幕。

如果只按“有 URL 就使用”的规则处理,中文视频有机会拿到英文翻译,或者拿到一份没有时间点的文字。页面看起来更快,结果却不是用户要的东西。

现在每条字幕候选都会记录语言、角色、格式、是否原始字幕和置信度。自动模式只在候选足够可信并且能形成时间轴时使用平台字幕,否则回到语音识别。用户也可以明确选择只用 ASR;如果强制要求平台字幕,而平台没有返回符合条件的内容,任务会直接说明原因。

这件事也改变了我对产品文案的写法。Videosays 能从视频语音生成文字和时间轴,但不能笼统地宣称“下载原字幕”。两句话听起来接近,背后的承诺并不一样。

返回一个任务 ID,不等于异步系统做好了

6 月 18 日,我把提交接口改成异步:用户提交后先拿到任务 ID,转写在后台继续执行。

当天看起来很顺。页面不会为了等一条长视频一直挂着,刷新后也能回到任务详情。

几天后,麻烦开始从另一个方向冒出来:任务卡在哪一步?用户连续提交五条视频时先跑谁的?识别服务已经接单但回调迟迟没到怎么办?扣掉的额度应该在什么时候确认?失败后怎样退回?暂存的媒体什么时候清理?

现在一条任务会经过等待、解析、处理中、完成或失败等状态。视频时长确认后才预扣额度;提交识别失败要释放,成功后再结算。回调没有按时到达时,定时任务会继续查询状态。队列会限制单个用户同时占用的任务数,避免一个批量用户把后面所有人都堵住。

这些东西没有一个会出现在首页功能列表里,但它们决定了用户看到的是“稍后回来还有结果”,还是一个永远转圈的页面。

7 月 20 日一天里,我连续改了公平调度、队列派发、定时查询和超时起算。那天之后我才真正接受一件事:异步不是把函数放到后台执行,而是要把每一个可能中断的位置变成可观察、可恢复的状态。

九个平台,并不是九份相同的需求

接入新平台很容易给人一种明确的进度感:图标多一个,支持列表长一行。

真实使用数据把这种感觉打破得很彻底。

截至 7 月 28 日,后台的历史平台雷达记录到:

平台任务数使用用户数
抖音12,628548
小红书7222
B站4117
YouTube4012
微信视频号2913
TikTok187
快手165
Instagram52
X / Twitter21

 

抖音不是“稍微多一点”,而是和其他平台完全不在一个数量级。

如果只看功能覆盖,我会继续把每个平台做成一张同样大的卡片;如果看真实任务,应该优先把抖音的解析失败、排队和结果质量处理好,再决定长尾平台值不值得继续投入。

这个判断来得有点晚。Instagram 和 X 已经接上了,真实使用分别只有 5 次和 2 次。我不会说这两次开发完全没有意义,它们帮助我把平台适配器拆得更干净,但如果从头再来,我不会这么早追求“支持九个平台”。

同一个后台的 30 天视图里,页面记录了 1,705 位访客、434 位完成确认的新账号、223 位至少生成过一次的用户,以及 8,570 个完成任务。这里也不能把任务数除以用户数,解释成“人均生成多少条”:访客、注册和任务的去重口径不同,老用户也会在这个周期继续提交。

我更在意的是,真正完成一次生成的人只占这批访客的一小部分。支持范围继续变长,不会自动解决用户为什么没有走到结果页。

用户最后拿走的不是一段字符串

第一版识别完成后,只保存一整段文字。

6 月 22 日,我开始保存 ASR 返回的分段结果,并在详情页加入时间轴和 SRT、VTT 导出。这个改动表面上只是多存了一列数据,实际让结果从“看过一遍”变成了可以继续使用的材料。

完整文字适合搜索和复制;分段时间轴可以回到原视频定位;SRT、VTT 可以继续进剪辑或字幕流程。三种输出来自同一份分段数据,而不是在导出时重新猜时间点。

后面拿公开视频做案例时,真正需要的也不只是“把全文复制走”:先在文字里搜到一句话,再看左边的时间段回到原视频,这一步反而经常决定那份结果能不能继续用。

这也是我现在介绍 Videosays 视频文案提取页面 时,只先说“公开链接、完整文字和分段时间轴”的原因。Agent、CLI、API 和多种支付方式都是真的,但它们不应该抢在用户要拿走的结果前面。

如果重新做一次

如果现在回到 6 月 10 日,我会先只把抖音做稳。

先让一条任务从分享链接走到分段结果,再故意切断其中每一步:链接失效、媒体超时、识别服务未回调、余额不足、数据库更新失败。确认任务能留下正确状态,额度能结算或退回,用户能看懂失败原因,然后再接第二个平台。

我也会从第一天保存时间轴,而不是只保存全文。全文是语音识别接口最容易展示的结果,却不一定是用户之后最好用的结果。

还有一件事,是早点把“用户输入失败”和“系统失败”分开。私密视频、已删除内容、需要登录或受地区限制的链接,本来就不应该和识别服务故障混在一个成功率里。数字会变得没那么好看,但至少知道该修哪一段。

做 Videosays 之前,我把视频转文字理解成一次模型调用。现在看,它更像一段不太稳定的物流:先辨认用户交来的地址,找到真正的货物,选择能走通的线路,把它安全送到识别服务,再把返回的散件整理成可以继续使用的结果。

模型当然重要。只是对这个产品来说,用户粘贴链接和看到时间轴之间,大部分工作都发生在模型之外。

作者:kennyshaw,Videosays 开发者。