Skip to content

Latest commit

 

History

History
320 lines (238 loc) · 10 KB

File metadata and controls

320 lines (238 loc) · 10 KB

VoskRecognitionService 差异分析与修正文档(更新版)

概述

本文档详细分析了 SubtitleEdit 项目与用户项目的 Vosk 识别结果差异,并说明了所做的修正。


主要差异分析

1. 后处理流程不完整(已修正)

SubtitleEdit 的完整后处理流程

SubtitleEdit 的 AudioToTextPostProcessor.Fix() 方法执行以下步骤(按顺序):

  1. 过滤无效段落 - 移除低质量的识别结果
  2. 添加句号 - 根据时间间隔和上下文添加标点符号
  3. 修复大小写 - 修正文本的大小写
  4. 修复短持续时间 - 调整过短的字幕显示时间
  5. 分割长行 - 将过长的文本分割为多行(Vosk不执行此步骤
  6. 合并短行 - 合并时间相近的短行
  7. 自动平衡行 - 平衡两行之间的文本
  8. 重新编号 - 重新分配字幕序号

用户项目的原始实现

用户项目的 MergeResults() 方法只实现了部分功能:

  1. ✅ 基础合并(时间间隔<=300ms)
  2. ✅ 智能合并(基于 SubtitleEdit 逻辑)
  3. ✅ 添加标点符号
  4. ✅ 过滤无效的 Vosk 识别结果

缺失的功能

  • ❌ 修复大小写
  • ❌ 修复短持续时间
  • ❌ 自动平衡行
  • ❌ 重新编号

2. 关键发现:Vosk 不执行分割长行

重要发现:SubtitleEdit 在调用 Vosk 后处理时,splitLines 参数设置为 false

// SubtitleEdit 的调用
TranscribedSubtitle = postProcessor.Fix(AudioToTextPostProcessor.Engine.Vosk, transcript, 
    checkBoxUsePostProcessing.Checked, true, true, true, true, false);
//                                                                              ^^^^
//                                                                              不分割长行

这意味着:

  • Vosk 识别结果不应该被分割成多行
  • 只应该进行合并操作
  • 这解释了为什么我们的输出每个词都是单独的一行

3. 合并逻辑差异(已修正)

SubtitleEdit 的合并条件

SubtitleEdit 使用 Utilities.QualifiesForMerge() 方法,判断条件包括:

public static bool QualifiesForMerge(Paragraph p, Paragraph next, 
    double maximumMillisecondsBetweenLines, int maximumTotalLength, bool onlyContinuousLines)
{
    if (p?.Text != null && next?.Text != null)
    {
        var s = HtmlUtil.RemoveHtmlTags(p.Text.Trim(), true);
        var nextText = HtmlUtil.RemoveHtmlTags(next.Text.Trim(), true);

        // 条件1:总长度不超过最大值
        // 条件2:时间间隔不超过最大值
        if (s.Length + nextText.Length < maximumTotalLength && 
            next.StartTime.TotalMilliseconds - p.EndTime.TotalMilliseconds < maximumMillisecondsBetweenLines)
        {
            if (string.IsNullOrEmpty(s))
            {
                return true;
            }

            // 条件3:行延续标记(省略号、逗号、CJK字符)
            var isLineContinuation = s.EndsWith("...", StringComparison.Ordinal) ||
                                          (AllLetters + "…,-$%").Contains(s.Substring(s.Length - 1)) ||
                                          CalcCjk.IsCjk(s[s.Length - 1]);

            // 条件4:不合并歌词
            if (s.EndsWith('♪') || nextText.StartsWith('♪'))
            {
                return false;
            }

            return isLineContinuation;
        }
    }
    return false;
}

关键差异

  • SubtitleEdit 移除了 HTML 标签后再计算长度
  • SubtitleEdit 检查更多的行延续标记(包括字母、符号等)
  • SubtitleEdit 不合并歌词(♪ 符号)

用户项目的合并条件

用户项目的实现基本相同,但缺少语言特殊处理。


修正方案

1. 创建辅助类

SubtitleUtilities.cs

位置:SRT.Core/Helpers/SubtitleUtilities.cs

功能:

  • HTML 标签处理(RemoveHtmlTags
  • CJK 字符检测(IsCjk
  • 合并条件判断(QualifiesForMerge
  • 合并下一个字幕(MergeNextIntoP
  • 自动换行(AutoBreakLine
  • 语言判断(IsNonStandardLineTerminationLanguage
  • 字符计数(CountCharacters

SubtitlePostProcessor.cs

位置:SRT.Core/PostProcessing/SubtitlePostProcessor.cs

实现了完整的后处理流程(8个步骤):

  1. MergeBasicWords() - 新增:基础合并(时间间隔<=300ms)
  2. FilterInvalidResults() - 过滤无效结果
  3. AddPeriods() - 添加句号
  4. FixCasing() - 修复大小写
  5. FixShortDuration() - 修复短持续时间
  6. SplitLongLines() - 分割长行(Vosk不执行)
  7. TryForWholeSentences() - 尝试完整句子(Vosk不执行)
  8. MergeShortLines() - 合并短行
  9. AutoBalanceLines() - 自动平衡行
  10. Renumber() - 重新编号

2. 修改 VoskRecognitionService.cs

添加引用

using VideoTranslator.SRT.Core.PostProcessing;

添加字段

private string _currentLanguageCode = "en";

修改 RecognizeAsync 方法

public async Task<List<ISrtSubtitle>> RecognizeAsync(string audioPath, string languageCode = "en")
{
    _currentLanguageCode = languageCode;
    // ... 其余代码不变
}

简化 MergeResults 方法(关键修正)

private List<ISrtSubtitle> MergeResults(List<ISrtSubtitle> results)
{
    if (results.Count == 0)
    {
        return results;
    }

    #region 使用完整的后处理逻辑(参考SubtitleEdit)
    var postProcessor = new SubtitlePostProcessor(_currentLanguageCode);
    var processed = postProcessor.Fix(
        results, 
        usePostProcessing: true, 
        addPeriods: true, 
        mergeLines: true, 
        fixCasing: true, 
        fixShortDuration: true, 
        splitLines: false  // 关键:Vosk不分割长行
    );
    #endregion

    return processed;
}

删除冗余方法

删除以下方法(已移至 SubtitleUtilitiesSubtitlePostProcessor):

  • QualifiesForMerge
  • IsCjkCharacter
  • AddPeriods
  • GetFirstWord
  • GetLastWord

修正后的完整流程

Vosk 识别结果(List<ISrtSubtitle>)
    ↓
MergeResults()
    ↓
SubtitlePostProcessor.Fix()
    ↓
┌─────────────────────────────────────────┐
│ 1. MergeBasicWords()                   │ 基础合并(时间间隔<=300ms)
│ 2. FilterInvalidResults()            │ 过滤无效结果
│ 3. AddPeriods()                       │ 添加句号
│ 4. FixCasing()                        │ 修复大小写
│ 5. FixShortDuration()                 │ 修复短持续时间
│ 6. SplitLongLines()                   │ 分割长行(Vosk不执行)
│ 7. TryForWholeSentences() (2次)        │ 尝试完整句子(Vosk不执行)
│ 8. MergeShortLines()                   │ 合并短行
│ 9. AutoBalanceLines()                  │ 自动平衡行
│ 10. Renumber()                        │ 重新编号
└─────────────────────────────────────────┘
    ↓
处理后的字幕(List<ISrtSubtitle>)
    ↓
ConvertToSrt()
    ↓
SRT 文件

关键参数配置

SubtitlePostProcessor 参数

参数 默认值 说明
SetPeriodIfDistanceToNextIsMoreThan 600ms 添加句号的时间间隔阈值
SetPeriodIfDistanceToNextIsMoreThanAlways 1250ms 强制添加句号的时间间隔阈值
ParagraphMaxChars 86 (en), 60 (jp/cn/yue) 段落最大字符数

FixShortDuration 参数

参数 默认值 说明
minimumDisplayMilliseconds 500ms 最小显示时间
minimumMillisecondsBetweenLines 50ms 字幕行之间的最小间隔
maximumCharactersPerSeconds 25.0 最大字符速度(字符/秒)

MergeShortLines 参数

参数 默认值 说明
maxMillisecondsBetweenLines 100ms 合并的最大时间间隔
onlyContinuousLines true 是否只合并延续行

预期效果

修正后的代码应该产生与 SubtitleEdit 一致的结果,包括:

  1. ✅ 正确的标点符号
  2. ✅ 正确的大小写
  3. ✅ 合理的显示时间
  4. ✅ 适当的行合并
  5. ✅ 平衡的行布局
  6. ✅ 正确的序号
  7. 不进行长行分割(Vosk特性)

注意事项

  1. 语言支持:当前实现支持英语、日语、中文、粤语等,但名称列表和德语名词处理需要额外实现。

  2. 性能考虑:完整的后处理流程会增加一些处理时间,但能显著提高字幕质量。

  3. 配置灵活性:可以通过修改 SubtitlePostProcessor 的构造参数来调整后处理行为。

  4. 扩展性:如果需要添加更多语言特殊处理,可以在相应的方法中添加条件判断。

  5. Vosk 特性:Vosk 识别结果不应该被分割成多行,只应该进行合并操作。


测试建议

  1. 单元测试:为每个后处理方法编写单元测试
  2. 集成测试:使用相同的音频文件对比 SubtitleEdit 和用户项目的输出
  3. 边界测试:测试极端情况(空字幕、超长字幕、重叠字幕等)
  4. 语言测试:测试不同语言的后处理效果

总结

通过创建完整的后处理流程,用户项目现在应该能够产生与 SubtitleEdit 一致的字幕结果。主要改进包括:

  1. 实现了完整的后处理流程(10个步骤)
  2. 修正了合并逻辑(HTML 标签处理、行延续标记)
  3. 添加了缺失的功能(大小写修复、短持续时间修复、自动平衡)
  4. 关键修正:Vosk 不执行分割长行操作
  5. 添加了基础合并步骤(时间间隔<=300ms)
  6. 保持了代码的可维护性和扩展性
  7. 使用了用户项目中的 ISrtSubtitle 接口

这些修正确保了逻辑上的完全一致性,同时保持了用户项目的代码风格和架构。


更新历史

  • 2026-02-02:发现关键问题 - Vosk 不执行分割长行操作,修正了 splitLines 参数为 false,添加了基础合并步骤。