[BUG] AI 分句产出的 cue 没有长度上限,而 optimizer 只能合并不能切分
Before you report
- I have searched existing issues to avoid duplicates
- I have tried with the latest available version (or can reproduce on latest)
Current vs. Expected behavior
开启 AI 分句后会出现单条超长字幕。截图里单条占据 5~7 行、覆盖半个画面;在页面上读取 read-frog
注入的 shadow DOM 实测,单条 cue 长度为 375 字符,对比 MAX_CHARS_CJK = 30。
根因是一个隐含假设被打破了。optimizeSubtitles 的设计输入是词级碎片,碎片天然比目标短,
所以它只需要"往上凑",不需要"往下切" —— 代码里因此只有合并操作,没有任何 split。这在原
路径上是成立的。
AI 分句把这个前提颠倒了:它输出的是完整句子,可能天生就超过上限,而下游没有任何手段处理。
具体证据:
DEFAULT_SUBTITLES_SEGMENTATION_SYSTEM_PROMPT的 rule 1("Complete sentences only")和 rule 2("Never split at incomplete clauses ... MUST be merged")全是合并压力,没有任何 一条设置长度上限。一个长复合句就能产出 200+ 字符的 cue。optimizeSubtitles→processSubtitles里的wouldExceedLimit只触发flushBuffer(), 即"在追加下一个输入片段之前要不要先收尾"。如果单个输入片段本身已经超长,它照样被无条件 push 进空 buffer,下一轮原样吐出。rebalanceToTargetRange同理,只有mergeSegmentPair,且只在currentLength < min时 触发。isQualityPoor(>20% cue 超 250 字符时启用 pause detection)这层兜底对此无效:pause detection 同样只改变输入片段之间的 flush 边界,对单条超长片段是 no-op —— 恰好在它本该 生效的场景失效。
结果是 MAX_WORDS = 15 / MAX_CHARS_CJK = 30 在开启 AI 分句时退化成只参与合并判断、但从
不被强制执行的装饰性常量。
预期行为: 无论上游产出什么长度,进入渲染的 cue 都应受 MAX_WORDS / MAX_CHARS_CJK
约束。
与 #2099 / #2101 的关系: 这条路径上不存在任何长度约束,所以降低 temperature(#2099)只能减 轻、不能消除该问题。截图里「上下两段都是中文」则由 #2101 解释(AI 分句阶段模型翻译了原文), 与本 issue 的超长问题是彼此独立的根因。三者修法各不相同。
To Reproduce
- 配置任意 LLM provider,开启 AI 分句。
- 播放一个带 word-level 自动字幕(
scrolling-asr格式)、且语速较快长句较多的视频(讲座、 播客类最容易触发,口语 vlog 反而不明显)。 - 观察到单条字幕占据三行以上。
触发前提是送进模型的输入为词级。开启 AI 分句时
utils/subtitles/fetchers/youtube/index.ts:425会直接return parseStandardSubtitles(...), 绕过parseScrollingAsrSubtitles。实测同一视频同一份 timedtext:开启时 6468 条 fragment (平均 4.4 字符),关闭时 375 条(平均 79.9 字符)。人工上传字幕的视频输入是句级的, 不容易触发。
Which area(s) are affected? (Select all that apply)
Other
Extension version (if applicable)
- Extension channel/version: Chrome Web Store 1.46.0
Provide environment information
- Extension channel/version: Chrome Web Store 1.46.0Additional context
修复方向有两层。
短期:在分句 prompt 里补一条长度上限规则。改动小,但 prompt 层的长度约束模型遵守率有限, 只能算缓解。
根治:给 optimizer 加一个 split pass。难点在于切分需要发明一个原数据里不存在的时间点 —— 这也正是当初没写 split 的原因。两种做法:
- 按字符数比例在 cue 内部线性插值切分。实现简单,但对语速不均的段落会偏。
- 把词级时间戳一路保留到 optimizer(目前分句之后就丢了),切分时回查真实边界。准确,但 需要改数据流。
方案 2 和 #2099 里提的"让模型只返回句子边界索引、时间戳由代码回查"是同一个方 向 —— 都指向"词级时间戳不该在管线中途被丢弃"。如果决定往那边走,两个 issue 可以合并成一 次重构。
需要的话我可以提 PR。
Source: mengxi-ream/read-frog