Maya Python 查询引用文件中的编码问题 – 中文乱码

最近在做一个功能,查询 Maya 文件中引用的其他文件。Maya 官方文档提供的方法之一是用 Python 命令来查询引用的文件。后来又使用直接读取文件获取引用文件的方法。详情可以之前的两篇文章。本文主要来说说这之间遇到的一些编码问题,编码的问题一般就是中文乱码的问题。

Maya Python 命令获取引用文件

因为 Maya 安装目录下的 Python(mayapy.exe)版本为 Python 2.7,因此先写了一个 Python 2.7 的脚本,用来对 Maya 文件获取它引用的文件。

项目使用的是 Python 3.6,再写一个 Python 3.6 的文件,调用 mayapy.exe 执行这个 Python 2.7 的脚本。

整个流程是:有两个 Python 文件,2.7 版本的文件可以称之为 get_reference_file.py,3.6 版本的文件可以称之为 execScript.py。Python 3.6 执行 execScript.pyexecScript.py 调用 Python 2.7 去执行 get_reference_file.py

execScript.py 的部分代码如下:

mayapy_path = 'C:\\Program Files\\Autodesk\\Maya2018\\bin\\mayapy.exe'  # Python 2.7
script = 'get_reference_file.py'  # 2.7 的脚本
reference_file_path = subprocess.Popen(mayapy_path+' '+script+' '+file_path,stdout=subprocess.PIPE,stderr=subprocess.PIPE)  # 调用 mayapy.exe 执行脚本 `get_reference_file.py`,即 Python 2.7 执行 2.7 的脚本
out, err = reference_file_path.communicate()

上面代码,第三行就是上面一段文字的实现,从 execScript.py 文件传入一个参数(file_path)到 get_reference_file.py 文件,在 get_reference_file.py 文件中获取 file_path,如果这个参数中含有中文会出现乱码,不过仍可以执行查询功能,不需要进行编解码操作。

代码第四行是获取结果(out)与错误信息(err),这些结果与错误信息,会出现一些中文乱码情况,,大概是 Python 2 与 3 的编码有区别造成的,这里不过多赘述。

  • 返回正确的结果。

get_reference_file.py 会返回一些信息,如果返回的信息中只有引用的文件并且正确,按我之前文章的写法,那么正确返回的应该是个列表,得到的 out 就是传过来的列表,不过,这个列表格式是 bytes,编码格式为 ascii。并且如果列表中的每条路径含有中文会出现乱码,如下:b"[u'C:/test/maya/\\u6587\\u6863/m4.mb', u'C:/test/maya/\\u6587\\u6863/w4.ma'],此时可以使用 eval(out.decode('utf-8')) 对结果解码,再转为 list 格式,结果如下:['C:/test/maya/文档/m4.mb', 'C:/test/maya/文档/w4.ma']

  • 返回错误的结果与错误信息。

并不是所有的文件都能查得到引用文件,当查不到时,也可以返回一点信息,比如:没有查到引用文件时,返回 ‘没有引用文件’,此时也会得到的 out 会出现乱码,如:b'\xe6\xb2\xa1\xe6\x9c\x89\xe5\xbc\x95\xe7\x94\xa8\xe6\x96\x87\xe4\xbb\xb6\r\n',编码是 ‘utf8’,此时还使用 out.decode('utf8') 即可解码为 ‘没有引用文件’。

可如果用此方法查看错误信息(err),会出现解码错误,如下:UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbe in position 0: invalid start byte。通过命令 import chardetchardet.detect(err) 查看 err 编码格式(前文的编码亦是如此查看),为 ‘Windows-1254’,无法用 ‘utf8’ 解码,用 ‘gbk’ 也无法解码。从打印的 err 可以知道,err 中混合着中文乱码、英文、数字等字符。单独取出 err 其中的一部分尝试解码,如报错信息开头:\xbe\xaf\xb8\xe6: \xb4\xd3\xca\xd7\xd1\xa1\xcf\xee\xce\xc4\xbc\xfe\xa1\xb0D:/\xce\xd2\xb5\xc4\xce\xc4\xb5\xb5/maya/2018/zh_CN/prefs/synColorConfig.xml,从最前面的 \xbe\xaf\xb8\xe6 已经可以猜测,‘utf8’ 无法解码,而 ‘gbk’ 应该可以。因为每个中文经 ‘utf8’ 编码后对应三个 ‘\x’ 开头的字符,如之前的 ‘没有引用文件’ 经 ‘utf8’ 编码后对应 18 个 ‘\x’,这是因为一个中文在 ‘utf8’ 中占三个字节,而 ‘gbk’ 占两个字节。

用 ‘utf8’ 果然无法解码,尝试用 ‘gbk’ 解码,得到结果为:警告: 从首选项文件“D:/我的文档/maya/2018/zh_CN/prefs/synColorConfig.xml,说明 err 报错信息,部分乱码可以用 ‘gbk’ 解码。但是否存在有些乱码可以用 ‘utf8’ 编码呢?似乎是存在的,笔者通过用 ‘gbk’ 解码时的报错信息,如:UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 3007: illegal multibyte sequence 了解到,在报错信息的 3007 位置存在 ‘gbk’ 无法解码的字符,那么是否可以用 ‘utf8’ 解码呢?由于单个字符不能用 ‘utf8’ 解码,需要更多的字符,那就截取更多的字符,用命令 err[2950:3050] 截取位置 3007 附近的 100 个字符,如下:b' \xe5\x9c?MAYA_PLUG_IN_PATH \xe4\xb8\x8a\xe6\x89\xbe\xe4\xb8\x8d\xe5\x88\xb0\xe6\x8f\x92\xe4\xbb\xb6\xe2\x80\x9cgiggleMaya\xe2\x80\x9d\xe3\x80?\r\n\xb4\xed\xce\xf3: file: C:/test/maya/ChenNan_Mod.ma',观察到字符串 \xe4\xb8\x8a\xe6\x89\xbe\xe4\xb8\x8d\xe5\x88\xb0\xe6\x8f\x92\xe4\xbb\xb6\xe2\x80\x9c,21 字符符合 ‘utf8’ 编码格式,解码后得到:上找不到插件“。这说明 err 信息中既有可以用 ‘gbk’ 解码的乱码,也有可以用 ‘utf8’ 解码的乱码。

