查询 maya 文件中的引用文件路径 -- 直接读取文件内容获取路径

查询 maya 文件中的引用文件路径 – 直接读取文件内容获取路径

之前查询 maya 文件中的引用文件路径,采用的是 maya Python 命令的方式。存在一些问题:

  • Maya 客户端:目前了解到,以上方法,必须安装 Maya。
  • 版本不全匹配:Maya 版本很多,所以文件版本也不统一。以上方法要求 Maya 软件版本不低于文件版本(创建文件的 Maya 版本)。
  • 文件类型单一:由于需要打开文件获取文件的版本,以上查询方法,只能查询 ma文件(ASCII),mb 文件(二进制)不行。
  • 查询方法繁琐:以上方法需要用到 mayapy.exe,而且需要单独文件,使用 subprocess.Popen,Python 2 与 Python 3 结合,存在许多问题:编码、数据传输等。
  • Maya Python:需要了解 maya Python 的许多命令。

基于以上问题,在项目中使用会带来不好的体验,所以尝试直接读取文件内容,查询引用文件的路径。

ma 文件

对于 ma 后缀的文件,查询引用文件的路径比较简单。ma 文件的类型为 Maya ASCII File,可以直接用记事本等工具打开,查看内容。

查看内容后,了解到引用文件的路径并不是单独存在,而是跟在以 file -rdi 开头的字符串之后,可以读取以 file -rdi 的字符串,在分割出需要的内容。(当然也可以匹配 file -r 开始的字符串,文件信息中每个引用路径存在两条,只不过开头不一样)。

with open(upload_file_path, 'rb') as f:
    for f_row in f.readlines():
        if f_row.startswith(b'file -rdi'):
            reference_filepaths.append(f_row.decode('gbk').split('"')[-2])

上面的代码中,以 ‘rb’ 读取文件,f.readlines() 将内容逐行读取为一个列表,其中含有引用文件的路径的行的形式为 file -rdi 1 -ns "w1" -rfn "w1RN" -op "v=0;p=17;f=0" -typ "mayaAscii" "C:/test/maya/文档/w1.ma";,这是一个 bytes 类型的字符串。f_row.startswith(b'file -rdi') 可以把以 file -rdi 开头的字符串找出来,因为路径中存在中文,可能会乱码(我这边是),以 gbk 解码(或许你是别的编码,如:utf-8。),再通过 " 分割字符串得到一个列表,取出列表倒数第二个元素,即需要的引用文件的路径。

以上方法只能查找同类型引用文件,即 ma 文件引用 ma 文件,如果 ma 文件引用 mb 文件,则规则有所改变。

通过分析可知,ma 引用的 mb 文件,在文本中显示为 ' -typ "mayaBinary" "C:/test/maya/文档/m4.mb";',打印结果为 '\t\t -typ "mayaBinary"',所以可以这样做:

reference_filepaths = []
    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"'):
                reference_filepaths.append(f_row.decode('gbk').split('"')[-2])  # []
        return list(set(reference_filepaths))

对于引用的 ma 文件不能仅仅再使用 'file -r ' 开头检索,因为引用的 mb 文件也是以它开头,而引用的 mb 文件那一行,又没有引用信息,引用信息单独一行,即前文所说,此时必须舍弃。所以同时以字符串 ' -typ "mayaAscii" ' 过滤。这样可以得到需要的 ma 文件的引用信息。

对于引用的 mb 文件,前文已经分析,如上文代码,可以匹配 '\t\t -typ "mayaBinary"' 开始的字符串,再取得需要的路径信息。但是以 '\t\t -typ "mayaBinary"' 开始的字符串会重复一次,所以需要使用 set 去重。本以为到这里已经结束,在测试中发现,'\t\t -typ "mayaBinary"' 开始的字符串并不一定出现两次,如果引用的文件又引用了其他文件,则其他文件的路径会出现一次,导致有些文件路径出现一次,有些两次,而出现一次的是不需要的,无法去重。

还有,如果文件名出现中文,那么在 file -r -rpr "m? -dr 1 -rfn "reference1" -op "v=0;" -typ "mayaAscii" "C:/test/maya/文档/m柱.ma"; 中的开头部分 “m?” 处的中文会乱码,且 gbk 无法解码。

分析后,目前最终代码如下:

def get_reference_filepath_in_ma(upload_file_path):
    reference_filepaths = []
    temp_filepaths = []
    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:  # 引用为 .ma 文件
                # 先去除无法解码的节点部分,再取路径,对路径 bytes 解码
                reference_filepaths.append(f_row.split(b'"')[-2].decode('gbk'))
            elif f_row.startswith(b'\t\t -typ "mayaBinary"'):  # 引用为 .mb 文件处理
                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
    return reference_filepaths

