批量并行的图片转码

快速将大量图片转码为所需的格式

Posted by wszqkzqk on October 29, 2023
本文字数:2279

前言

相机、手机等设备拍摄的照片通常输出为JPEG格式,而直出的JPEG图片不仅压缩率低,而且还为了保存图片上的无用噪点而浪费了大量的存储空间。笔者经常需要将这些JPEG图片转码为WebP格式,以节省存储空间。

笔者以前通常使用bash、zsh或fish的for循环,调用各编解码器的命令行工具来实现批量转码,但是WebP等图片往往无法多线程编码,在转码时只有一个核实际参与计算,导致电脑性能不能得到充分利用。

parallel的使用

笔者最近开始使用parallel这个工具进行并行的多张图片批量转码,它可以将多个命令行工具的输出进行并行处理,从而充分利用电脑的性能。

首先需要安装parallel,在Arch Linux下可以使用pacman安装:

sudo pacman -S parallel

parallel的基本使用方法非常简单,既可以使用:::<来传递并行处理的参数,也可以使用管道符|来传递并行处理的参数,例如:

parallel echo ::: 1 2 3 4 5
parallel echo < <(seq 1 5)
seq 1 5 | parallel echo

这三条命令的输出均为:

1
2
3
4
5

parallel还可以接受一些选项来调整并行处理的参数,例如:

  • -j:指定并行处理的最大任务数,例如-j 4表示最多同时处理4个任务;默认值为CPU核心数。
  • -k:保持输出的顺序,即使任务完成的顺序不同。
  • --bar:显示进度条。

parallel用于并行图片转码

在有多张图片的情况下,使用parallel可以弥补图片编码器本身不支持多线程编码的问题,从而充分利用电脑的性能。

可以使用以下命令将当前目录下的所有JPEG图片转码为WebP格式,保留所有元数据信息,并显示进度条:

parallel --bar "cwebp -metadata all {} -o {.}.webp" ::: *.jpg

这里的{}表示parallel接受的参数,{.}表示parallel接受的参数的文件名部分,:::表示parallel接受的参数来自于命令行,*.jpg表示当前目录下的所有JPEG图片。

对于较大的WebP图片,则需要对cwebp命令的-segments选项调整为1,还可以将-partition_limit选项调整为100,从而提高编码速度:

parallel --bar "cwebp -metadata all -segments 1 -partition_limit 100 {} -o {.}.webp" ::: *.jpg

如果需要同时删除原图,可以使用以下命令:

parallel --bar "cwebp -metadata all -segments 1 -partition_limit 100 {} -o {.}.webp && rm {}" ::: *.jpg

类似地,如果需要调用ffmpeg或者convert其他命令行工具来批量转码图片,也可以使用parallel来实现:

parallel --bar "ffmpeg -hwaccel auto -i {} -c:v libsvtav1 {.}.avif" ::: *.jpg
parallel --bar "convert {} {.}.avif" ::: *.jpg

对于音频转码,可以考虑类似的方法,但是对于视频转码则没有必要,因为视频编码器一般有良好的多线程支持。

parallel的字符串替换

GNU parallel中的替换字符串的说明:

  • {}:输入行。这个替换字符串将被从输入源读取的完整行替换。输入源通常是stdin(标准输入),但也可以通过-a, :::, 或::::给出。如果命令行不包含替换字符串,那么{}将被添加到命令行的末尾。
  • {.}:无扩展名的输入行。这个替换字符串将被去掉扩展名的输入替换。例如,foo.jpg变为foosubdir/foo.jpg变为subdir/foo
  • {/}:输入行的基本名。这个替换字符串将被去掉目录部分的输入替换。
  • {//}:输入行的目录名。这个替换字符串将被输入行的目录部分替换。
  • {/.}:无扩展名的输入行的基本名。这个替换字符串将被去掉目录和扩展名部分的输入替换。
  • {#}:要运行的作业的序列号。这个替换字符串将被正在运行的作业的序列号替换。
  • {%}:作业插槽号。这个替换字符串将被作业的插槽号替换,插槽号在1和并行运行的作业数量之间。
  • {n}:来自输入源n的参数或第n个参数。这个位置替换字符串将被输入源n的输入或第n个参数替换。
  • {n.}:来自输入源n的参数或第n个参数,但是没有扩展名。
  • {n/}:来自输入源n的参数或第n个参数的基本名。
  • {n//}:来自输入源n的参数或第n个参数的目录名。
  • {n/.}:来自输入源n的参数或第n个参数的基本名,但是没有扩展名。
  • {=perl expression=}:用计算的perl表达式替换。$_将包含与{}相同的内容。计算perl表达式后,$_将被用作值。
  • {=n perl expression=}:等同于{= perl expression =}的位置替换字符串。

:::::::的组合使用

多个:::::::可以组合使用,其中,每个:::::::所跟的参数组都将视为输入源,并生成所有输入源的组合,例如:

parallel echo ::: 1 2 ::: a b c

这条命令的输出为:

1 a
1 b
1 c
2 a
2 b
2 c

:::::::的区别在于,:::是在命令中直接读取输入源,而::::是从文件中读取输入源,例如:

parallel echo :::: input.txt

如果这里的input.txt文件内容为:

abcd
efgh
ijkl

那么这条命令的输出为:

abcd
efgh
ijkl