压缩博客的图片和视频

最近琢磨着更新一下《爱丁堡的博物馆,但因为要先把图片拖到OneDrive文件夹里,再点右键在线查看→···→嵌入大(1024px)→复制URL→输入![alt text]()→粘贴,所以歇了。仔细一想,这样插图确实太复杂了,干脆还是把图片全存本地,用remotely-save同步到VPS里好了——但这样就需要自己压缩图片和视频,也就有了这篇博客。

图片

压缩图片最立竿见影的方法是降分辨率。我不清楚2023年博客最适合的分辨率是多少,但我之前插的图大部分是1024px,够用。

ImageMagick可以原地修改图片(magick mogrify,但因为我要保留原图看压缩有没有劣化太多,所以我喜欢用创建新图片的convert(仍然是ImageMagick的一部分,但切记不要在前面加上magick。让图片最长边不超过1024px的命令是:

convert 要修改的图片 -resize 1024x1024\> 新的图片 # 注意大于号

JPEG

缩小图片之后我们还可以更进一步减小JPEG图片的大小——当然,以技术指标上的降质来换。参数降低不代表观感降低,所以可以大胆地压。最长边只有1024pxJPEG,压到100KB左右观感毫无问题:

jpegoptim 新的图片(会被覆盖) --quiet --size=100K

JPEG一般是自己的相片,在分享前得把敏感元信息脱掉这件事已经是共识了。网上的教程喜欢用ExifToolImageMagick一口气去掉所有元信息,但其实有些元信息是应该保留的:

  1. EXIF Orientation信息:当你旋转一张照片时,编辑器大概率只是在这里记录下旋转了多少度,并没有真的把整张图片转过去。所以要么保留它,要么在去除它之前真的把图片转过去(convert -auto-orient
  2. ICC Profile:这里记录了图片的色彩空间等信息,具体我也不懂,但去除后图片会变色。我的做法是保留它们,因为最敏感的GPS信息是以EXIF格式存储的。

另外,拍摄时间之类的元信息也没必要清除——如果一张照片连拍摄时间都要隐藏才能发,那我建议直接别发。俗话说的好:上网不涉密,涉密不上网。

PNG

PNG格式的图片一般不会暴露敏感信息:一来谁的相机照出来是PNG啊,二来PNG17年才加入EXIF支持。所以不用担心什么元信息。

至于压缩软件PNG我选pngquant。记得19年我选了一圈,但现在忘了当时为什么选pngquant了。

视频

可恶,我为什么要在博客里插视频呀,这不是给自己找麻烦么!我插了几段H.264编码,比特率莫名其妙地高的视频——太占地了,压缩!

因为1080p的分辨率已经够低了,所以我不想通过降分辨率来压缩。余下的方法有换编码和调整比特率。编码方面我选H.265,不选VP9的原因是VP9编码器太慢了,不选AV1是因为太新了。

比特率这块,我是一头雾水,主要原因是FFmpeg的选项太多了。根据《Understanding Rate Control Modes (x264, x265, vpx)》这篇博客的结论,我的需求是Streaming,应该选择Two-pass CRF or ABR with VBV-constained bitrate。但那篇文章前面的部分根本没提到Two-pass CRF这种做法,我也不能理解这是什么意思,文章作者也不明白Two-pass CRF是啥。我认为作者笔误了,Streaming应该选的是Two-pass ABR or CRF with VBV-constained bitrate。

明白了不同的rate control后,我心里仍然没数:具体参数怎么选啊。其实我只是想把那几个视频变小而已,做出什么让步、达成什么效果都行。所以我直接用FFmpeg Encode/H.265 Wiki里见到的第一个例子:

ffmpeg -i input -c:v libx265 -crf 26 -preset fast output.mp4

另外,在安卓上用remotely-save插件同步视频会造成Obsidian闪退,解决方法是禁止同步大文件。

脚本

基本就是给前面提到的命令加个for循环。其中EXTENDED_GLOB是为了开启GLOB的取反和或操作,NULL_GLOB则是在匹配不到任何字符串时取消执行整条命令而不报错。

还有,最开始我对每个循环都遍历一次所有文件——毕竟从复杂度上来说这和只遍历一次一样,不过我有331个文件和26个文件夹,遍历一次已经够慢了,所以还是改掉了。

#!/bin/zsh

setopt EXTENDED_GLOB NULL_GLOB
files=(**/(*.{jpg,png,mp4}~*-(optim.jpg|fs8.png|crf26.mp4)))

for f (${(M)files:#*.jpg})
        convert $f -auto-orient -resize 1024x1024\> JPG:- | jpegoptim - --quiet --strip-exif --size=100K > ${f%%.jpg}-optim.jpg && rm $f

for f (${(M)files:#*.png})
        convert $f -resize 1024x1024\> PNG:- | pngquant - > ${f%%.png}-fs8.png && rm $f

for f (${(M)files:#*.mp4})
        ffmpeg -i $f -c:v libx265 -crf 26 -preset fast ${f%%.mp4}-crf26.mp4 && rm $f

🐘在Mastodon中搜索https://emptystack.top/note/compress-image-video以点赞、评论。