作者:鬼猫猫,微信公众号:真的起名无力

微信扫一扫关注


背景

我们在日常开发中经常面临路径文件名处理的问题,比如:

  • 把某个文件夹下所有的 .mov 文件拿出来,有时需要一直递归下去
  • 三个平台(win mac linux)的路径转换
  • 提取出文件名中的文件类型(拓展名)
  • 提取出文件名中的帧号
  • 文件夹的相对路径花式跳转

配合 os sys glob 模块,这些还算在可控范围。但遇到文件序列帧处理,就真的想跪了。

  • 识别我们是同一套序列帧
  • 有缺失帧的处理
  • Nuke 软件的 %04d 风格、Houdini 软件的 $F4 和其他 #### 风格转换
  • 递归扫描出指定文件下,某几种指定格式的序列帧文件
  • 把序列帧文件展开成一帧一帧(执行拷贝操作)
  • 还有一个遗憾,使用 os 处理,这很不面向对象。

安装

使用 pip 安装

pip install -U dayu-path

导入库

from dayu_path import DayuPath

技术细节介绍

care 这些的请直接调到下一节

dayu_path 最初版是基于 unipath 库进行拓展的。

后来觉得安装 dayu_path 还得安装 unipath 太烦了。

于是就合并了 unipath 的功能,发展到了现在的样子。

dayu_path是继承
str(python 3.x) 或
unicode(python 2.x)
类进行拓展开发的。

os 模块里面的相关的函数,添加到 dayu_path 类上。

也就是说 str/unicode 有的函数它都有,os 有的函数它也(大部分)有。

并根据影视动画行业的文件特点,增加了序列文件的处理功能。

使用举例

from dayu_path import DayuPath
my_file = DayuPath('d:/test_dayu_path/a/exr/pl_0010_comp_master_v0001.1001.exr')

基础用法

# 返回文件的名字
print my_file.name 
# pl_0010_comp_master_v0001.1001.exr

# 返回文件的拓展名
print my_file.ext 
# .exr

# 返回去掉name最后一个 .之后的内容
print my_file.stem 
# pl_0010_comp_master_v0001.1001
print my_file.stem.stem 
# pl_0010_comp_master_v0001

# 返回文件名中符合 `.v{}.` 的部分 
print my_file.version 
# v0001 

# 返回文件名中包含数字且不合符version格式的最后一组匹配到的数字 们
print my_file.frame
# 1001 

# 返回文件所在的文件夹
my_file.parent 
# d:/test_dayu_path/a/exr
my_folder = my_file.parent.parent
# d:/test_dayu_path/a

# 返回文件夹的子文件/文件夹
my_folder.child('exr') 
# d:/test_dayu_path/a/exr

# 判断文件/文件夹是否存在
my_folder.exists() 
# True
my_folder.child('hahaha').exists() 
# False

# 让操作系统打开文件夹
my_folder.show() 
# windows就弹出文件资源管理器啦~
my_file.show() 
# 打开文件所在文件夹

# 一些 os 模块的函数展示
my_folder.listdir() 
# [u'd:/test_dayu_path/a/exr']

处理序列帧

我们假设个场景,外包/DI 发回来的一大堆的 exr 序列帧文件,结果如下

from_vendor/
  pl_0010/
    exr/
      pl_0010_comp_v0007.1001.exr
      pl_0010_comp_v0007.1002.exr
      pl_0010_comp_v0007.1005.exr
      pl_0010_comp_v0007.1006.exr
    mov/
      pl_0010_comp_v0007.mov
      something_other_format.mp4
  20190306/
    20190306.csv
    exr/
      A001C001_170922_E4FB.7272829.exr
      A001C001_170922_E4FB.7272830.exr
      A001C001_170922_E4FB.7272831.exr
      A001C001_170922_E4FB.7272832.exr
      something_other_format.jpg

制片大大要把这些文件上传到公司内部服务器上,并且要符合公司文件夹层级规范。

于是拜托你来帮忙写个小工具,要求如下:

  • 自动识别到 exrmov 文件,其他文件忽略掉
  • 能在文件序列有缺失帧的时候提示他
  • 只想指定 from_vendor 文件夹
  • 可以自行决定是否要把文件的帧号从 10010001 开始(比如重新给 DI 发来的文件排帧号)
  • 当有缺帧的时候,可以自行决定是否保持帧号(比如外包只提交了某几个关键帧)
  • 拷贝到公司服务器符合规则的路径下

看在她/他这么有诚意的份儿上,你打开 Python,进行了如下行云流水的操作。

