字幕文件 BOM 处理遇 bug?Python 与 ripgrep 助力轻松修复!

字幕文件 BOM 处理遇 bug?Python 与 ripgrep 助力轻松修复!
修复字幕文件字节顺序标记 bugPython 与 ripgrep 来帮忙最近在 2026 年 7 月 18 日我一直忙着整理本地媒体库中的字幕文件。当下流行的字幕文件格式有两种分别是 SRTSubRip 字幕和 WebVTT网络视频文本轨道。我选择把字幕统一成 WebVTT 格式原因是它能和 HTML5 的 video 元素兼容而我所有视频都是通过嵌入静态网站的 video 元素播放的。不过很多字幕只有 SRT 格式所以我编写了 一个 Python 函数用于将 SRT 文件转换为 WebVTT 文件。这两种格式看着不复杂转换过程似乎也简单但现实却给了我“当头一棒”在对转换后的字幕抽检时我发现处理 字节顺序标记BOM 出现了一个 bug而且修复起来还费了不少劲。未正确处理 UTF - 8 BOM字节顺序标记是在文本文件开头使用 零宽度无间断空格字符 UFEFF 的特殊方式它能告知读取文件的程序文本的编码方式。具体编码方式取决于用于编码该字符的字节序列下面是几个例子EF BB BF表明文件是 UTF - 8 编码的文本。UTF - 8 的字节顺序固定所以这个标记仅用于表明编码方式。FE FF表明文件是 UTF - 16 编码的文本采用 大端字节序UTF - 16BE。FF FE表明文件是 UTF - 16 编码的文本采用 小端字节序UTF - 16LE。00 00 FE FF表明文件是 UTF - 32 编码的文本采用大端字节序。我所有的 SRT 输入文件都是 UTF - 8 编码的其中一些带有 UTF - 8 字节顺序标记但我没正确处理它们。比如假设有如下输入的 SRT 文件UFEFF1 00:00:01,001 -- 00:00:10,010 You have grown, Keyne. 2 00:02:00,002 -- 00:20:00,020 Soon you’ll be needing another name.将其转换为 WebVTT 格式时我需要添加 WEBVTT 头部删除序号并更改时间戳格式。为了删除序号我会检查一行是否全为数字。由于 BOM 与第一个序号在同一行这行并非全是数字所以我没删除它。结果这一整行包括 BOM都被复制到了 WebVTT 文件的中间WEBVTT UFEFF1 00:00:01.001 -- 00:00:10.010 You have grown, Keyne. 00:02:00.002 -- 00:20:00.020 Soon you’ll be needing another name.正确的转换应该同时删除字节顺序标记和第一个序号WEBVTT 00:00:01.001 -- 00:00:10.010 You have grown, Keyne. 00:02:00.002 -- 00:20:00.020 Soon you’ll be needing another name.在我的本地媒体库中可以假设所有内容都是 UTF - 8 编码的。所以我能安全地删除字节顺序标记而且网页浏览器仍然能正确解码我的字幕。使用 encodingutf-8-sig 修复转换器起初我尝试手动处理 BOM。我编写代码查找 UFEFF 并将其从文件中去除还试图检测它并将其重新插入到转换后的 WebVTT 文件中后来我才意识到可以直接将其完全删除。这一过程有点混乱因为我把底层的文本编码代码与高层的字幕转换步骤混在了一起。在写这篇文章的过程中我发现了一个更优雅的解决方案如果使用 encodingutf-8-sig 打开 SRT 文件Python 会自动检测并跳过文件开头可选的 UTF - 8 编码的 BOM。我的其他代码则无需关心 BOM 是否存在。我 修复了函数中的 bug这意味着未来的转换将能正常进行。但那些已经生成的有问题的文件该怎么办呢使用 ripgrep 检测 UTF - 8 BOM一开始我尝试用 TextMate 搜索 UFEFF但每次搜索都会导致 TextMate 崩溃于是我转而使用命令行工具。我用 ripgrep 进行文本搜索。默认情况下它会对文件进行 “BOM 嗅探”读取文件时它会查看前几个字节将文件从实际编码转换为 UTF - 8然后在转换后的版本上执行搜索。这正是 BOM 的预期使用方式但如果要搜索的正是 BOM 本身这种方式就不太有用了。相反我们可以通过在正则表达式中使用 (?-u:…) 来禁用 ripgrep 的 Unicode 支持从而搜索原始字节这个标志来自 Rust 的 regex 包。以下命令用于查找以 UTF - 8 BOM 开头的行$ rg ^(?-u:\xEF\xBB\xBF)如果只需要在文件开头查找 BOM还需要使用 --multiline 标志。该标志会使脱字符 ^ 锚定到文件开头而不是任意行的开头。但由于我要查找的是文件中间的 BOM所以不使用 --multiline 标志是正确的。这次搜索找出了几十个包含错误 BOM 的文件。一开始我在 TextMate 中打开这些有问题的文件并手动编辑$ rg --files-with-matches --null ^(?-u:\xEF\xBB\xBF) | xargs -0 mate但我很快意识到这样做太慢了于是我编写了一个 Python 脚本来一次性清理所有文件#!/usr/bin/env python3 import glob for filepath in glob.glob(**/*.vtt, recursiveTrue): with open(filepath, rb) as f: content f.read() if b\xef\xbb\xbf in content: content content.replace(b\xef\xbb\xbf, b) with open(filepath, wb) as f: f.write(content) print(filepath)运行这个脚本后我使用 ripgrep 命令进行检查发现所有错误的 BOM 都已从我的媒体库中删除。我还使用 Git 仓库管理字幕文件因此可以确认脚本没有引入其他更改。在遇到这个 bug 之前我只是模糊地听说过字节顺序标记从未真正处理过相关问题。这也正是我喜欢将本地媒体档案管理为 手工构建的静态网站 的原因——这种低技术含量的方法让我有很多机会探索底层概念了解计算机的实际工作原理。