总之,可以打印出错误结果和错误信息,截取 ‘\x’ 开头的乱码,用 ‘gbk’ 或者 ‘utf8’ 解码查看哪些代码出错,以便更好地解决问题。

直接读取文件内容获取引用文件

直接读取文件内容获取引用文件分为两种情况,以 ma 结尾(以下简称 ma 文件)和以 mb 结尾(以下简称 mb 文件)的文件。对应到实际的需求中,就是两种情况:文件夹名中有中文;文件名中有中文。

  1. ma 文件引用 ma 与 mb 文件。

先看段代码:

with open(upload_file_path, 'rb') as f:
    for f_row in f.readlines():
        if f_row.startswith(b'file -r ') and b' -typ "mayaAscii" ' in f_row:
            reference_filepaths.append(f_row.decode('gbk').split('"')[-2])
        elif f_row.startswith(b'\t\t -typ "mayaBinary"'):
            if (f_row.decode('gbk').split('"')[-2]) not in temp_filepaths:
                temp_filepaths.append(f_row.decode('gbk').split('"')[-2])
            else:
                reference_filepaths.append(f_row.decode('gbk').split('"')[-2])
        elif f_row.startswith(b'requires maya'):
            break

代码第三行处理引用文件是 .ma 后缀的文件,通过两个条件找出包含 ma 文件路径的行,如下:b'file -r -ns "w4" -dr 1 -rfn "w4RN" -op "v=0;" -typ "mayaAscii" "C:/test/maya/\xce\xc4\xb5\xb5/w4.ma";\n'。先 ‘gbk’ 解码,再以 ‘"’ 分割,取出列表倒数第二个字符串,即结果,如下:'C:/test/maya/文档/w4.ma'

文件夹名中有中文时,没有问题,文件名换成中文时,却出现无法解码的情况。未解码时:b'file -r -rpr "\xd6" -dr 1 -rfn "reference1" -op "v=0;" -typ "mayaAscii" "C:/test/maya/\xce\xc4\xb5\xb5/\xd6\xf9.ma";\n'。报错为:UnicodeDecodeError: 'gbk' codec can't decode byte 0xd6 in position 14: illegal multibyte sequence,原来是文件名中有中文,这行出现单个 ‘\x’ 开头字符,无法被解码,出现 ‘\x’ 的位置是引用文件节点位置。

所以需要去除这部分,既如此先分割,取出路径部分,再解码即可,第三行代码修改为:reference_filepaths.append(f_row.split(b'"')[-2].decode('gbk'))

对于 .mb 后缀的文件无需担心这个乱码问题,因为获取的行,没有这些字符,如下:b'\t\t -typ "mayaBinary" "C:/test/maya/\xce\xc4\xb5\xb5/\xb7\xbd.mb";\n',结果为:'C:/test/maya/文档/方.mb'

  1. mb 文件引用 ma 与 mb 文件。

对于 mb 文件更为简单一些,由于直接获取文件路径,乱码的情况是一致的,得到的 bytes 类型的乱码结果,如:b'C:/test/maya/\xe6\x96\x87\xe6\xa1\xa3/\xe6\x96\xb9.mb\x00?\x00\x00\x01reference1',这里需要用 ‘utf8’ 解码即可得到正确的结果,如:C:/test/maya/文档/方.mb?╔reference1,虽然有其他的乱码,但这已不是考虑的范围。

  1. 小结

查找 ma 文件的引用文件,解码采用的是 ‘gbk’,而查找 mb 文件的引用文件,解码采用的是 ‘utf8’。

总结

乱码问题有时很棘手,不过也并不是那么难。一般情况下,‘gbk’ 和 ‘utf8’ 已经可以解决大部分问题,如果不行应该不是 ‘gbk’ 和 ‘utf8’ 的问题,可能是字符串格式的问题。两种编码格式进行解码时,必须是对 bytes 类型解码。而如果是字符串乱码呢?那就先要编码,再解码。如字符串 a:a = 'C:/test/maya/\xe6\x96\x87\xe6\xa1\xa3/m4.mb',可以用 a.encode(‘utf8’).decode(‘utf-8’) 编码再解码吗?不行,编码 ‘utf8’,解码 ‘utf8’ 是没有效果的,需要编码其他,再转为 ‘utf8’。 如:b2.encode("raw_unicode_escape").decode("utf-8")a.replace('\\x', '%').encode('latin-1').decode('utf-8')。总之就是编解码不应该一致,否则就是回到原点。

另一个问题是,尽可能早的对字符解码,即取出包含不需要的字符串序列时,应该先解码,在截取需要的。而不应该先截取需要的,再去解码。除非有特别情况。