面向数字艺术家的 Python 文件管理技巧

第 2 部分:面向艺术家的 Python 技巧系列
522 位读者喜欢这篇文章。
cats

Opensource.com

阅读第 1 部分:使用 Python 为数字艺术家自动化重复性任务

如果您从事数字艺术工作已经有一段时间了,那么良好的文件管理的重要性对您来说应该是显而易见的。如果您与其他艺术家合作,这一点就更加重要。每个人都有自己喜欢的命名约定和项目目录结构。当您尝试查找本应以某种方式命名的文件时,但您的合作伙伴之一认为以三个臭皮匠的俏皮话命名每个文件会更有趣时,这可能会非常令人沮丧。(嘿,这种情况确实会发生!)

一旦您开始使用脚本自动化流程的某些部分,这种挫败感就会加剧。现在,是您的代码而不是您自己找不到正确的文件。更糟糕的是,大多数脚本都不会寻找轻微命名更改的解决方法。它们根本无法工作。

幸运的是,只需几行相对简单的代码,您就可以帮助缓解此类问题。让我们以一个不是由自娱自乐的合作者引起的问题为例。有时问题可能出在您自己身上。我个人从不(咳咳)犯错。但偶尔,我使用的程序会完全按照我告诉它们的方式执行,而不是按照我打算让它们执行的方式执行。

案例分析

动画是我工作的重要组成部分。在创建动画或视觉特效时,将动画作品的每一帧输出(渲染)为单独的图像文件是一种良好的做法。(有时您每帧渲染多个图像,但请允许我简化一下以开始。)通常,这些动画帧都放在硬盘驱动器上的单独目录中。

现在,我不确定这种情况是否发生在其他人身上,或者这只是我的一个怪癖,但在某些时候(在多个软件包中),我选择了我的渲染文件应该去的目录,但随后出现了一个小故障。(有些人可能会说是“用户错误”,但请记住,我从不犯错。)我的渲染没有保存到 project/render/,而是遗漏了最后一个斜杠,并且每个渲染的图像文件都以单词“render”开头,而不是进入 render 目录。也就是说,我希望动画的第 1 帧是 project/render/frame0001.png,但程序却创建了 project/renderframe0001.png。现在我的主项目目录中充斥着数千个渲染文件。真糟糕。

我有几个选择可以支配。简单的解决方案是将所有这些渲染文件移动到正确的目录,然后简单地容忍糟糕的命名。但是,如果我的后期制作步骤期望没有以 render 单词开头的命名结构,这可能会出现问题。如果我需要重新渲染,情况可能会变得更加复杂。

说到重新渲染,这可能是另一种选择。我可以删除所有命名不正确的文件,修复动画程序中的输出路径,然后重新进行所有渲染。但是,问题在于,渲染动画帧有时可能非常耗时。对于复杂的场景,单帧可能需要一个多小时。将其乘以每秒 24-30 帧的动画,我们可以很快看出重新渲染并不是我们希望的快速简便的解决方案。

当然,始终有手动选项:将所有渲染文件移动到正确的目录,然后逐个更改每个文件的名称。当然,如果您只有几十帧,那可能不是什么麻烦。如果您有数千帧动画,那非常麻烦。

Python 脚本解决方案

那么还剩下什么?没错:编写脚本!与本系列的第 1 部分一样,我们将使用 Python 来完成此操作。在第 1 部分中,我们使用了 subprocess 模块。此示例不需要该模块,但它确实使用了 Python 的另一个内置模块 osos 模块提供了一种执行任务(例如移动和重命名文件)的方法,这些任务由您的操作系统处理。并且由于 Python 是多平台的,因此 os 模块在 Python 可以运行的任何地方都有效,而与您使用的实际操作系统无关。

因此,您的脚本的快速而简陋的版本可能如下所示

import os
    
for filename in os.listdir('./'):
    if filename.startswith('renderframe'):
        os.rename(filename, filename[:6]+'/'+filename[6:])

如果您以前从未编写过代码,则此脚本中有一些您可能不熟悉的内容。让我们从您知道的内容开始。第一行 import os 使您的脚本意识到 Python 的内置 os 模块(类似于您在上一篇文章中导入 subprocess 的方式)。

下一行代码(额外换行符除外)指示循环的开始。循环是脚本和编程中主要的省时方法之一。基本上,如果您需要一遍又一遍地执行某个过程(例如重命名一堆文件),那么循环可以拯救您(和您的理智)。

