在LumeCMS中進行WebP轉換

7 min

language: ja bn en es hi pt ru zh-cn zh-tw

您好,我是無能。

由於我之前一直使用WordPress,WebP轉換只需安裝插件並在內部執行ImageMagick即可輕鬆完成,但LumeCMS中不存在這樣的功能。

或許Lume的插件可以實現,但從圖像處理的效率來看,執行用C語言編寫的程序會更快、更高效。

雖然Nginx端的腳本並非不可能,但由於我正在進行反向代理,還需要引入其他功能,而且反向代理伺服器的資源相當有限,為1vCPU Mem 1GB Disk 25GB,因此我認為沒有必要在如此有限的資源內強行執行。

此外,似乎也有人透過在內部執行Lua腳本來實現。 透過Nginx+Lua+libwebp實現伺服器圖片自動WebP轉換

缺點是這個腳本會在請求時執行,我感覺這可能會導致資源集中。

作為npm的包裝器,似乎有cwebp-bin,但實際上需要的是圖片轉換和已建置的靜態網站HTML檔案中圖片路徑的重寫。

因此,我決定在自己編寫的init守護進程腳本中執行一個將圖片轉換為WebP的shell腳本,該腳本作為後端處理,監控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本身應該也是一樣的才對,為什麼會這樣呢?

那麼,下次再見了。

Related Posts