之前一直是用第三方图片 API 当作博客文章封面,但是有一些图片并不符合我的审美。

API 的图片挺大的,用来当作壁纸合适,但是用来当小小的文章封面未免大材小用。一页博客,加载一张 API 的图片还好,加载多张就算是懒加载也有点力不从心了。

尝试过给 API 后面加上参数,限制长宽,似乎无效。

那还不如自己搞一个图片 API,里面放上我喜欢的图片。于是就有了下面的折腾。还有就是用上了 picgo cli,好像还有个压缩插件,就研究了下。

用到了 FFmpegCloudflare workerpicgo compress 插件,还有几个图片在线处理网站。

图片获取

用了几张原来的 API 的图片,其他的是 B 站一个 up 的视频封面。用了获取B站(bilibili)视频封面图 – 下载 🦊 Firefox 扩展(zh-CN) 这个拓展获取。油猴脚本都过期无效了,功能也正常。

图片处理

抠图

在线抠图软件\_图片去除背景 | remove.bg – remove.bg,用了这个网站抠图,效果还不错。

在线处理

Photopea | Online Photo Editor,本来想左侧左下角任务占比百分之五十的,想了想没必要,不过这个网站还是比较直观的。

切割指定大小,转化 webp 格式

用到了 FFmpeg,这个真是个强大的软件。用 F12 查看到封面图片的宽度,不过一般要留大一点,可能有写屏幕大一点避免模糊。

#!/bin/bash

# --- 配置 ---
# 输入文件夹名 (仅在批量模式下生效)
INPUT_DIR="半成品"
# 输出文件夹名
OUTPUT_DIR="output_webp"
# 目标宽度 (高度会按比例自动缩放)
TARGET_WIDTH=570
# WebP 图片质量 (0-100, 推荐 80)
QUALITY=80

# --- 脚本开始 ---
# 创建输出文件夹 (这是一个通用操作,提前执行)
mkdir -p "$OUTPUT_DIR"

# 检查脚本运行时是否带有参数(文件名)
if [ "$#" -gt 0 ]; then
    # 如果参数数量大于0,则进入“单独处理模式”
    echo "--- 单独处理模式 ---"
    echo "准备处理 $# 个指定文件..."
    echo ""

    # 遍历所有传入的参数(也就是文件名)
    for file in "$@"; do
        # 检查一下文件是否存在,避免报错
        if [ -f "$file" ]; then
            # 获取不带路径和扩展名的文件名
            filename=$(basename -- "$file")
            name_no_ext="${filename%.*}"

            echo "正在处理: $filename ..."

            # 使用 ffmpeg 进行转换 (核心逻辑和原来一样)
            ffmpeg -i "$file" -vf "scale=$TARGET_WIDTH:-1" -q:v "$QUALITY" -y "$OUTPUT_DIR/$name_no_ext.webp"
        else
            # 如果文件不存在,给个提示
            echo "警告: 文件 '$file' 不存在或不是一个文件,已跳过。"
        fi
    done

else
    # 如果参数数量为0(即没有传入任何文件名),则进入原有的“批量模式”
    echo "--- 批量处理模式 (处理 '$INPUT_DIR' 文件夹) ---"
    echo ""
    
    # 查找所有图片文件并进行转换 (这里的代码和原来完全一样)
    find "$INPUT_DIR" -maxdepth 1 -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.tiff" | while read -r file; do
      # 获取不带路径和扩展名的文件名
      filename=$(basename -- "$file")
      name_no_ext="${filename%.*}"

      echo "正在处理: $filename ..."

      # 使用 ffmpeg 进行转换
      ffmpeg -i "$file" -vf "scale=$TARGET_WIDTH:-1" -q:v "$QUALITY" -y "$OUTPUT_DIR/$name_no_ext.webp"
    done
fi

echo ""
echo "--- 所有图片处理完成!---"
chmod +x convert.sh # 赋予执行权限

批量重命名

下载下来后图片名都是乱码难以分辨,可以用以下命令将文件夹种的所有图片命名为 1. WebP 2. WebP,方便后续增加图片分辨。

$i = 1; Get-ChildItem | Where-Object { $_.PSIsContainer -eq $false } | ForEach-Object { Rename-Item -Path $_.FullName -NewName "$($i++)$($_.Extension)" }

图片压缩

GitHub - juzisang/picgo-plugin-compress: Image compression plugin for PicGo 这个好久没更新了,用不了 TinyPNG。

然后我在 GitHub issue 区域找到了这个 GitHub - supine0703/picgo-plugin-compress-next: Image compression plugin for PicGo(\>=^2.3.0). Update, adapt and optimize. Better support and richer features,但是也有一年更新了,有个 bug,配置页面没有弹出配置 TinyPNG 的 API 选项。这个 bug 已经被人提交了,但作者应该好久没上线了。

用不了就不用了,最后用了本地压缩。好处就是处理速度快不需要联网。
压缩率为19.3%,效果显著

压缩率为19.3%,效果显著

图片 API

因为我已经有图床了,随机调用图床图片的链接就行了。只需要将链接汇聚起来,做一个随机选择,重定向输出。同时还考虑到防盗链的问题(也许不需要)。
综上考虑做一个 serverless 服务最好,就想到了 Cloudflare,赛博大善人,用上了它的 worker 服务。

// --- 配置区 ---
// 注意:末尾不要加斜杠 /
const allowedReferer = "https://chifan.de";

// --- API 核心代码 ---

export default {
  async fetch(request, env, ctx) {

    // 1. 获取请求头中的 'Referer' 信息
    const referer = request.headers.get('Referer');

    // 2. 防盗链逻辑判断
    // (我们允许 referer 为空的情况,这样方便直接在浏览器地址栏测试)
    if (referer && !referer.startsWith(allowedReferer)) {
      // 返回 403 Forbidden 错误
      return new Response(`Hotlinking not allowed. Access denied for referer: ${referer}`, {
        status: 403,
        statusText: 'Forbidden'
      });
    }

    // 3. 如果通过了防盗链检查,就执行原来的随机图片逻辑
    const imageUrls = [
      // 在这里粘贴你的图片链接
      // 例如:
      // "https://example.com/image1.jpg",
      // "https://example.com/image2.png"
    ];

    // 从列表中随机选择一个图片链接
    const randomIndex = Math.floor(Math.random() * imageUrls.length);
    const randomImageUrl = imageUrls[randomIndex];
    
    // 如果图片列表为空或出现意外,返回一个提示
    if (!randomImageUrl) {
        return new Response('Image URL list is empty or an error occurred.', {
            status: 500,
            statusText: 'Internal Server Error'
        });
    }

    // 重定向到被选中的图片
    return Response.redirect(randomImageUrl, 302);
  },
};