在此特定示例中,您正在使用 for 循环,这是一种特殊的循环,您可以使用它来迭代一系列事物。在本例中,您正在迭代当前目录中所有文件的名称。您是如何做到这一点的?让我向您介绍 os.listdir

Python os 模块有一个名为 listdir 的函数。该函数将任何目录路径作为输入,并返回该目录中所有文件的列表。在此示例中,您正在使用 os.listdir('./')'./' 位是一串文本(因此有引号),它是“我现在所在的当前目录”的简写。

“太好了,”您可能会说,“所以 os.listdir('./') 创建了当前目录中所有文件的列表,但这与 for 循环有什么关系?”

好问题!您不想一次处理整个文件列表。您需要一次处理一个文件。for 循环迭代 os.listdir('./') 提供的文件列表。由于您的 for 循环需要一个通用名称来调用每个正在处理的文件,因此我们使用变量 filename 作为占位符。

了解了这一点,请查看设置 for 循环的完整行:for filename in os.listdir('./'):。这一行的英文翻译是,“列出我的当前目录中的每个文件。然后循环遍历文件名列表。为了简单起见,在处理每个文件时,只需将每个文件称为 filename。”

在脚本的下一行代码中,您处于循环中(您可以看出来,因为该行已缩进)。因为您处于循环中,所以您在此处执行的任何操作都将针对当前目录中的每个文件重复执行。请记住,我们开始这样做是因为我的渲染文件最终出现在错误的位置——可能此处还有其他不是我的渲染帧的文件。我们需要确保此脚本不会重命名和移动任何文件;它需要将自身限制为仅那些渲染帧。

过滤和重命名

幸运的是,我们有一种很好的方法可以做到这一点。我们可以根据我告诉我的动画软件生成的可怕的错误命名进行过滤。当前目录中所有位置错误、命名不正确的文件都以 renderframe 开头。

对于脚本循环遍历的每个文件,它需要检查该文件是否以 renderframe 开头。这正是这行代码的作用:if filename.startswith('renderframe'):。这行代码使用了脚本和编程中的另一个常见构造,if 语句或条件语句。它以单词 if 开头,然后是测试条件。该测试条件必须为真或假。如果测试条件为真,则脚本可以执行 if 语句规定的特定代码位。如果测试条件为假,则会跳过该代码位。

在本例中,测试条件使用了 startswith 函数,该函数内置于 Python 中的所有字符串。顾名思义,如果字符串以您作为输入提供的任何文本位开头,则 startswith 函数返回 true。否则,它返回 false。因此,将 if filename.startswith('renderframe'): 翻译成英文,它将读作,“如果我们的文件名列表中的当前文件名以文本 'renderframe' 开头,则执行下一段代码。”

好的。您已经获得了当前目录中的文件列表,并且您已将该列表缩小到仅包含我们位置错误的渲染文件。现在是实际重命名并将这些文件移动到它们应该在的位置的工作了。幸运的是,可以使用 os 模块的 rename 函数在单行代码中完成此重命名和移动步骤。

os.rename 函数接受两个输入参数:您要重命名的文件以及您要将其重命名为什么。但是,很酷的部分是,这些输入参数将文件的路径视为其名称的一部分。因此,如果您在第二个输入中包含不同的路径作为一部分,则可以一次性重命名和移动文件。为减少打字欢呼吧!

现在,查看第二行文本(os.rename(filename, filename[:6]+'/'+filename[6:])),前半部分非常简单明了。逗号后面的后半部分是您以前可能没有遇到过的另一种小怪癖。但是,解开它并不困难。这只是了解您想要做什么的问题。

假设您的脚本已启动,并且正在处理文件 renderframe0001.png。要重命名和移动文件,您只需在单词 render 后添加一个 / 字符。单词 render 的长度为六个字母。有了这个小信息,您可以使用 filename 变量中已有的内容为文件构造一个新路径。您只需要正确的符号。

我一直很喜欢 Python 用于获取文本字符串子集的符号。它是 [start:end],其中 start 是子集的第一个字符,end 是子集中最后一个字符之后的字符。与每种健全的编程语言一样,Python 从数字零开始计数。因此,在我们的示例中,当我们使用包含文本 renderframe0001.pngfilename 变量时,您可以使用 filename[3:7],Python 会将 derf 作为结果提供给您。

