按标点切、拿句段切,两次都失败:救场的是词级时间戳里那 280 毫秒静音

最近在跟一条字幕轴较劲

手头有个小需求:给一段会议通知配字幕。文字部分不难,转写工具一大把,难的是时间轴,每条字幕什么时候出现、什么时候消失,得精确到能对上说话人的停顿。以前我的做法是导出全文,再在字幕软件里一句句拖,长录音拖下来眼睛都花了,而且拖完没有任何记录能说明这一刀为什么切在这儿。

这次想换个思路:如果转写接口本身就返回细粒度的时间戳,字幕轴是不是可以直接算出来,不用手拖?

阿里云百炼 CLI 的 bl speech recognize 能把语音转成文字,返回里带不带词级时间戳、细到什么程度,我得自己验一遍才知道。验法是用往返实验:先让 TTS 合成一段我知道原文的话,再让 ASR 把它转回来。原文在我手上,转写差在哪、时间戳细到哪一层,逐字对照就有答案,不用猜。

往返跑通,只有一个字不一样

合成一句会议通知:

bl speech synthesize --text "今天下午三点在第二会议室开选题会,记得带上周的数据报表。" --voice longcheng_v3 --out 通知.wav

再转回来:

bl speech recognize --url 通知.wav --language zh --out 通知.json

转写全文是这样:

今天下午3点在第二会议室开选题会,记得带上周的数据报表。

跟原文对照,我一个字一个字过了一遍,唯一差异是"三点"变成了"3点"。

我一开始以为这是识别错误,准备写个纠错规则。后来想明白了:这是 ASR 对数字做规范化输出,口语里的"三"落在时间语境里就写成阿拉伯数字。它不是错,是输出约定。字幕上要"三点"还是"3点",是我自己的文案口径问题,后处理统一替换就行,别把它当 bug 修。真去"修"它,反而可能改坏那些本来就该是阿拉伯数字的地方。

坑一:想按标点切,词级根本没有标点

时间结构比我预期的细。JSON 里分三层:全文、句级、词级。这句话的句级只有一段,160–4440ms;词级给了 16 个词,每个词都带起止毫秒。

看到 16 个词各带时间戳,我当时的想法是:这下省事了,按标点把词串切开,每段的首词起始毫秒当 cue 起点、末词结束毫秒当 cue 终点,字幕轴就出来了。

然后我把词面拼起来看了一眼:

今天下午3点在第二会议室开选题会记得带上周的数据报表

逗号不见了。词级输出不带标点,一个标点都没有。

这一刀切空了。句级那层是有标点的,可整句就一段,一段没法切成两条字幕。我坐在两条都不通的路中间:有时间的没标点,有标点的没时间粒度。

我还想过一个绕法:拿句级那层带标点的文本去跟词级序列做对齐,按位置把标点插回词流里,插回去就有标点可切了。理论上能写,代价是要多做一套字面对齐逻辑,比后面那个静音间隔的规则复杂得多。更要紧的是,就算对齐做成了,插回去的也是 ASR 自己的标点,不是原文标点。后面第二个坑会看到,它的标点确实会被改写。为了切个字幕条搭这么一套东西,我觉得不值,先放一放,回头看看有没有更省事的路。

那条路确实存在,就写在同一份 JSON 里,不过我得先栽第二个跟头才看见它。

坑二:想拿句段切,三句被并成了一段

那就退一步,用句段当字幕条吧,一句一条,标点也齐。

为了验证,我又合成了三句议题:"第一个议题是季度复盘。第二个议题,下月选题排期。散会前记得提交周报。"转写回来一看,三句被并成了一个句段,标点还被规范化了,"复盘。第二"变成了"复盘,第二"。

这条退路也不通,而且比坑一更隐蔽。如果我只跑了前面那句单句素材,根本发现不了:一句话本来就只有一个句段,"句段=字幕条"看起来完全成立。多句素材一上,句段数就不等于原句数了,条数直接错;句段里的标点也不等于原文标点,切点跟着错。

