在 LumeCMS 中进行 WebP 转换
大家好,我是无能。
由于我之前一直使用 WordPress,WebP 转换通过安装插件并在内部执行 ImageMagick 就可以轻松完成,但 LumeCMS 中不存在这样的功能。
也许 Lume 的插件可以实现,但从图像处理的效率来看,执行用 C 编写的程序会更快、更高效。
虽然通过 Nginx 端的脚本并非不可能,但考虑到正在进行反向代理,还需要引入其他功能,而且反向代理服务器的资源是1vCPU Mem 1GB Disk 25GB,在如此有限的资源内,我没有觉得有必要强行进行。
此外,似乎也有人通过在内部执行Lua脚本来实现。 通过 Nginx+Lua+libwebp 实现服务器图像的自动 WebP 转换
缺点是,由于此脚本在请求时执行,我感觉它有可能导致资源集中。
似乎有一个名为cwebp-bin的npm包装器,但实际需要的是图像转换和已构建的静态网站 HTML 文件中图像路径的重写。
因此,我决定在自己创建的init守护进程脚本中执行一个 shell 脚本来将图像转换为 webp,该脚本作为后端处理,监控 Lume 的更改并自动构建。
此外,我没有使用 libvips。因为 libvips 的 WebP 转换内部只是使用了libwebp,它与可以从 apt 等轻松安装的cwebp是相同的,所以我将使用 cwebp 进行转换。
转换用的 Shell 脚本
如下所示,排除重复模式,将图像文件组转换为WebP。
#!/bin/bash
# 监控目录
SOURCE_DIR="/var/www/html/soulmining/src/uploads"
# WebP 输出目录
DEST_DIR="/var/www/html/soulmining/src/uploads"
# 待转换的扩展名
EXTENSIONS=("png" "jpg" "jpeg")
# WebP 质量设置 (0-100)
QUALITY=80
# 检查所需命令
command -v cwebp >/dev/null 2>&1 || { echo >&2 "未找到 cwebp 命令。请安装。"; exit 1; }
# 如果输出目录不存在则创建
mkdir -p "$DEST_DIR"
# 处理文件的函数
process_file() {
local file="$1"
local filename=$(basename "$file")
local name="${filename%.*}"
local dest_file="$DEST_DIR/${name}.webp"
# 跳过已转换的文件
if [ -f "$dest_file" ]; then
echo "跳过: $filename (已转换)"
return
fi
# 转换为 WebP
cwebp -q $QUALITY "$file" -o "$dest_file"
if [ $? -eq 0 ]; then
echo "转换成功: $filename -> ${name}.webp"
else
echo "转换失败: $filename"
fi
}
# 主处理
for ext in "${EXTENSIONS[@]}"; do
find "$SOURCE_DIR" -type f -name "*.$ext" | while read file; do
process_file "$file"
done
done
echo "处理完成"
将其追加到 init 守护进程的 c/initme-watcher`](httpithub.cturame-watcher)中。
#!/bin/bash
### BEGIN INIT INFO
# Provides: lume-watcher
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Watches directory and triggers Lume task
# Description: Watches the specified directory and triggers the Deno Lume task when changes are detected.
### END INIT INFO
WATCHED_DIR="/var/www/html/soulmining/src/"
COMMAND="/home/haturatu/.deno/bin/deno task lume --dest=site"
CONV_WEBP="/opt/sh/webp.sh"
~~~
省略 (省略)
~~~
# 开始监控
monitor_directory() {
inotifywait -m -r -e modify,create,delete "$WATCHED_DIR" | while read -r directory events filename; do
echo "$(date): Change detected" >> "$LOG_FILE"
last_run_time=$(get_last_run_time)
now=$(current_time)
if [ $((now - last_run_time)) -ge $COOLDOWN_TIME ]; then
check_and_rotate_log
echo "$(date): Executing command" >> "$LOG_FILE"
sleep 0
cd $OUTPUT_ROOT_DIR || exit
$CONV_WEBP >> "$LOG_FILE" 2>&1
$COMMAND >> "$LOG_FILE" 2>&1
cd ~ || exit
set_last_run_time
else
echo "$(date): Command not executed due to cooldown" >> "$LOG_FILE"
fi
done
}
~~~
以下略 (以下省略)
~~~
严格来说,这个 init 守护进程会检测目标目录内的所有更改,所以即使只上传了图片也会触发重新构建,但这也没办法。
如果要做的话,可以在执行inotifywait时指定选项 --exclude "dth"。
追加到 _config.ts
好了,还有工作要做。
如果保持原样,Lume 构建时 HTML 文件的路径会指定原始图像的路径。
所以,让我们在构建时进行重写。
import { walk } from "httpeno.lad/d.ts";
~~~
略 (省略)
~~~
site.addEventListener("afterBuild", async () => {
const buildDir = site.dest() // 构建输出目录
for await (const entry of walk(buildDir, { exts: [".html"] })) {
if (entry.isFile) {
let content = await Deno.readTextFile(entry.path);
// 将 URL 转换为 .webp 的正则表达式
const regex rcload"'\s]+\.(png|jpe?g|gif;
content = content.replace(regex, (match) => {
return match.replac(png|jpe?g|gif '.webp');
});
await Deno.writeTextFile(entry.path, content);
console.log(`Processed: ${entry.path}`);
}
}
});
说实话,在lume-watcher中使用sed来重写会更短、更快、更方便……这是个秘密。
顺便说一下,Lume 的 -w 选项不稳定
虽然我运行着守护进程,但 Lume 官方也提供了 watch 功能,如果使用-w选项启动deno task lume -w,可以不使用简易服务器,在文件每次更改时生成 HTML,但这并不稳定。
在我的环境下经常崩溃。
LumeCMS 本身在 cms 仓库中的 `apteme.ts` 文件内
// Start the watcher
const watcher = site.getWatcher();
// deno-lint-ignore no-explicit-any
watcher.addEventListener("change", async (event: any) => {
const files = event.files!;
await site.update(files);
dispatch("previewUpdated");
});
watcher.start();
似乎是通过它调用的。
await site.update(files);
看起来是在这里。
既然它是在 LumeCMS 的代码中被调用的,那么 Lume 本身应该也是一样的,但为什么呢?
那么。下次再见。