您可能会注意到您的代码不仅使用了两次此符号,而且在每种情况下,它都缺少起始字符或结束字符的值。这是一个很酷的小便利技巧。如果您仅提供起始字符,但保留符号中的冒号,则 Python 会假定您想要字符串中该点之后的所有字符。同样,如果您仅包含结束字符值,Python 将为您提供字符串中在该字符之前的所有字符。在我们的示例中,filename[:6] 为您获取 renderfilename[6:] 符号为您获取 frame0001.png

使用此技术,您可以在单词 render 后将文件名分成两半。现在您要做的就是使用额外的斜杠 (/) 重新组装它。因此,将所有内容放在一起,这行代码 (os.rename(filename, filename[:6]+'/'+filename[6:])) 翻译为,“通过在文件名的第六个字符后插入斜杠来重命名我的文件。”

添加用户反馈

这就是描述用于移动和重命名大量位置错误文件的快速而简陋的脚本的全部内容。唯一可能值得添加到其中的是少量用户反馈。如果您要移动和重命名数千个文件,则可能需要一分钟左右的时间。了解您的脚本正在处理的文件将很有用。您可以在重命名前使用少量打印语句来完成此操作。您完成的脚本可能如下所示

import os
    
for filename in os.listdir('./'):
    if filename.startswith('renderframe'):
        print('Moving and renaming:', filename)
        os.rename(filename, filename[:6]+'/'+filename[6:])

好了!如果您像我一样,您的软件犯了完全按照您的要求执行的错误,那么这几行代码可以为您节省大量时间。

此代码块还可以作为其他有用的文件管理脚本的良好起点。例如,我喜欢 Blender 合成器中的文件输出节点。即使在渲染静态(非动画)帧的多个通道时,我也会使用它。但是,缺点是文件输出节点始终将当前帧号添加到它生成的每个文件的末尾。这对于动画来说很好,但当我只是渲染静态图像时,这有点烦人。我最终得到一堆以 0001.png 结尾的文件。

幸运的是,通过对我的移动和重命名脚本进行一些小的修改,我可以轻松地一次性修剪掉这些 0001

import os
    
for filename in os.listdir('./'):
    if filename.endswith('0001.png'):
        print('Renaming:', filename)
        os.rename(filename, filename[:-8]+filename[-4:])

此脚本与上一个脚本之间的差异非常小。此脚本不是处理文件名的开头,而是从文件名的末尾开始处理。因此,此脚本没有使用 filename.startswith,而是使用 filename.endswith 作为其过滤机制。此脚本不是在第六个字符后插入斜杠,而是修改 0001.png 之前的字符(即,直到倒数第八个字符的所有字符)。请注意 filename[:-8] 中的负数。该负值告诉 Python 从字符串的末尾而不是开头开始。

好了!现在您有了一种一次性更改大量文件的开头或结尾(或中间!)的方法。您可以节省时间并避免执行枯燥的重复性任务,从而可以将精力集中在完成更有趣的创意工作上。

User profile image.
Jason van Gumster 主要虚构内容。他写作、制作动画,偶尔也使用开源工具进行教学。他经营一家小型独立动画工作室,撰写了《Blender For Dummies》和《GIMP Bible》,并继续在 [有时] 每周播客《开源创意播客》中滔滔不绝地讲述他的经历。@monsterjavaguns 上的冒险经历(和谎言)。

5 条评论

对于 Python 新手来说,这是一篇不错的小文章!但是,有一件事可能会让他们有点困惑,那就是您在切片时如何描述第二个参数。“end”实际上不是最后一个字符;它是您要抓取的范围中最后一个字符之后的第一个字符。干杯!

您向新手解释编程的方式真好!(我会厚颜无耻地复制它,我已经警告过您了!)
顺便说一句,我也从不犯错误 ;-),这总是解释器的错!脚本语言应该有类似 WYSIWYM 的东西...
现在,由于 python 解释器背后有 WYSIWYG 理念,它正在抱怨最后一个脚本中多余的 ')'。此外,我猜您的意思是使用 '+filename[-4:]'(从倒数第四个到结尾)而不是 '+filename[:-4]'(从开头到倒数第四个)。
再次感谢您的这篇文章,我会把它展示给正在学习计算机图形学的小妹妹:我认为这对她有帮助。

最后一个脚本的完整代码应如下所示
import os

for filename in os.listdir('./')
if filename.endswith('0001.png')
print('正在重命名:', filename)
os.rename(filename, filename[:-8]+filename[-4:])

回复 作者 oldMammuth

知识共享许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.