恢复编码错误的文件名

Recovering filenames with bad encoding

我已经为这个问题苦苦挣扎了一段时间,但是编码工作太痛苦了,我不得不向你聪明的头脑寻求帮助。

在一次去乌克兰的旅行中,一位朋友将一些乌克兰命名的文件复制到我的笔式驱动器中。然而,如你所料,在复制到我的电脑的过程中,文件名变得无法读取垃圾,例如:

Ôàíòîì

好吧,我有充分的理由相信原始文件名是使用 CP1251 编码的(我知道这一点是因为我手动检查了编码表并设法正确翻译了乐队的名称)。显然发生的事情是,在复制过程中,CP1251 代码得到维护,OS 现在只是将它们解释为 Unicode 代码。

我尝试使用以下脚本 "interpret" Python 中的代码:

print u"Ôàíòîì".decode('cp1251')

虽然感觉不对。结果也完全是垃圾:

Ôàíòîì

如果我这样做:

print repr(u"Ôàíòîì".decode('cp1251'))

我获得:

u'\u0413\u201d\u0413\xa0\u0413\xad\u0413\u0406\u0413\xae\u0413\xac'

我发现,如果我能获得 Unicode 中的所有代码点并将它们偏移 0x350,我会将它们放在乌克兰西里尔字母的正确位置。但我不知道该怎么做,可能有一个比这在概念上更正确的答案。

如有任何帮助,我们将不胜感激!

编辑:这是正确翻译的示例

Ôàíòîì 应翻译为 Фантом。

Ô 0x00D4 -> Ф 0x0424
à 0x00E0 -> а 0x0430
í 0x00ED -> н 0x043D
ò 0x00F2 -> т 0x0442
î 0x00EE -> о 0x043E
ì 0x00EC -> м 0x043C

正如我之前所说,正确和错误的代码点之间有一个 0x0350 的偏移量。

(好的,这些文件是音乐文件......我猜你怀疑...)

其他一些测试字符串(翻译我不知道): Áåç êîíò›îëfl Äâîº Êàï_òîøêà Ïîäèâèñü

您可以像这样添加这个 0x350 偏移量:

Python 2:

>>> s = u'Ôàíòîì'
>>> decoded = u''.join([unichr(ord(c)+0x350) for c in s])
>>> print decoded
Фантом
>>> u'Ôàíòîì'.encode('latin1').decode('cp1251')
'Фантом'
>>> a = u'Ôàíòîì'.encode('8859').decode('cp1251')   
>>> print a   
Фантом    

如果您查看样本中的单个字符,其中大部分来自西里尔字母,但还有其他字符来自希腊语和科普特语、拉丁语扩展 B 和 u'fe52' 是 beyond 后面的句号.所以有点乱。
编辑:

a = u'Ôàíòîì'.encode('cp1252').decode('cp1251')
print a
Фантом
a = u'Äâîº Êàï_òîøêà'.encode('cp1252').decode('cp1251')
print a
Двоє Кап_тошка
a = u'Ïîäèâèñü'.encode('cp1252').decode('cp1251')
print a
Подивись
a = u'Áåç êîíò›îë'.encode('cp1252').decode('cp1251')
print a
Без конт›ол

cp1252 适用于给定的样本,但 Áåç êîíò›îëfl 除外,其中拉丁小连字 Fl U+FB02 似乎是多余的

我发现,除了文件名之外,我所有的文件都有错误编码的元数据。

我发现 mp3 文件的 id3 元数据标准只支持 latin1、utf8 和 utf16 编码。

我的文件都包含在 mp3 文件上设置为 latin1 的 CP1251 数据。可能在俄罗斯和西里尔文字国家,所有音乐播放器都被设置为理解 latin1 应该被解释为 CP1251,但我不是这样。

我使用了 Python 和诱变剂来更正元数据。在读取 mp3 元数据时,诱变剂假定数据编码为 latin1,结果显示乱码。我要做的就是把那些乱码重新编码成latin1并解码为CP1251,得到unicode。然后我覆盖了 mp3 元数据,然后 mutagen 明白 unicode 应该保存为 utf-8。所有元数据都是正确的。

为了更正文件元数据,我使用了以下 Python 脚本:

from mutagen.easyid3 import EasyID3

def decode_song_metadata(filename):
    id3 = EasyID3(filename)
    for key in id3.valid_keys:
        val = id3.get(key)
        if val:
            print key
            decoded = val[0].encode('latin1').decode('cp1251')
            print decoded
            id3[key] = decoded
    id3.save()

def correct_metadata():
    paths = [u'/Users/felipe/Downloads/Songs']    

    for path in paths:
        print 'path: ' + decode_filename(path)
        for dirpath, dirnames, filenames in os.walk(path):
            for filename in filenames:
                try:
                    decode_song_metadata(os.path.join(dirpath, filename))
                except:
                    print filename


if __name__ == '__main__':
    correct_metadata()

这更正了 mp3 元数据,但是更正文件名需要不同的技巧,因为它们有不同的编码问题。我认为发生的事情是原始文件名在 CP1251 中,但是当它们从我的 fat32 格式的 USB 记忆棒复制到我的 Mac 时,macOS 将文件名解释为 latin1。这起源于带有奇怪重音字符的文件名,这些字符在 "Normal Form Decomposed" 中以 UTF-16 编码,其中每个重音都保存为与主字母不同的 unicode 字符。 macOS 还添加了污染文件名的 BOM 标记。所以为了纠正这个我不得不做相反的操作:

  • 获取文件名。 returns 一个 unicode 字符串,其中拉丁重音字符以标准形式分解。
  • 我们必须再次转换为 Normal Form Composed。
  • 然后我们用 UTF-16 编码。
  • 我们删除 BOM。
  • 我们解码解译为CP1251。

为了解码文件名,我使用了以下脚本:

def decode_filename(filename):
    # MacOS filenames are stored in Unicode in "Normal Form Decomposed"
    # form, where the accents are saved separated from the main
    # character. Because the original characters weren't proper
    # accentuated letters, in order to recover them we have to decompose
    # the filenames.
    # 
    norm_filename = unicodedata.normalize('NFC', filename)
    utf16 = norm_filename.encode('utf16')
    bom = codecs.BOM_UTF16

    if utf16.startswith(bom):
        # We have to remove the BOM bytes
        utf16 = utf16[len(bom):]

    cp1251 = utf16.decode('cp1251')
    return cp1251

这应该与 运行 os.walk() 方法返回的 unicode 一起使用。

虽然上面的脚本有效,但我最终没有使用它来更正文件名。我正在使用启用了 "Auto organizer" 功能的 iTunes。这很棒,因为每次我在 iTunes 上播放歌曲时,它都会获取 mp3 元数据(我已经使用上面的第一个脚本更正了)并重命名 mp3 文件以包含歌曲名称,甚至文件夹。我发现这比更正文件名更好,因为这也会正确重命名文件夹并放置对歌曲有意义的文件名。