压缩博客的图片和视频
最近琢磨着更新一下《爱丁堡的博物馆》,但因为要先把图片拖到OneDrive文件夹里,再点右键→在线查看→···→嵌入→大(1024px)→复制URL→输入![alt text]()→粘贴,所以歇了。仔细一想,这样插图确实太复杂了,干脆还是把图片全存本地,用remotely-save同步到VPS里好了——但这样就需要自己压缩图片和视频,也就有了这篇博客。
图片
压缩图片最立竿见影的方法是降分辨率。我不清楚2023年博客最适合的分辨率是多少,但我之前插的图大部分是1024px,够用。
用ImageMagick可以原地修改图片(magick mogrify),但因为我要保留原图看压缩有没有劣化太多,所以我喜欢用创建新图片的convert(仍然是ImageMagick的一部分,但切记不要在前面加上magick)。让图片最长边不超过1024px的命令是:
convert 要修改的图片 -resize 1024x1024\> 新的图片 # 注意大于号
2024-02-06更新
其实在2023-12-14,也就是写完《音乐整理博士》的前几天,我不再用ImageMagick压缩图片了。
现在我直接把原图通过git-annex记录进Git仓库,再push到S3桶中。在Netlify构建时,先恢复缓存,再从S3桶中下载新增的图片。然后使用Zola的模板函数压缩图片、把指向原图的<img>替换成带有srcset、height、width、decoding、loading属性的<img>(视频仍然用FFmpeg提前压缩好)。最后再把缓存保存到Netlify的缓存目录里。
光说有点抽象,最好还是把设置仓库的命令、压缩图片的模板、构建脚本都贴上来。但是我已不想在博客上花时间了,所以我不会解释到那种程度。只是有读者在我停更之后才发现这篇博客,我才决定至少要说明一下现在的解决方案。
这样的好处是保留了原图,之后想要改变压缩策略时不会被二次压缩。因为使用了srcset配合多种分辨率的同一张图,使用小屏幕浏览时不会加载没必要的细节。而且所有图片都由Netlify分发,速度大概会比自己的VPS快。还有,把图片一同登记到Git中,可以避免更新了Markdown,但忘了把配套图片传到VPS的粗心之举(真的发生过)。
快过年了,祝大家万事如意。
JPEG
缩小图片之后我们还可以更进一步减小JPEG图片的大小——当然,以技术指标上的降质来换。参数降低不代表观感降低,所以可以大胆地压。最长边只有1024px的JPEG,压到100KB左右观感毫无问题:
jpegoptim 新的图片(会被覆盖) --quiet --size=100K
JPEG一般是自己的相片,在分享前得把敏感元信息脱掉这件事已经是共识了。网上的教程喜欢用ExifTool或ImageMagick一口气去掉所有元信息,但其实有些元信息是应该保留的:
- EXIF Orientation信息:当你旋转一张照片时,编辑器大概率只是在这里记录下旋转了多少度,并没有真的把整张图片转过去。所以要么保留它,要么在去除它之前真的把图片转过去(convert -auto-orient)。
- ICC Profile:这里记录了图片的色彩空间等信息,具体我也不懂,但去除后图片会变色。我的做法是保留它们,因为最敏感的GPS信息是以EXIF格式存储的。
另外,拍摄时间之类的元信息也没必要清除——如果一张照片连拍摄时间都要隐藏才能发,那我建议直接别发。俗话说的好:上网不涉密,涉密不上网。
PNG
PNG格式的图片一般不会暴露敏感信息:一来谁的相机照出来是PNG啊,二来PNG到17年才加入EXIF支持。所以不用担心什么元信息。
至于压缩软件PNG我选pngquant。记得19年我选了一圈,但现在忘了当时为什么选pngquant了。
视频
可恶,我为什么要在博客里插视频呀,这不是给自己找麻烦么!我插了几段H.265编码,比特率莫名其妙地高的视频——太占地了,压缩!
最开始我用降低比特率的方法压缩,但是发现有些读者没法看H.265的视频。然后我开始考虑把H.265的视频转成VP9/AV1给看不了H.265的朋友当退路,所以对比了下libvpx-vp9、libsvtav1的编码速度和效果(libvpx-vp9的速度极慢,效果也不如libsvtav1——但它们的速度和效果都不如libx265)。考虑到这三种编码的覆盖率各有千秋,最后我还是把目光投向了所有浏览器都支持的H.264——毕竟博客上的视频是让读者看的,不是给自己收藏的,所以折腾那些读者没法看的编码、参数没有用。
可是,视频原本是H.265,如果其他条件相同,转成H.264一定会更大。那只好使用绝活了——降分辨率:
ffmpeg -i input.mp4 -vf scale=iw/2:ih/2 output.mp4
另外,在安卓上用remotely-save插件同步视频会造成Obsidian闪退,解决方法是禁止同步大文件。
脚本
基本就是给前面提到的命令加个for循环。其中EXTENDED_GLOB是为了开启GLOB的取反和或操作,NULL_GLOB则是在匹配不到任何字符串时取消执行整条命令而不报错。
还有,最开始我对每个循环都遍历一次所有文件——毕竟从复杂度上来说这和只遍历一次一样,不过我有331个文件和26个文件夹,遍历一次已经够慢了,所以还是改掉了。
#!/bin/zsh
set -euxo pipefail
SRC_FOLDER=$1
setopt EXTENDED_GLOB NULL_GLOB # Don't exec if no match
files=($SRC_FOLDER/**/(*.{jpg,png,mp4}~*-(optim.jpg|fs8.png|scaled.mp4)))
cleanup () {
sed -i "s|${1:t}|${2:t}|g" $SRC_FOLDER/**/*.md
rm $1
}
for f (${(M)files:#*.jpg}) {
local newf=${f%%.jpg}-optim.jpg
convert $f -auto-orient -resize 1024x1024\> JPG:- \
| jpegoptim - --quiet --strip-exif --size=100K > $newf
cleanup $f $newf
}
for f (${(M)files:#*.png}) {
local newf=${f%%.png}-scaled.png
convert $f -resize 1024x1024\> PNG:- | pngquant - > $newf
cleanup $f $newf
}
for f (${(M)files:#*.mp4}) {
local newf=${f%%.mp4}-crf31.webm
ffmpeg -i $f -vf scale=iw/2:ih/2 $newf
cleanup $f $newf
}