使用非常特定的格式重命名一批图片

使用非常特定的格式重命名一批图片

我有大约这种格式的 4k 图片:

photo_6923@06-01-2022_14-18-36.jpg
photo_6924@07-01-2022_00-03-23.jpg
photo_6925@07-01-2022_01-36-20.jpg
photo_6926@07-01-2022_10-44-20.jpg
photo_6927@07-01-2022_10-44-20.jpg

正如您所猜测的,@ 之后到下划线的第一个字符串对应于日期,从下划线到文件扩展名,再到时间。

不幸的是,秒数不显示,因此在给定分钟拍摄的照片将具有相同的结局;例如

photo_6925@07-01-2022_01-36-20.jpg
photo_6926@07-01-2022_10-44-20.jpg
photo_6927@07-01-2022_10-44-20.jpg

我想像这样重命名它们

  • (1) 删除photo_*.*@

  • (2)更改文件的格式将“-”改为“_”

  • (3)将时间的格式改为_01h36m20s而不是_01-36-20

  • (4) 如果一个文件存在多个实例,不要删除它们,而是添加后缀,如

    照片_6926@07-01-2022_10-44-20.jpg ---> 07_01_2022_01h36m20s_00001.jpg

    照片_6927@07-01-2022_10-44-20.jpg ---> 07_01_2022_01h36m20s_00002.jpg

  • (5)最后一个问题:我有不同的文件夹,其中包含不同的图片,不幸的是,在解决第 1-4 点之后,不同的文件夹中可能有两张同名的图片,因此将它们移动到新文件夹会覆盖其中的一些图片。一个潜在的解决方案是继续增加后缀的计数器(这就是为什么我在那里添加了五个零)。这意味着

如果文件夹1和文件夹2中都存在名称A,则将文件夹1和文件夹3中的所有图片移动到新文件夹时,检查是否存在多个姿势。如果是这种情况,请将计数器加一,即想象以下情况

$ ls 1/ 2/ 3/

1/ 
07_01_2022_01h36m20s_00001.jpg
07_01_2022_01h36m20s_00002.jpg

2/ 
07_01_2022_01h36m20s_00002.jpg

3/

当移动它们时,

