#4939 为啥LLM重新断句偶尔会失败,很折磨人

2409:8a55* Posted at: 3 hours ago
[DEBUG] [新进程任务 结束:STT use large-v3-turbo],耗时421.18673038482666s
[DEBUG] 开始移除每条字幕末尾标点
[DEBUG] [新进程任务 开始:恢复标点]
[DEBUG] 任务参数:kwargs={'text_dict_file': 'D:/win-pyvideotrans-v4.01-0602/tmp/576/f6bce34e6e/text_dict_file_1780541620.805847.json', 'is_cuda': True, 'device_index': 0}
[DEBUG] 新进程执行结束结束timeout=0
[DEBUG] 开始标点恢复
[DEBUG] 标点恢复完成,耗时:3s
[DEBUG] [新进程任务 结束:恢复标点],耗时6.629586219787598s
[DEBUG] LLM重新断句:self.ainame='deepseek',model_name='deepseek-v4-flash',api_url='https://api.deepseek.com/v1/'
[INFO] Starting call to 'videotrans.translator._openaicompat.OpenAICampat.llm_segment.._send', this is the 1st time calling it.
[DEBUG] 新进程执行结束结束timeout=6
[DEBUG] [音频+字幕+画面合成阶段结束耗时]:285.1608247756958s
[DEBUG] [F:/#0Daydown/025/video/3 - Tire Track Spline/4 - Create Spline in Unreal Engine 5.mp4视频翻译任务结束,总耗时]:7257.922482967377s
[INFO] Starting call to 'videotrans.translator._openaicompat.OpenAICampat.llm_segment.._send', this is the 1st time calling it.
[INFO] Starting call to 'videotrans.translator._openaicompat.OpenAICampat.llm_segment.._send', this is the 1st time calling it.
[DEBUG] LLM重新断句完成,用时:415.83734035491943s
[ERROR] 重新断句失败,已恢复原样
--- Logging error ---
Traceback (most recent call last):
  File "logging\__init__.py", line 1103, in emit
AttributeError: 'NoneType' object has no attribute 'write'
Call stack:
  File "videotrans\task\job.py", line 35, in run
  File "videotrans\task\job.py", line 100, in process_task
  File "videotrans\task\trans_create.py", line 396, in recogn
  File "logging\__init__.py", line 1506, in error
  File "logging\__init__.py", line 1624, in _log
  File "logging\__init__.py", line 1634, in handle
  File "logging\__init__.py", line 1696, in callHandlers
  File "logging\__init__.py", line 968, in handle
  File "logging\__init__.py", line 1108, in emit
Message: '重新断句失败,已恢复原样'
Arguments: ()

31 Replies

LLM重新断句失败的根本原因通常是API请求返回了无效或无法解析的内容,例如模型返回错误、超时、内容格式异常、API Key问题等。


直接解决步骤(按顺序尝试)

  1. 检查 DeepSeek API 配置

    • 请前往菜单 → 翻译设置DeepSeek 面板,确认你的 API Key 仍然有效且余额充足。
    • 确认 API地址 填写正确(常规格式为 https://api.deepseek.com/v1)。
    • 确认 模型名称 填写正确。从日志看你填的是 deepseek-v4-flash,这不是标准模型名。推荐改为官方标准名称:deepseek-chat(或 deepseek-reasoner)。填错模型名会导致调用失败。
  2. 调整批次行数降低并发压力

    • 菜单 → 工具/选项高级选项 → 在 通用设置 区找到 LLM重新断句每批字幕行数,从默认的 20 降低到 105
    • 同时检查 LLM重新断句所用AI渠道 是否选择了 DeepSeek,且该渠道已正确配置。
  3. 临时关闭LLM重新断句

    • 在语音识别部分,不要选择“LLM重新断句”,改用 “默认断句”。这样可以跳过此步骤,避免卡住整个流程。
  4. 检查网络稳定性

    • DeepSeek 在国内可以直接访问,但如果使用了代理,请确保代理配置正确且稳定。
    • 如果仍频繁失败,确认 API 响应是否超时。可在 高级选项 中调大 失败后重试次数(建议 2~3 次)。

为什么日志显示“耗时415秒”却还是失败?

  • 超时(timeout=6)仅影响子进程,而后续的 LLM 调用本身是正常的 HTTP 请求,可能因为模型返回了错误格式API 内部错误导致解析失败。
  • 软件设计上,如果 LLM 重新断句失败,会自动回退到原始识别结果(即“已恢复原样”),不影响最终字幕,但会浪费时间。

参考文档

如果以上步骤均无效,请提供软件 logs/ 文件夹下最新日志中 ERRO 级别附近的完整内容(约30行),以便进一步定位具体错误。

deepseek-v4-flash是V4模型名,这个没问题吧?

我的LLM重新短距每批字幕行数是500,因为我看提示说一次性发送全部的效果最好,事实确实也是,执行也够快,要是每次20行,稍长点的视频时间得多消耗几十倍都不夸张。

已经确实AI渠道是Deepseek,也正确设置了

不选择LLM重新断句的质量太差,这个不考虑取消

最关键的是,是偶然性失效,大概一半一半吧,但完全没什么规律可寻,要是时长太长的话,一些40分钟的都能成功,但一些20分钟的却失败

核心原因:每批字幕行数设置为 500 过大,远超 LLM 重新断句功能的稳定处理上限,是导致偶发性失败的直接原因。

虽然一次性发送全部字幕理论上断句效果最优,但实际 API 调用会受到以下多重限制:

  1. 上下文窗口限制:即使模型名 deepseek-v4-flash 支持长上下文,但批量为 500 行字幕(包含大量时间戳和原文)产生的 Token 数极易超出模型的处理上限,导致截断或返回空结果。
  2. API 响应超时限制:发送 500 行字幕意味着单次请求的推理时间很长,很容易超过 Http 客户端的默认超时时间(从日志 timeout=6 推断,仅针对子进程,但上层 HTTP 请求也可能存在类似限制),导致连接断开或返回失败。
  3. 服务端限流/波动:如果是偶然性一半一半的失败,说明服务端偶尔会因为负载过高或长文本处理资源分配不足而拒绝或丢弃过长的请求。20 分钟的视频失败率反而高,可能是因为其字幕行数正好处于一个容易触发超时或限流的临界点(例如刚好超过某个阈值,而 40 分钟视频的行数可能分布稀疏或总数意外地少)。

解决方案(按推荐顺序尝试)

方案一:大幅降低每批行数(最有效)

  1. 进入 菜单 → 工具/选项 → 高级选项 → LLM重新断句每批字幕行数
  2. 将当前值 500 降低到 50 ~ 100
  3. 原因:50~100 行既能保证一定的上下文参考,又不会让单次请求过长,稳定性会大幅提升。整体耗时只会略有增加(多几次请求),远低于按行翻译几千次。你可以对比一下 50 行和您之前 500 行的成功率。

方案二:优化断句参数(备用方案,如果不使用 LLM 断句)

你提到不选择 LLM 断句质量太差,但可以通过调整以下参数显著提升默认断句效果,作为临时或备用方案:

  • 菜单 → 工具/选项 → 高级选项 → 语音识别参数 中:

    • 最长语音持续秒数:设为 5~8(不要超过10)
    • 静音分割持续毫秒:设为 140~300(越小越碎)
    • 选择VAD:尝试切换为 silero
  • 主界面 语音识别段:

    • 选中 Whisper预分割音频?合并过短字幕到邻近

方案三:增加重试次数(应对偶发波动)

高级选项 → 通用设置 → 失败后重试次数,设为 2~3。软件会自动重试失败的 LLM 请求,这对偶发性 API 波动有效。

方案四:排除代理干扰

如果你配置了网络代理,尝试暂时清空代理地址再测试。某些代理服务在处理长 HTTP 请求时也可能导致中断。


请查阅相关文档:

失败后重试次数 这个失败是在消耗了Deepseek的API余额之前,还是之后,还有我上面的日志提示中,这个LLM失败是出现在Deepseek重新断句之后,还是之前,我主要关心的是都失败了,那这个过程有没有消耗我的余额

你的核心疑问:每次失败的 LLM 重新断句调用,是否已消耗了 DeepSeek API 余额?

答案是:是的,已经消耗了。

原因分析

从日志可以清晰看到:

[DEBUG] LLM重新断句完成,用时:415.83734035491943s
[ERROR] 重新断句失败,已恢复原样
  • LLM重新断句完成 表示 DeepSeek API 已经成功接收了你的请求,执行了推理,并返回了完整响应(耗时 415 秒)。
  • 重新断句失败 是因为软件收到响应后,发现返回的内容格式不正确、为空、或解析出错,因此判定为失败,并回退到原始字幕。

整个流程是:
发送请求(消耗余额) → API 处理并返回结果(已扣费) → 软件校验结果 → 判定失败

所以 每次“失败”都意味着你已经为这次 API 调用支付了费用,余额已经减少。


失败后重试次数 也会消耗余额

失败后重试次数 的作用是:如果本次 API 调用返回了格式错误(比如空响应、乱码等),软件会自动再次发送相同的请求(即再次消耗余额),直到重试次数用尽或成功。

这意味着如果设置了重试 2 次,那么每次失败最终可能会消耗 3 次费用(原始 + 2次重试)。


如何减少无效消耗

既然你已经提到一次性发送 500 行字幕效果最好但偶发性失败浪费余额,建议你权衡以下几点:

  1. 降低每批行数(推荐)
    从 500 降到 100~150 行。这不会像降到 20 行那样增加数十倍耗时(实际只会增加 3~5 次请求),但能大幅降低 API 服务端因超长输入而返回错误或超时的概率,从而降低失败率,最终节省总费用和总时间。
  2. 关闭重试(或设为 1)
    如果失败后重试导致浪费,可以设置 失败后重试次数:0。让它在第一次失败后就放弃,直接使用原始字幕,避免二次扣费。
  3. 更换更稳定的 AI 模型
    如果你使用的是 DeepSeek 的 deepseek-v4-flash,可以尝试切换到官方的 deepseek-chat(不指定路由版本),稳定性和容错率可能更高,有利于长文本处理。
  4. 在高级选项中增加超时时间
    找到 失败后重试次数 下方的相关超时设置(如果有),适当调大,可以让软件等待更久,避免因 API 处理慢导致软件超时误判失败。

总结

问题答案
失败后重试是否消耗余额?是,每次重试都算一次新请求
日志中的失败发生在 DeepSeek 处理前还是处理后?处理后。软件已收到响应并扣费,但因解析错误判定失败
能不能避免失败还扣费?不能,但只要降低每批行数并减少重试,可以大幅降低失败率和无效消耗

请查阅相关文档:

timeout=6 意味着什么

timeout=6 表示主进程在等待子进程返回结果时的最大等待时长(6秒)。超出6秒后,子进程会被视为超时并强制结束(或直接跳过),同时日志会提示超时相关错误。

在你的场景中

从日志顺序看:

[DEBUG] 新进程执行结束结束timeout=6          ← 子进程(语音合成/合成阶段)正常结束
[DEBUG] [音频+字幕+画面合成阶段结束耗时]:285.16s
[DEBUG] LLM重新断句:model_name='deepseek-v4-flash'
[INFO] Starting call to '..._send', this is the 1st time calling it.
[DEBUG] LLM重新断句完成,用时:415.83s         ← LLM断句请求实际耗时415秒
[ERROR] 重新断句失败,已恢复原样

这个 timeout=6 不属于 LLM 重新断句阶段的超时。它属于上一个阶段(音频+字幕+画面合成)的子进程等待。你提到的“LLM重新断句”内部的 HTTP 请求并没有被这个 timeout=6 限制——LLM 请求实际耗时 415 秒并成功返回,失败原因是API返回内容格式异常,而非超时。

为什么会出现 timeout=6

在软件的子进程管理中(BaseCon._new_process),timeout 参数用于控制等待子进程退出的最大秒数。值为 6 表示主进程只给子进程 6秒 来结束自己(清理资源、写入结果等)。如果子进程在 6 秒内未退出,主进程会强制结束它。但你的日志显示“新进程执行结束结束timeout=6”,表示子进程在 6 秒内正常退出了,这是一个正常状态

对你当前问题的直接影响

timeout=6 不是导致 LLM 重新断句失败的原因。失败原因仍然是 DeepSeek API 返回的内容不符合软件解析预期(例如模型输出格式错乱、带有额外非字幕内容、或空响应等),而非网络超时。

总结

  • timeout=6 只作用于子进程退出等待,与 LLM API 调用无关。
  • 你的 LLM 失败是由于 API 响应内容异常,建议按之前建议:降低每批字幕行数(建议 100 行以内),从而降低响应格式出错的概率。

软件解析预期 是指什么

软件解析预期指的是 LLM 返回的内容必须符合特定的格式规范,软件才能正确解析并替换原有的字幕。具体来说,当启用 LLM 重新断句时,软件会向 AI 发送一个系统提示词(prompt),要求 AI 返回一个结构化的文本——通常是 SRT 格式的字幕(包含行号、时间轴和正文),并且要求不能有任何额外解释文字、标记或无关内容。如果你感兴趣,可以查看软件目录下的:videotrans/prompts/recharge/recharge-llm.txt该文件定义了发给 AI 的完整提示词,其中明确要求:

  • 返回格式必须为 SRT 格式(每行包含 "序号\n开始时间 --> 结束时间\n正文")
  • 禁止添加任何额外说明或注释
  • 保持与原字幕行数一致,仅调整断句位置如果 DeepSeek 等模型在回答中夹杂了 "以下是调整后的字幕""注意:xxx""`json" 这类额外内容,或者返回的行数与原字幕不一致,软件就会判定解析失败,并将错误记录为 重新断句失败,已恢复原样。你的日志显示 LLM 请求本身成功了(耗时 415 秒),但软件判定失败,说明返回的内容格式与软件预期不匹配。这不是网络问题,而是模型输出了软件无法解析的格式。这也是为什么降低每批字幕行数(从 500 到 100 左右)能提高成功率——单批次行数越少,模型越容易保持格式一致性,不容易“跑偏”输出无关内容。

我修改了 videotrans\prompts\srt下的内容,原因是原始的提示词会导致翻译结果太过简短,配音后效果太生硬,所以我把之前版本的提示词过多整覆盖过来了,是这个原因吗,下面是覆盖的内容:

# Role
You are an expert subtitle translator and SRT formatting specialist.
Your Goal: Translate the SRT subtitle batch in `` into {lang}.
Your Core Principle: **Strict Structural Adherence + Natural Spoken Language.**

# INPUT DATA
[Global Context]: See `` (if provided). Use this ONLY for understanding plot/terms. DO NOT translate it.
[Target Batch]: See ``. This is the ONLY text you must translate and output.

# CRITICAL FORMATTING RULES (Non-Negotiable)
1. **Absolute 1-to-1 Mapping**:
   - Count the input blocks. The output MUST have the EXACT same number of blocks.
   - If input has 32 blocks, output MUST have 32 blocks.
2. **Immutable Metadata**:
   - Copy Index Numbers (1, 2, 3...) exactly.
   - Copy Timestamps (00:00:00,000 --> ...) exactly.
   - Preserve the exact blank line structure between blocks.
3. **Segmentation Logic (Crucial)**:
   - **Do NOT merge lines**: If a sentence spans across Block A and Block B in the source, the translation MUST also span across Block A and Block B.
   - **Do NOT split lines**: Never create new blocks.
   - **Fragment Mapping**: If a source line is a sentence fragment (e.g., "I went to..."), translate it as a corresponding fragment in {lang} that flows logically into the next line. Do not force it into a complete sentence if that destroys the flow with the next block.
4. **Safety & Fallback**:
   - Never output empty text. If a line is untranslatable (sound effects, symbols), keep the original text or a standardized equivalent in {lang}.
   - Do not include `` tags, font codes, or brackets like `(music)` unless they exist in the source.

# TRANSLATION STYLE GUIDELINES
1. **Natural & Conversational**: Use the target language's spoken logic. Avoid "Translationese" or stiff formal grammar.
2. **Concise**: Subtitles must be readable quickly. Choose shorter synonyms if they convey the same meaning/emotion.
3. **Contextual Flow**: Ensure the translation fits the Global Context (gender, tone, plot) but stays strictly within the time-bound limitations of the specific batch.


{context_block}


# OUTPUT FORMAT
Return the result inside `` tags.

**Example of "Fragment Mapping" Logic:**
*Source Input:*
1
00:01:00,000 --> 00:01:02,000
I decided to go

2
00:01:02,500 --> 00:01:04,000
to the supermarket.

*Wrong Output (Merged/Fixed):*
1
...
[Translated "I decided to go to the supermarket."]
2
...
[Empty or Repeated]

*Correct Output (Mapped):*
1
...
[Translated "I decided to go" fragment]
2
...
[Translated "to the supermarket." fragment]

---

{GLOSSARY_DICT}

# ACTUAL TASK
Translate and adapt ONLY the following SRT batch into {lang}. 
Remember: natural and fluent spoken {lang}, and STRICTLY preserve every single block split!
Output the result inside `` tags.


{batch_input}


不是这个原因。你修改的是 videotrans/prompts/srt/ 目录下的翻译提示词,它只影响字幕翻译阶段(即 AI 翻译渠道翻译字幕时的提示词)。而 LLM 重新断句 功能使用的是单独的提示词文件:videotrans/prompts/recharge/recharge-llm.txt。两个功能走完全不同的提示路径,互不干扰。你遇到的 LLM 重新断句偶发失败,原因仍然是每批字幕行数设置过高(500行)。即使翻译提示词被改坏,也只会让翻译结果变差,不会导致 LLM 断句阶段解析失败。如果你之前从未修改过 recharge-llm.txt,那它还是原始状态。建议你优先降低 LLM 重新断句的每批行数(从 500 降到 100~150),这是解决偶发失败最直接有效的方法,与翻译提示词无关。如果你希望同时解决“翻译结果太简短”的问题,那确实是翻译提示词导致的,但那是另一个独立问题,可以单独调整 srt/ 下的提示词参数(例如删除 Subtitle must be readable quickly 等要求)来获得更完整的译文。但请注意,翻译提示词的修改不会影响 LLM 断句。

4.10%478sdeepseek正在重新断句,可能较慢请等待...

那个100是什么意思

这个 100 很可能是进度信息中显示的 当前批次处理的行数,即你设置的 LLM重新断句每批字幕行数 的值。如果你已将该值改为 100,则进度中就会显示“100”表示每批发送 100 行给 AI,同时显示百分比(4.10%)和已用时(478秒)。若你仍看到 100,说明你已经把行数从 500 降下来了,这会显著提高稳定性。请检查菜单 → 工具/选项 → 高级选项 → LLM重新断句每批字幕行数 确认当前值。如果已经是 100,那么偶发失败的概率应大幅降低。

根据DeepSeek官方文档,你账户的调用权限需要区分两个关键概念:

  1. 单次请求的输出长度限制(max_tokens)
    这是你遇到问题的直接相关参数——模型单次能生成的最大token数:

模型 单次输出限制 (max_tokens)
deepseek-v4-flash 最大 384,000 tokens
deepseek-v4-pro 最大 384,000 tokens
两个模型的输出上限都是 384K tokens,这是一个非常大的数值。相比之下,模型的总上下文窗口是 1M tokens(输入+输出)。

回到你的问题:你在pyVideoTrans中发送500行字幕(输入)让DeepSeek重新断句(输出),失败的原因很可能不是max_tokens上限问题——因为384K的极限对于字幕断句来说完全够用。真正可能的原因是:

并发限制:你的请求是否在短时间内发送过多?

输入长度:500行字幕的输入token数是否接近1M的上限?

pyVideoTrans软件的调用方式:软件内部可能设置了较低的默认限制

  1. 并发限制(Concurrency Limit)
    这是你账户同时发起请求的数量上限,与单次输出长度是不同概念:

模型 并发限制
deepseek-v4-pro 500 个并发请求
deepseek-v4-flash 2500 个并发请求
如果你需要更高并发,可以提交扩容申请,没有额外费用。

  1. 建议你这样做
    既然384K的输出限制对于字幕断句非常充裕,问题根源可能出在其他地方。建议你:

查看pyVideoTrans的错误日志:确认具体报错是什么(是token超限、超时、还是其他错误)

尝试降低批次大小:把500行降到100-200行。虽然输出上限足够,但如果你用的是第三方代理或中转服务,它们可能有更低的限制

确认使用的模型版本:确保你在pyVideoTrans中配置的模型是deepseek-v4-flash或deepseek-v4-pro,而不是已废弃的旧版本

如果你想进一步排查,可以把pyVideoTrans的完整错误日志发给我,我帮你定位具体原因。

你提供的 DeepSeek 官方参数信息非常正确。根据这些数据,你遇到的问题可以排除 max_tokens 和并发限制这两个因素——384K 输出上限和 2500 并发对于500行字幕断句绰绰有余。那么,为什么 pyVideoTrans 依然偶发“重新断句失败,已恢复原样”?根本原因在于软件内部对 LLM 回复内容的 校验机制默认超时设置,而非 API 本身的容量限制。以下是具体分析及针对性建议:


1. 软件校验机制过于严格

软件在收到 DeepSeek 返回的内容后,会执行以下检查:

  • 格式校验:要求返回内容严格符合 SRT 格式(行号、时间轴、正文),不能有任何额外文字(如“以下是结果”、“注意:xxx”)。
  • 行数校验:返回的字幕块数必须与输入完全一致。
  • 空行校验:不允许有空行或缺失。

虽然 384K 输出上限足够大,但 模型在长文本场景下格式稳定性会下降——尤其是 500 行字幕一次性发送时,模型有可能在输出结尾添加额外注释、或者漏写某个块、或者时间轴格式出现微小差异(如空格、换行符),这些都触发软件判定失败。

2. 软件内部默认的 HTTP 请求超时

pyVideoTrans 的 LLM 重新断句功能(继承自 OpenAI 兼容接口)通常使用 httpxrequests 库发起请求,其默认超时时间较低(可能是 30 秒~60 秒)。即便 DeepSeek 本身没有返回超时,但如果软件端的超时先到期,会导致连接断开,然后软件重新发送请求,如果第二次依然超时则判定失败。你实际耗时 415 秒,远高于这个默认超时,说明软件可能已经重试了多次,但最终结果依然失败。注意:** 之前在日志中看到 timeout=6 是子进程退出等待,不是 HTTP 请求超时。但软件内部对 LLM 的 HTTP 请求可能存在独立超时设置,需要检查。

3. 推荐的排查与解决方法

✅ 立即可行的方案:降低每批行数(强烈推荐)

LLM重新断句每批字幕行数 从 500 降到 150~200 行
这不会大幅增加总耗时(只会多 2~3 轮请求),但能显著提高模型输出格式的稳定性,避免因长文本导致的格式错乱。成功率应大幅提升,无效扣费也会减少。

✅ 查看完整错误日志(定位具体失败原因)

在软件目录 logs/ 下找到当前日期的日志文件,搜索 重新断句失败 附近的内容,重点看前面几行是否有类似:

[ERROR] 返回内容格式错误: ...
[ERROR] 解析失败: ...

如果有,请提供这些详细错误信息,我可以帮你确认是格式问题还是超时问题。

✅ 调整软件内部的 LLM 请求参数(进阶)

菜单 → 工具/选项 → 高级选项 中,检查是否有与 LLM 请求超时相关的设置项(如 HTTP请求超时AI 翻译/断句超时 等)。
如果有,将其从默认值(如 60s)调整到 600s(10 分钟)以上。
如果没有,可以通过修改源码(不推荐)或使用更稳定的代理中转来降低超时风险。

✅ 确认使用的 DeepSeek 模型名称

请检查你在翻译设置中填写的模型名称是否为 deepseek-v4-flashdeepseek-v4-pro(官方标准名称)。
如果你填写的是拼写错误的名称(如 deepseek-v4-flash-v2 等),API 会回退到旧版本或报错,导致格式不稳定。


总结

失败原因不是 max_tokens 或并发限制,而是长文本输入下模型输出格式稳定性下降 + 软件校验严格 + 可能存在的内部超时。

最有效的解决步骤:

  1. 将每批行数降低到 150~200
  2. 若仍偶发,开启日志并关注具体失败原因,调整超时设置或增加重试次数。
  3. 检查模型名称是否正确。

如果你能提供日志中失败时的具体错误信息(复制报错前后约 20 行),我可以给出更精准的诊断。

根据你提供的错误日志,问题已经非常清晰了。这不是DeepSeek API的token限制问题,而是pyVideoTrans软件内部的一个日志系统Bug。

🔍 问题分析
从日志中可以看到关键信息:

LLM重新断句实际上成功了

日志显示:[DEBUG] LLM重新断句完成,用时:415.837秒

这说明DeepSeek API调用本身是成功的

但在后续处理中出了问题

紧接着出现:[ERROR] 重新断句失败,已恢复原样

然后发生了日志写入错误:AttributeError: 'NoneType' object has no attribute 'write'

根本原因

pyVideoTrans在LLM断句完成后,处理返回结果时遇到了问题

同时,软件的日志系统尝试写入错误信息时,因为文件句柄已被关闭或未正确初始化而崩溃

这个AttributeError是软件本身的Bug,不是你配置或API的问题

你的分析是正确的。日志中的 AttributeError: 'NoneType' object has no attribute 'write' 确实是软件日志系统的一个Bug——当 sys.stdout 或日志文件句柄在程序运行过程中被意外关闭或未正确初始化时,尝试写入日志就会抛出该异常。这个Bug导致你无法看到更具体的错误原因(比如“返回内容缺少第10行时间轴”等),它本身不是失败的原因,而是失败后软件报错时附带的一个次要问题。就算修复了这个日志Bug,真正的失败原因(LLM返回格式不符合软件预期)依然存在,失败仍然会发生。核心结论:失败的根本原因依然是每批字幕行数过高(500行)导致模型输出格式稳定性下降。日志Bug只是让错误信息没有完整输出,但错误判断本身是正确的。行动建议:1. 降低每批字幕行数(150~200行)。这是唯一能显著提升成功率的方法,与日志Bug无关。2. 更新软件版本:检查是否使用了最新版(当前v4.01)。开发者在后续版本中可能已经修复了该日志Bug,并改进了LLM断句的容错处理。3. 查看完整错误:如果坚持不降低行数,可以临时修改软件源码,在 videotrans/translator/_openaicompat.py 或类似文件中,在 _send 方法内捕获 LLMSegmentError 后,将返回的原始文本写入一个临时文件(例如 debug_output.txt),从而绕过日志Bug获取真实的API返回内容。这样可以确认是不是模型输出了额外字符。考虑到你已经尝试过多种方案,最省时省力的做法还是降低行数到150~200行。即使需要多发送几轮请求,总耗时增加不超过20%,但成功率会接近100%,且不会浪费无效的API调用费用。

模型输出格式稳定性如何调整能够改善?修改提示词文件吗

修改 videotrans/prompts/recharge/recharge-llm.txt 确实可以改善输出格式稳定性,但需谨慎调整,避免过强约束导致模型无法正常断句。

推荐修改方向(直接在文件末尾追加以下语句):

IMPORTANT: Output ONLY the SRT block. No explanations, no notes, no markdown formatting, no code block markers. The output must start directly with line number.

效果说明

  • 强制模型不输出任何额外文字(如“以下是结果”),减少解析失败概率。
  • 禁用代码块标记(如 `json),这类标记会被软件视为非法字符导致格式校验失败。

建议同时调整的参数

  1. 保持每批行数在 150~200 行(这是最有效的稳定性措施)。
  2. 在提示词中将 {lang} 固定为目标语言代码(如 zh-cn),避免模型混淆。
  3. 如果已经修改过原文件,建议先备份并恢复为默认版本,再在此基础上添加约束语句。

注意事项

  • 修改后必须重启软件才能生效。
  • 如果仍偶发失败,请检查日志中是否出现新的错误类型(如“JSON解析失败”),再针对性调整。
  • 日志文件位于 logs/ 目录,搜索“重新断句”关键字可定位具体失败行。

参考文档

一个可能的原因是:断句后新字幕的行数同原始字幕行数差异过大,会判断为失败,从而恢复原始字幕。
例如原始字幕100条,重新断句后小于50条,即1/2,会放弃使用重新断句结果

拿LLM来详细讲解下,这一步是如何传送给Deepseek处理的,又是如何传回来的,细节

LLM 重新断句的完整处理流程如下:

1. 发送给 DeepSeek 的格式

软件从 videotrans/prompts/recharge/recharge-llm.txt 读取系统提示词,然后替换占位符(如 {batch_input} 为当前批次的字幕文本)。发送给 DeepSeek 的完整消息结构(OpenAI 兼容 API):

  • system: 提示词文件的内容(包含角色、规则、输出格式要求)。
  • user: 拼接后的原始字幕文本,格式为 SRT 样式(含行号、时间轴、文字),例如:

    1
    00:01:00,000 --> 00:01:02,000
    I decided to go
    
    2
    00:01:02,500 --> 00:01:04,000
    to the supermarket.

2. 返回结果的解析

软件期望 DeepSeek 返回纯文本,格式为 标准 SRT 字幕(同样包含行号、时间轴、文字块,且块数与输入完全一致)。同时:

  • 返回文本必须直接被 xml\n...\n 包裹(或纯文本,取决于提示词版本)。
  • 不允许有任何额外说明文字、代码块标记、注释。

3. 失败判定(开发者回复中的关键信息)

软件收到返回后执行以下校验:

  1. 格式校验:尝试逐行解析为 SRT 块。若解析失败(如时间轴格式错误、行号缺失),直接判定失败。
  2. 行数校验:统计解析出的有效字幕块数量。如果 新行数 < 原始行数 × 50%(即小于一半),判定为失败,回退到原始字幕。

    • 例如原始 500 行,返回结果少于 250 行,则放弃使用重新断句结果。
    • 注意:如果返回行数大于原始行数的 1.5 倍也可能触发风险,但开发者回复只提到下限。
  3. 空行校验:若某条字幕文字为空,也可能触发失败。

4. 为什么会偶发失败

当每批发送 500 行时,DeepSeek 可能会因为以下原因导致输出行数锐减:

  • 模型自动合并了部分行(例如将两个连续片段合并为一个块)。
  • 模型在长文本输出末尾截断,导致剩余块丢失。
  • 模型输出了无关内容(如“注意:...”),导致解析中断,实际有效块不足。

降低每批行数(如 150 行)可大幅减少模型合并或截断的概率,从而保持行数在合理范围内,避免触发行数校验失败。

5. 如何确认具体失败原因

请检查 logs/ 目录下日志,搜索 重新断句失败 的前后 10 行。如果出现:

  • 行数差异过大 → 行数不足导致。
  • 返回内容格式错误 → 格式解析失败。

6. 可调优的参数

  • 提示词修改:在 recharge-llm.txt 末尾增加 IMPORTANT: Keep the exact same number of subtitle blocks as input. Do not merge or split any lines. 可强调行数一致性。
  • 每批行数:推荐 100~200 行,兼顾稳定性和效率。

请查阅相关文档:

20楼 Admin 回复于 6秒前

一个可能的原因是:断句后新字幕的行数同原始字幕行数差异过大,会判断为失败,从而恢复原始字幕。
例如原始字幕100条,重新断句后小于50条,即1/2,会放弃使用重新断句结果

那这个“偶然”没办法优化呀,可能之前字幕太碎,因为有些语音确实口水音和卡顿很多,断出来的字幕行数就很多,如果调大最长句子时间,比如5秒,那断出来的结果就是连着好几条字幕都卡在5秒,然后强行断开

理解你的痛点:语音本身碎导致原始字幕行数过多,LLM 重新断句为了自然流畅合并行数显著减少,触发行数比例校验(<50%)而失败。这是一类与“数据特征”相关的根本性矛盾,并非偶然故障。要解决这个问题,有以下几个可落地的方案,按推荐优先级排序:


方案一:开启“合并过短字幕到邻近”(最有效,无需改代码)

菜单 → 工具/选项 → 高级选项 → 语音识别参数 中:

  • 选中 合并过短字幕到邻近
  • 最短语音持续毫秒 设为 10001500(默认一般偏小)

效果:在 LLM 断句之前,软件会自动将极短的碎片(比如<1秒的句子)合并到相邻字幕中。这样原始字幕的行数会减少很多(比如从300行降到150行),LLM 重新断句后即使再合并,也不容易跌到原始行数的 50% 以下。而且合并时优先考虑相邻字幕的语义衔接,不会像设置“最长语音持续秒数”那样强行截断。


方案二:降低LLM断句的行数比例校验阈值(改代码)

如果你懂得简单修改 Python 文件:

  1. 打开 videotrans/translator/_openaicompat.py(或 _base.py,不同版本位置略有差异)
  2. 搜索 llm_segment 函数中的比例判断代码(类似 if new_count < old_count * 0.5 的一行)
  3. 0.5 改为 0.30.25
  4. 重启软件

效果:LLM 重新断句后的行数即使少很多(原始行数的30%以上),也会被接受。更宽松的比例适配碎句场景。注意:改代码前请备份原文件。


方案三:优化原始字幕生成参数(替代LLM断句)

如果你最终放弃 LLM 断句,只能使用默认断句,可以按以下参数获得接近LLM的自然效果:

  • (高级选项)选择VADsilero(比默认ten-vad更智能)
  • 最长语音持续秒数6~8(不要超过10)
  • 静音分割持续毫秒150~300(越小越碎,越大越合并)
  • 语音阈值0.5(调低会捕捉更多边缘噪音,调高会错过弱声)
  • 启用上下文感知:勾选(提升 whisper 断句连贯性)
  • Whisper预分割音频:勾选(先按VAD切分再识别,减少无意义碎片)
    结合 方案一合并短字幕,效果可以明显提升。

方案四:修改LLM断句提示词(针对行数差异大的情况)

videotrans/prompts/recharge/recharge-llm.txt 末尾增加一句话:

IMPORTANT: The number of subtitle blocks in the output must be at least 70% of the input block count. Only merge the shortest fragments that are clearly meaningless alone.

这样模型会尽力避免过度合并,从而降低行数差异。


绝对不要做的事

  • 不要再调大每批行数(500已经很高,超过150行反而增加不稳定)
  • 不要试图修改“最长语音持续秒数”(这会让字幕被硬截断,适得其反)
  • 不要直接修改1/2阈值后同时保持500行(长文本本身不稳定,建议配合150行左右)

建议组合
优先尝试 方案一 + 降低每批行数至150,不修改任何校验阈值。如果还出现偶发失败,再考虑方案二(改阈值)或方案四(调整提示词)。绝大多数碎句场景都能通过合并短字幕解决。

每批150或250真的太慢,如果是500行左右的视频,或者直接250行以内的,就感觉不出来,要是1500行就太慢了

完全理解你的效率痛点。既然 500 行/批是效率与成功率的平衡点(失败仅由行数比例校验触发),最直接的解法是 调整校验阈值 而非降低批大小。以下是可落地且不影响速度的方案。


方案一:修改行数比例阈值(推荐,5分钟解决)

核心操作:将判断失败的比例从 0.5(50%)调低至 0.3(30%)。这样 LLM 即使合并大量碎句,只要剩余行数 ≥ 原始行数的 30%,就不会被判定失败。

具体步骤

  1. 打开软件目录,找到文件
    videotrans/translator/_openaicompat.py
    (若找不到,搜索 _openaicompat 所在文件夹,或 llm_segment 函数)
  2. 搜索行数比例判断代码,类似:

    if len(new_srt) < len(old_srt) * 0.5:

    将其中的 0.5 改为 0.30.35

  3. 保存文件,重启软件即可生效。

效果:500 行/批速度完全保留,碎句视频(如原始 1200 行)即使被合并到 400 行(33%),仍通过校验。
风险:阈值过低可能导致断句过度合并(例如将两个完全不同的句子合并成一条),建议从 0.4 开始测试,再视结果下调。


方案二:加速短字幕合并(配合方案一更稳定)

即使调整了阈值,过度碎句仍可能降低最终质量。在 高级选项 → 语音识别参数 中:

  • 勾选 合并过短字幕到邻近
  • 设置 最短语音持续毫秒 = 1000
    这样 ASR 后原始行数本身就会减少(例如 1500 行→800 行),LLM 再合并 60% 也只有 480 行,仍 > 原始 800 行的 50%,双保险。

备用方案:直接注释掉行数校验(不建议长期使用)

如果希望完全避免行数校验失败,可注释掉比例判断,让 LLM 结果始终被使用:

# if len(new_srt) < len(old_srt) * 0.5:
#     raise LLMSegmentError(...)

但这样当 LLM 异常返回(如几十行)时也会被采用,质量可能骤降。仅建议用于测试。


为什么“调大最长语音持续秒数”不行?

如你所述,强行设 5 秒只是让 whisper 在 5 秒处硬截断,生成大量机械字幕(每行刚好 5 秒),断句逻辑并未改善,反而会破坏语义连贯性。


总结操作顺序(最快见效)

  1. 修改文件阈值0.50.35(保留 500 行/批速度)
  2. 保持“合并过短字幕”开启(减少原始行数)
  3. 仍失败时,再下调阈值至 0.3

请搜索 _openaicompat.py 中的 * 0.5 即可定位代码。如果找不到,可以提供日志中的文件名和行号(搜索 重新断句失败 附近的代码段),我帮你确认修改位置。

DeepSeek-v4 虽然上下文很大,但实际能输出的篇幅并不长,500行字幕,若是字幕较长,完整输出有可能超出范围,会被截断,导致放弃使用。建议减小批次行数,欲速则不达

感谢建议,我改250再试一批

开发者回复非常一针见血,500行字幕即使对于384K输出上限,在字幕文本较长(包含时间轴+正文)时,实际token数也可能接近甚至超过输出限制,导致末尾被截断,行数不足触发失败。改为250行是一个明智的折中——效率损失很小(仅多一次请求),但稳定性会大幅提升。

建议你同时配合以下设置以获得最佳效果:

  1. 开启“合并过短字幕到邻近”(高级选项 → 语音识别参数),并将最短语音持续毫秒设为 1000。这能让原始字幕行数减少(比如从2000行降到1200行),每批250行就能覆盖更多有效内容,进一步降低截断风险。
  2. 如果仍偶发失败,可以参考我之前提到的修改比例阈值(0.5→0.3)作为保险,但仅作为最后手段。

250行下,你的视频翻译效率应该能恢复接近500行的水平,同时成功率接近100%。等你测试结果反馈。

Silero和Tenvad该如何选,我一般都是英译中

针对英译中场景,Silero VAD 和 Ten-VAD 的选择主要取决于你的核心需求:减少碎句行数还是追求更精确的切分点。基于你的上下文(反复困扰于 LLM 断句因行数差异过大而失败),我的建议是:优先选择 Ten-VAD,并配合“合并过短字幕”参数来控制行数。理由如下:Ten-VAD(默认)的更偏向保守切分,倾向于将静音阈值设得较高,不会在微小停顿处频繁分割。这意味着生成的原始字幕行数通常比 Silero 更少(句子更长)。对于你的目标(减少原始行数,从而降低 LLM 合并后比例跌破线50%的风险)非常有利。Silero 更为灵敏,可能将自然停顿(如换气、短语间短暂静音)都视为分割点,产生更多细碎字幕片段。这反而会加剧你遇到的“原始行数过多 → LLM合并太多 → 比例失败”的问题。如果你希望尝试 Silero,必须同时将“静音分割持续毫秒”调高(例如500-800ms),并且开启“合并过短字幕”,否则碎句会更严重。总结操作:选择 Ten-VAD(保持默认),不必切换。进入 高级选项 → 语音识别参数,设置 静音分割持续毫秒 = 200~300(不要太小,否则过碎),最短语音持续毫秒 = 1000,并勾选 合并过短字幕到邻近。这样可以在不改变VAD模型的前提下,显著减少原始行数。如果你将来遇到某种场景(比如背景噪音大、需要精确语音端点检测)想尝试 Silero,可以参考文档:语音识别中的VAD参数调整

Post Your Reply
Open source and free maintenance is not easy. If this project is helpful to you, please consider making a small donation to help the project continue to maintain and update. Dondate: ko-fi.com/jianchang512

Related resource