栽完这个跟头我回头想,验证素材本身也有讲究。第一段是单句、只有一个逗号,太"乖"了,很多结构问题在它身上不会暴露。第二段换成三句议题、句号和逗号都有,坑马上就出来了。以后验这类接口,样片得多备几种:单句、多句、带数字、停顿多的口语,各跑一遍再下结论。

两个坑的共性是同一件事:ASR 返回的文本层结构,是为转写结果服务的,不是为字幕切分服务的。指望它替我断句,指望不上。

救场的是那 280 毫秒

回头看词级时间戳那张表,答案一直摆在那里。

词 起(ms) 止(ms)
今天 160 400
下午 400 680
3 680 840
…… …… ……
会 2480 2640
记得 2920 3200
报表 4160 4440

"会"止于 2640ms,"记得"起于 2920ms,中间空了 280 毫秒。其余相邻词之间的间隔都是 0。

原文那个位置正好是逗号。说话人在逗号处换了一口气,音频里表现为一段静音,词级时间戳把这段静音如实记成了两个词之间的空隙。

所以断句不用问文本层,问时间层:相邻词的间隔超过阈值就断一条。我这轮阈值取 250ms,落在"逗号处 280ms"和"其余 0ms"之间,跑出来的 SRT 是:

1
00:00:00,160 --> 00:00:02,640
今天下午3点在第二会议室开选题会

2
00:00:02,920 --> 00:00:04,440
记得带上周的数据报表

两条 cue,断点正好落在原句逗号处。这份 SRT 是这轮跑出来的真实产物,两条的起止毫秒直接取自词级字段,我没有手工挪过任何一个时间点,cue 文本就是词面拼接的结果。

回头看这个过程,切字幕条这件事被我绕了一大圈:先想靠标点,标点不在词级;再想靠句段,句段会并段。真正管用的信号一直是最不像"文本"的那个,两个词之间的静音长度。

阈值这块得补一句:250ms 是这轮素材校出来的,不是通用常数。这轮的间隔分布很干净,除了逗号那 280ms 全是 0,取中间值很容易。换成语速快、停顿短的素材,250ms 可能一条都切不出来;换成停顿多的口语,又会被切得碎。我的办法是拿一段自己已知标点的素材先跑一遍,把相邻词间隔全列出来,看标点处和非标点处分别落在什么区间,再定阈值。

这轮学到的三件事

一是往返实验值得做。原文在手上,转写差异和时间戳粒度都能查实,比拿一段来历不明的录音试要踏实得多。

二是别把 ASR 的文本层结构当字幕结构用。词级没标点,句段会并段、标点会被规范化,两条路我都试过了,都不通。

三是时间戳里的"空隙"是有信息量的。280 毫秒的静音对应的就是那个逗号,比任何标点都可靠,因为它来自音频本身。

边界,写清楚

  • 数字规范化:三→3 是 ASR 的输出约定,不是错误,字幕文案要哪个写法自己定
  • 词级无标点:按标点切条不成立,静音间隔切分是本文方法,阈值需按素材自校
  • 句段会并段:多句素材的句段数不等于原句数,别拿句段当字幕条
  • 说话人分离未实测:--diarization 这轮没跑(素材是单说话人),多人会议里谁说了什么的效果不在本文结论内

还有一条:这轮的音频是 TTS 合成的,干净、单说话人、没有底噪也没有抢话。真实会议录音的条件差得多,切分表现会变,拿自己的素材重跑再定参数。

想试的话

三步就够:合成一句(或者直接拿你手上的一段录音)、bl speech recognize 转写、用词级时间戳的静音间隔切你的字幕轴。Key 在控制台密钥管理页领,音色和模型清单在阿里云百炼 CLI(bl)那一侧。

跑完建议先做一件小事:把转写全文和原文逐字对一遍,看数字写法差在哪。这种对照做多了,你就知道哪些后处理规则值得写进自己的字幕流水线,哪些根本不用管。

posted @ 2026-10-07 22:39  新的白白酱  阅读(13)  评论(0)    收藏  举报