$ mv 1/* 2/* 3/,

ls 3/ 
07_01_2022_01h36m20s_00001.jpg
07_01_2022_01h36m20s_00002.jpg
07_01_2022_01h36m20s_00003.jpg <-------- counter added one here

谢谢! PS:可以使用元数据来做到这一点,但不幸的是所有元数据都已从图片中删除。

答案1

zsh

autoload zmv
typeset -A n=()
#       1              23     4     5     6     7     8     9
zmv -n '(**/)photo_<->@((<->)-(<->)-(<->)_(<->)-(<->)-(<->))(.jpg)(#qn.)' \
       '$1${3}_${4}_${5}_${6}h${7}m${8}s_${(l[5][0])$((++n[\$2]))}$9'

如果满意,请删除-n(试运行)。在你的样本上,这给出了:

mv -- photo_6923@06-01-2022_14-18-36.jpg 06_01_2022_14h18m36s_00001.jpg
mv -- photo_6924@07-01-2022_00-03-23.jpg 07_01_2022_00h03m23s_00001.jpg
mv -- photo_6925@07-01-2022_01-36-20.jpg 07_01_2022_01h36m20s_00001.jpg
mv -- photo_6926@07-01-2022_10-44-20.jpg 07_01_2022_10h44m20s_00001.jpg
mv -- photo_6927@07-01-2022_10-44-20.jpg 07_01_2022_10h44m20s_00002.jpg

解释:

  • typeset -A n=()创建一个A最初为空的关联数组
  • zmv是一个可自动加载的函数,它需要一个全局模式(扩展的全局变量,zmv确实extendedglob在执行期间设置了该选项)并且表达作为两个单独的参数,并重命名与图案到扩大表达其中$1, $2... 对应于模式中相应的第n 对 所匹配的内容。(...)
  • 对于模式:
    • **/匹配任何级别的子目录(包括 0),并且该子目录位于第一个目录中,因此将在替换中(...)可用。$1请注意 zmv 处理文件深度优先(在它们所在的树枝之前离开)就好像被赋予了(#qod) 全局限定符这在重命名文件时通常很重要,但在这里并不重要,因为我们只重命名常规文件,而不是目录。
    • <->匹配任何正十进制整数。这就像<1-31>但未指定边界,因此匹配<0-infinity>或 IOW 任何十进制数字序列。(<1-31>)-(<1-12)-(<1900-2100>)...如果您希望匹配更严格,可以将其更改为。
    • (#q...)添加 glob 限定符,n以便排序为数字排序,而不是默认的词法排序(photo_10因此 photo_2例如而不是之前),并将.匹配限制为常规的文件(不包括所有其他类型,例如目录、符号链接、fifos...)。
  • 对于更换:
    • ${(l[5][0])expansion} l5eft用s 将扩展填充到字符长度0
    • $(( ++n[\$2] ))扩展为 key 的关联数组元素的值(与第二个, so$2匹配)加一。请注意,延迟该参数的取消引用,因此它不会在算术表达式内扩展。例如,如果被包含(这里不是这种情况),这将是一个问题。(...)((<->)-(<->)-(<->)_(<->)-(<->)-(<->))\$2]

要根据文件名中的日期设置 EXIF CreateDate:

exiftool -r -ext jpg -d '%d-%m-%Y_%H-%M-%S' \
  -if '$Filename =~ /@\d+-\d+-\d+_\d+-\d+-\d+\.jpg\z/i' \
  -'CreateDate<${FileName;s/.*@//;s/\.jpg\z//i}' .

(与外壳无关)/

解释:

  • -rr在作为参数传递的文件中以递归方式查找文件目录(此处为.当前工作目录)。
  • -ext jpg:仅考虑带有扩展名的文件jpg(不区分大小写)。
  • -if 'perl expression':进一步将过滤器限制为那些Perl 表达式返回真。
  • $Filename =~ /@\d+-\d+-\d+_\d+-\d+-\d+\.jpg\z/i:文件名(不带目录部分)是否与给定的perl正则表达式匹配,因此这里以.结尾@<digits>-<digits>-<digits>_<digits>-<digits>-<digits>-.jpg
  • -d '%d-%m-%Y_%H-%M-%S'使用 strftime/strptime 模板对日期进行f排序/排列。p
  • -'CreateDate<date':将CreateDateEXIF 元数据属性设置为指定日期,此处:
  • ${FileName;s/.*@//;s/\.jpg\z//i}我们已经删除了最右边的所有内容的文件名@.jpg扩展名。

答案2

我已经探索了一个解决方案,使用TXR口齿不清。目前的状态有点冗长。它将名称解析为结构。

对于可能存在多个目录而导致名称冲突的要求,我们应该同时处理所有目录。我准备了这个示例路径集:

path/a/photo_6923@06-01-2022_14-18-36.jpg
path/a/photo_6924@07-01-2022_00-03-23.jpg
path/a/photo_6925@07-01-2022_01-36-20.jpg
path/a/photo_6926@07-01-2022_10-44-20.jpg
path/a/photo_6927@07-01-2022_10-44-20.jpg
path/to/b/photo_6923@06-01-2023_14-18-36.jpg
path/to/b/photo_6924@07-01-2023_00-03-23.jpg
path/to/b/photo_6925@07-01-2023_01-36-20.jpg
path/to/b/photo_6926@07-01-2023_10-44-20.jpg
path/to/b/photo_6927@07-01-2022_10-44-20.jpg

我们在一个path/a目录和一个path/to/b目录中都有名字。存在具有相同时间/日期的冲突条目07-01-2022_10-44-20

在实际程序中,我们将使用一些 glob 表达式来获取名称,例如:

(glob "{path/a,path/to/b}/photo_*.jpg")

而不是从文件中读取,我们可以dummy-renamerename-path.

跑步:

$ txr rename.tl
path/to/b/photo_6926@07-01-2023_10-44-20.jpg -> path/to/b/07_01_2023_10h44m20s
path/a/photo_6926@07-01-2022_10-44-20.jpg -> path/a/07_01_2022_10h44m20s_00000
path/a/photo_6927@07-01-2022_10-44-20.jpg -> path/a/07_01_2022_10h44m20s_00001
path/to/b/photo_6927@07-01-2022_10-44-20.jpg -> path/to/b/07_01_2022_10h44m20s_00002
path/to/b/photo_6924@07-01-2023_00-03-23.jpg -> path/to/b/07_01_2023_00h03m23s
path/a/photo_6924@07-01-2022_00-03-23.jpg -> path/a/07_01_2022_00h03m23s
path/to/b/photo_6925@07-01-2023_01-36-20.jpg -> path/to/b/07_01_2023_01h36m20s
path/a/photo_6925@07-01-2022_01-36-20.jpg -> path/a/07_01_2022_01h36m20s
path/to/b/photo_6923@06-01-2023_14-18-36.jpg -> path/to/b/06_01_2023_14h18m36s
path/a/photo_6923@06-01-2022_14-18-36.jpg -> path/a/06_01_2022_14h18m36s

代码在rename.tl

(defstruct name ()
  orig dir number time
  (:method fmt (me)
    (let ((tm me.time))
      `@{tm.month}_@{tm.day}_@{tm.year}_@{tm.hour}h@{tm.min}m@{tm.sec}s`)))

(defun parse (str)
  (let ((dir (dir-name str))
        (base (base-name str)))
    (match `photo_@num\@@mm-@dd-@{yyyy}_@HH-@[email protected]` base
      (new name orig str dir dir number num
                time (new time year yyyy month mm day dd
                               hour HH min MM sec SS)))))

(defun dummy-rename (from dir to)
  (put-line `@from -> @(path-cat dir to)`))

(flow (file-get-lines "data")
  (mapcar parse)
  (group-by .time)
  (dohash (date part @1)
    (if (eql 1 (len part))
      (let ((n (first part)))
        (dummy-rename n.orig n.dir n.(fmt)))
      (each ((n part)
             (i "00000".."99999"))
        (dummy-rename n.orig n.dir `@{n.(fmt)}_@i`)))))

只有与相同日期/时间冲突的文件才会获得递增标记。由于这是跨多个目录的,因此我们不需要五位数字。

该算法的核心是获取解析的对象并根据日期将它们分组。对于那些仅包含一个元素的组,我们格式化名称而不使用附加计数器。在两个或更多的组中,我们迭代它们,同时并行迭代 00000 到 99999,并将其作为后缀。

重命名未排序,因为group-by会生成哈希表。我们可以通过以下方式更好地了解组织sort

$ txr rename.tl | sort
path/a/photo_6923@06-01-2022_14-18-36.jpg -> path/a/06_01_2022_14h18m36s
path/a/photo_6924@07-01-2022_00-03-23.jpg -> path/a/07_01_2022_00h03m23s
path/a/photo_6925@07-01-2022_01-36-20.jpg -> path/a/07_01_2022_01h36m20s
path/a/photo_6926@07-01-2022_10-44-20.jpg -> path/a/07_01_2022_10h44m20s_00000
path/a/photo_6927@07-01-2022_10-44-20.jpg -> path/a/07_01_2022_10h44m20s_00001
path/to/b/photo_6923@06-01-2023_14-18-36.jpg -> path/to/b/06_01_2023_14h18m36s
path/to/b/photo_6924@07-01-2023_00-03-23.jpg -> path/to/b/07_01_2023_00h03m23s
path/to/b/photo_6925@07-01-2023_01-36-20.jpg -> path/to/b/07_01_2023_01h36m20s
path/to/b/photo_6926@07-01-2023_10-44-20.jpg -> path/to/b/07_01_2023_10h44m20s
path/to/b/photo_6927@07-01-2022_10-44-20.jpg -> path/to/b/07_01_2022_10h44m20s_00002

我们可以清楚地看到,在第二个目录中,名称避免了冲突07_01_2022_10h44m20s_00002

答案3

使用 GNU 并行:

parallel mv -- {} '{=s/(.*)photo_\d+\@((..)-(..)-(....)_(..)-(..)-(..))(.jpg)/$_="$1$3_$4_$5_$6h$7m$8s_".sprintf("%05d",++$n{$2}).$9/e =}' ::: dira/* dirb/*

所有文件名现在都是唯一的,并且可以移动到同一文件夹中。

相关内容