LumeCMSでWebP変換を行う
4 min read
こんにちは、無能です。
今までWordPressを使っていたこともあり、WebP変換はプラグインを導入して内部的にはImageMagickを実行してかんたんに行っていましたがそんな機能はLumeCMSでは存在しません。
もしかすると、Lumeのプラグインで可能かもしれませんが結果的には画像処理として行う効率性からCで書かれたものから実行した方がより高速で効率的です。
Nginx側のスクリプトで不可能ではありませんが、リバースプロキシを行っているということもあり他にも導入する必要があったことと、リバースプロキシを行っているサーバーのリソースは1vCPU Mem 1GB Disk 25GB
とかなり限られたリソース内で無理して行う必要性を感じませんでした。
また、内部的にはLua
スクリプトを実行することで可能にしている方もいるようです。 Nginx+Lua+libwebpによるサーバー画像の自動WebP変換の実現
欠点としてはリクエスト時にこのスクリプトが実行されるのでリソースが集中する可能性を秘めているように感じてしまいます。
npm
としてのラッパーはcwebp-bin
というものがあるようですが、実際必要なのは画像のコンバートとビルドされた静的サイトのhtmlファイルの画像パスのrewriteです。
そこで、バックエンドの処理としてLumeの変更を監視し自動でビルドしているinitデーモン
のスクリプトを自分で作ったものの中でwebpに変換するシェルスクリプトを実行することにします。
また、libvipsは使っていません。あくまでlibvipsのWebP変換には内部的にlibwebp
を使っているだけでapt等から楽にインストール出来るcwebp
と同じものであるので、cwebpで変換を行います。
変換するシェルスクリプト
以下の様に重複パターンを除き画像ファイル群を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ファイルのパスはオリジナル画像のパスを指定しています。
ということでビルド時にrewriteしてくれるようにしましょう。
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自体も同じものであるはずな気がするけどなんでだろう。
それでは。 またよろしくおねがいします。