代码分析:

  1. 打开文件,读取为列表格式,内容为 bytes 格式。
  2. 找出 'file -r ’ 开始并且含有 ’ -typ “mayaAscii” ’ 的行(此为引用 .ma 文件的格式),去掉无法解码的部分,即直接获取路径部分。解码得到路径字符串。
  3. 找出 ‘\t\t -typ “mayaBinary”’ 开始的行,存入一个临时表,在临时表中出现过的值,是需要的路径。
  4. 当文件很大时,为了减少读取,当行开头为 ‘requires maya’ 时,结束读取。

mb 文件

mb 后缀的文件比较复杂,mb 文件的类型为 Maya Binary File,用记事本等工具打开后,内容出现乱码。在使用其他工具如 EmEditor 以二进制方式打开(十六进制视图)。通过对比可以看到存在引用的文件路径,通过分析,每个引用文件路径前会有字符 FREFFREF,可以以此为标志,查询路径中的引用路径。

单独的 FREFFREF 作为查询标志,不是特别安全,因为路径文件夹与文件名中可能会存在(虽然可能性比较小)。

继续调查,发现,引用 mb 文件时,需要的路径字符串主要格式为 ‘FREFFREF … C:/test/1.mb … RN … VERS’;引用 ma 文件时,需要的路径字符串主要格式为 ‘FREFFREF … C:/test/1.ma … RN …’。

FREFFREF 与 盘符 C 之间并不总是空格,了解到的就有 FREFFREF 后跟着 - 及其他无法显示的字符;C 之前紧邻着也会有其他字符。所以,最后只能以 FREFFREF 为开始标志,以 RN 后的多个空格为结束标志,获取 FREFFREF 中 F 偏移 160 个单位开始到 RN 后的多个空格为结束之间的字符,格式即:‘C:/test/1.mb … RN’

之后对得到的字符串用 utf-8 解码,再用正则匹配出以 .ma 或 .mb 结尾的路径字符串。由于担心匹配的字符串不是路径,需要再对字符串判断是否为路径。

目前最终代码如下:

from bitstring import ConstBitStream, BitStream

def get_reference_filepath_in_mb(upload_file_path):
    start_flag_char = '0x460x520x450x460x460x520x450x46'  # flag: 'FREFFREF'.
    hex_datas = ConstBitStream(filename=upload_file_path)  # 无文件报错
    # 查找 start_flag_char 开始的字符索引,返回一个生成器,找不到不报错
    start_flag_pos = hex_datas.findall(start_flag_char, bytealigned=True)
    start_flags = []
    if start_flag_pos:
        for start_flag in start_flag_pos:
            start_flags.append(start_flag)

    end_flags = []
    if start_flags:
        end_flag_char = '0x00' * 9  # flag: 9 个空格.
        for start_flag in start_flags:
            # 从 start_flag_char 字符开始,查找 end_flag_char 结尾的字符索引,FREFFREF 偏移 12 个字符后的第一个 9 个空格
            end_flag_pos = hex_datas.find(end_flag_char, start=start_flag+160, bytealigned=True)
            for end_flag in end_flag_pos:
                end_flags.append(end_flag)

    reference_filepaths = []
    if end_flags:
        for i in range(min(len(start_flags), len(end_flags))):
            try:  # 处理文件夹及文件名中有 FREFFREF 存在的情况
                # 获取 'FREFFREF' 偏移12个字符后到 '9 个空格',找不到不会抛出错误
                hex_data = hex_datas[start_flags[i]+160:end_flags[i]]
                str_data = BitStream.tobytes(hex_data).decode('utf-8')
                reference_filepath = re.findall('.*?\\.mb|.*?\\.ma', str_data)  # 找不到为 []
                # 判断路径是否正确
                if reference_filepath and os.path.isabs(reference_filepath[0]):
                    reference_filepaths.append(reference_filepath[0])
            except:
                pass
    return reference_filepaths

代码分析:

  1. 导入第三方模块 bitstring 的两个类 ConstBitStream, BitStream。
  2. mb 文件的类型为 Maya Binary File,通过 bitstring.ConstBitStream 读取文件为字节流。
  3. 使用 findall() 方法一次找出字符 FREFFREF 的位置,找到的索引为第一个 F 的位置,存为 start_flags 列表。
  4. 遍历 start_flags,以每一个 FREFFREF 偏移 160 个单位为开始位置,查找 ‘0x00’ * 9 的位置,存为 end_flags 列表。
  5. start_flags 和 end_flags 并不一样长,所以 min() 取个小值。
  6. 取出 start_flag+160 到 end_flag 之间的字符串,转为 bytes 格式,再解码,之后正则匹配出路径字符串。这里由于文件夹及文件名中有 FREFFREF 存在的情况,需要处理,无法解码直接 pass,只有能解码,才可能是字符串路径。才能去正则匹配。
  7. 通过 os.path.isabs() 方法判断得到的是否为有效路径。

小结

经测试,目前似乎没有问题了,但不排除还有其他问题。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值