先得到符合要求的文件们

from dayu_path import DayuPath


root_folder = DayuPath('/path/to/from_vendor')

# 使用 scan 函数进行扫盘操作
# recursive 代表是否要递归扫盘
# ext_filters 传入要过滤的文件格式
for seq_file in root_folder.scan(recursive=True, ext_filters=('mov','exr')):
    print seq_file 

    # 我们就得到了符合要求的序列文件,他们被合并成了一个 DayuPath
    # /path/to/from_vendor/20190306/exr/A001C001_170922_E4FB.%07d.exr
    # /path/to/from_vendor/pl_0010/exr/pl_0010_comp_v0007.%04d.exr
    # /path/to/from_vendor/pl_0010/mov/pl_0010_comp_v0007.mov

查看序列帧的帧数信息

# 我们拿这个序列文件举例 my_seq_file
# /path/to/from_vendor/pl_0010/exr/pl_0010_comp_v0007.%04d.exr

# 获取序列中所有的帧号
my_seq_file.frames
# [1001, 1002, 1005, 1006]

# 获取序列中丢失的帧
my_seq_file.missing
# [1003, 1004]

把序列帧展开,获得每个单个文件

for f in my_seq_file.frames:
    my_seq_file.restore_pattern(f)
    # /path/to/from_vendor/pl_0010/exr/pl_0010_comp_v0007.1001.exr
    # /path/to/from_vendor/pl_0010/exr/pl_0010_comp_v0007.1002.exr
    # /path/to/from_vendor/pl_0010/exr/pl_0010_comp_v0007.1005.exr
    # /path/to/from_vendor/pl_0010/exr/pl_0010_comp_v0007.1006.exr

符合要求的文件都拿到了,下面就是要拷贝重命名操作了.
这里我留个作业,请好学的自行你探索 dayu_path 里面的:

  • rename_sequence
  • copy_sequence

几种 pattern 风格转换

my_seq_file.to_pattern()
# /path/to/from_vendor/pl_0010/exr/pl_0010_comp_v0007.%04d.exr

my_seq_file.to_pattern('#')
# /path/to/from_vendor/pl_0010/exr/pl_0010_comp_v0007.####.exr

my_seq_file.to_pattern('$')
# /path/to/from_vendor/pl_0010/exr/pl_0010_comp_v0007.$F4.exr

其他场景应用

情景:需要用户指定一组 jpg 序列帧,只需要用户随意选择其中一帧

# 用户通过界面选择了 hahahah.1044.jpg 文件
single_file = DayuPath('d:/temp/hahahah.1044.jpg')

# 不递归扫盘
seq_file = list(single_file.scan())[0]

print seq_file
# d:/temp/hahahah.%04d.jpg

情景: maya 软件里面 file 节点的 file 属性

# maya 界面显示的就是 d:/temp/hahahah.%04d.jpg 文件
fake_seq_file = DayuPath('d:/temp/hahahah.%04d.jpg')
fake_seq_file.frames
# [] 空列表

# 不递归进行扫盘
real_seq_file = list(fake_seq_file.scan())[0]
real.frames
# [..., 1044, 1055, ...] 有了!

更高级的用法

你可以拓展 dayu_path哟~

比如说,你们公司的文件夹层级结构非常地规范,一个文件的全路径,包含了project shot 环节等等各种信息。

那么,拿到这个路径后,如何基于 dayu_path ,得到这些信息呢?

from dayu_path import DayuPath

work_file = DayuPath('x:/projects/my_awsome_project/shots/pl/0010/ani/v0001/pl_0010_ani_v0001.ma')

# 我想这样访问信息
work_file.project() 
# 返回 my_awsome_project

work_file.sequence() 
# 返回 pl

work_file.shot() 
# 返回 0010 或者 pl_0010

work_file.step() 
# 返回 ani

# 等等

我们可以给dayu_path添加plugin,比如上面的例子,可以这样做

from dayu_path import DayuPath
from dayu_path.plugin import DayuPathPlugin

# 定义干活的函数
def project(self):
   # 我粗暴地演示下,不为运行失败负责哟~
   return self.split('/')[2]

# 注册为 dayu_path 的属性
DayuPathPlugin.register_func(project)

# 测试一下吧
DayuPath('x:/projects/my_awsome_project/shots/pl/0010/ani/v0001/pl_0010_ani_v0001.ma').project()
# 我觉得应该打印出 my_awsome_project

未来新功能

支持 udim ,可以识别和展开

项目地址

https://github.com/phenom-films/dayu_path

欢迎 watch star fork 三连击