惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
A
Arctic Wolf
S
Security Affairs
O
OpenAI News
SecWiki News
SecWiki News
TaoSecurity Blog
TaoSecurity Blog
H
Heimdal Security Blog
T
Threat Research - Cisco Blogs
Hacker News: Ask HN
Hacker News: Ask HN
N
News | PayPal Newsroom
Google Online Security Blog
Google Online Security Blog
C
Cisco Blogs
The Hacker News
The Hacker News
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Privacy International News Feed
V
Vulnerabilities – Threatpost
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
T
Tenable Blog
T
The Exploit Database - CXSecurity.com
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Spread Privacy
Spread Privacy
人人都是产品经理
人人都是产品经理
www.infosecurity-magazine.com
www.infosecurity-magazine.com
V2EX - 技术
V2EX - 技术
L
LINUX DO - 最新话题
The GitHub Blog
The GitHub Blog
博客园 - 三生石上(FineUI控件)
T
The Blog of Author Tim Ferriss
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
The Cloudflare Blog
N
News and Events Feed by Topic
量子位
Google DeepMind News
Google DeepMind News
Application and Cybersecurity Blog
Application and Cybersecurity Blog
L
LINUX DO - 热门话题
P
Palo Alto Networks Blog
Stack Overflow Blog
Stack Overflow Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Attack and Defense Labs
Attack and Defense Labs
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hacker News - Newest:
Hacker News - Newest: "LLM"
Apple Machine Learning Research
Apple Machine Learning Research
The Register - Security
The Register - Security
Microsoft Security Blog
Microsoft Security Blog
Know Your Adversary
Know Your Adversary
Webroot Blog
Webroot Blog

mephisto.cc

修复rime-ice无法弹出候选框的问题 开机滚动字体显示优化 个人网站安全防护 树莓派上部署Vaultwarden 使用goaccess实时分析Caddy日志 个人网站监控 Linux下尝试使用Godot开发小游戏 Arch linux dae 透明代理 Airflow接管galler-dl下载任务 如何使用gallery-dl批量下载图像 Arch核显下如何愉快玩Dota2和CS2 Arch/labwc 环境网络相关设置 Rime添加dota2词库 Arch 重装记录 Supertuxkart iOS版终于发布了 打造自己的流媒体音乐服务 剪切板管理工具clipcat推荐 MangoHud性能监控 微信小程序开发记 Rofi试用 解决Arch下VSCode无法输入中文的问题 独立窗口管理器下无法录屏问题处理 上海市二手房成交数据监控 OpenLDAP监控 Arch linux如何顺畅连接蓝牙设备 使用Git和Ansible管理配置文件 Arch Linux SSL VPN 客户端配置 简单获取celery任务实时结果 Arch linux下iNode客户端的安装和使用方法 券商费率调整记 迷你主机组装记 Labwc更换定制皮肤 树莓派跑分流代理 实用自动代理配置示例 九年老鼠标复活记 Openvpn示例 Mac下如何旋转webp图片 如何隐藏Vscode中Markdown PDF插件的头部内容 解决Linux下网络配置无法变更保存的问题 labwc环境启用wlogout Ubuntu下新官方微信尝鲜 简单检测用户是否开启广告屏蔽 Atuin ZFS下卡顿问题解决 Wine安装64位微信 Fish 和 Atuin使用记 Fastapi简单实现临时下载链接 Fastapi微信公众号开发简要 Hugo根据语种展示不同内容 如何修复Waybar微信图标错误 信封加密简要 Linux环境下维护公众号记录 快速隐藏和呼出终端 Wayland环境自动切换壁纸 SuperTuxKart 试玩 简单抓取雪球股票快讯 为什么你的kill命令不能列出信号表 Wine安装微信保姆教程 Hysteria科学上网简要 Ubuntu 23.10 钉钉无法启动解决方法 网站导航栏防止插入Adsense自动广告 Linux环境按键检测 Hugo文末添加最后修改时间 Wayland环境ksnip无法复制问题解决 我眼中的股票市场 Caddy简单图片防盗链 greetd和regreet使用教程 Wayland环境下截图加后期修改 Wayland 环境下gif录屏 Firefox的一些有趣功能 Ubuntu 切换系统语言 Firefox 标题栏高度调整 网站添加回到顶部功能 Hugo自定义字体 Labwc 便捷配置 Wofi使用教程 窗口管理器labwc使用记 文件共享软件Dufs推荐 Hugo静态站点接入Adsense广告 Fcitx5配置详解 Ubuntu安装chrome的方法总结 一种Xterm.js的全屏方案 Google网址收录api Python示例 手机听离线音乐 Snipe it资产导入 应用启动器yofi使用配置 Snipe it资产管理系统安装使用 Ubuntu开机启动加速 Vue3 vite TypeScript跨域等相关设置 Ubuntu手动升级Libreoffice Ubuntu设置alacritty为默认终端 简体文章批量转换为繁体 使用Inkscape调整svg图片大小 实用命令 使用mitmproxy给手机app抓包 Python中rstrip方法细解 Github同主机多仓库部署deploy key问题处理 Linux安装最新版本Python KeePassXC使用教程 Ubuntu 22.10连接蓝牙耳机报错br-connection-profile-unavailable解决方法 Caddy日志配置轮转和格式化
Hugo全站AVIF记
2026-01-16 · via mephisto.cc

当你读到这篇文章的时候,本站除了一些 logo、thumbnail、icon(个人觉得不适合),绝大部分图片已完成从 png、jpg、WebP 等到 AVIF 的切换。

所谓 AVIF(AV1 Image File Format)是基于 AV1 编码、开放免版税的高效图像格式,是当前“压缩效率 + 现代特性 + 开放生态”的最佳平衡方案,显著优于 JPEG、WebP、PNG 等传统格式。

说人话是:节约带宽、用户加载快、可能利于 SEO。

时代在变化,以前用 Webp,觉得已经很不错了,如今又得切换。年近八旬的老人唐纳德·特朗普都在为美国四处抢矿、抢油、抢地盘,给自己网站优化下图片方案有何不可?利己利人。

先看疗效,非常显著!同一个文件,不同格式的体积差别:

1➜ ls -al GPXSee.png
2-rw-r--r-- 1 mephisto mephisto 802779 Dec 10 20:35 GPXSee.png
3➜ ls -al GPXSee.webp
4-rw-r--r-- 1 mephisto mephisto 126820 Dec 10 20:35 GPXSee.webp
5➜ mogrify -format avif GPXSee.png
6➜ ls -lhrS --time-style=+"" GPXSee.*
7-rw-r--r-- 1 mephisto mephisto 103K  GPXSee.avif
8-rw-r--r-- 1 mephisto mephisto 124K  GPXSee.webp
9-rw-r--r-- 1 mephisto mephisto 784K  GPXSee.png

本站原始图片一般是 png 格式,来自各种截图软件的默认输出,我总是尽力保留原始图片和一个小体积压缩图片,之前是 png 和 webp,很明显现在要换成 png 和 AVIF 了,webp 后续可能考虑删除。

原始图片效果最清晰,以防不时之需,兜底的。

1. 如何转换格式

  • 单张转换
1magick input.png output.avif
  • 批量转换

批量操作要小心,你应该懂的!,毕竟受响图片多,mogrify 会保留原始图片(各个 Linux 发行版是否行为一致未测试)。

决定动手前,最好自己创建个临时目录,放入各种图片测试下,看看是否和预期一致。

1# jpg/jpeg/png/webp → avif
2mogrify -format avif *.jpg *.jpeg *.png *.webp

magickmogrify都来自 imagemagick 包,不得不说真的很强大。

1➜  ~ pacman -Qo $(which magick)
2/usr/bin/magick is owned by imagemagick 7.1.2.12-2
3➜  ~ pacman -Qo $(which mogrify)
4/usr/bin/mogrify is owned by imagemagick 7.1.2.12-2
  • 写程序转换

我让 AI 帮我写了个 Golang 程序,初步审核了下,符合自己需求。

需求:

  • 转换图片格式,优先级 png > jpg > jpeg > webp,因为我知道自己的图片源是这个顺序,有 png 优先使用 png 源转换压缩。
  • 帮忙修改 Markdown 中的图片连接
  1package main
  2
  3import (
  4	"flag"
  5	"fmt"
  6	"os"
  7	"os/exec"
  8	"path/filepath"
  9	"regexp"
 10	"strings"
 11)
 12
 13// 定义源文件优先级:png > jpg > jpeg > webp
 14var srcPriorities = []string{"png", "jpg", "jpeg", "webp"}
 15
 16// convertToAVIF 按优先级查找源文件并转换为 AVIF(支持深层目录)
 17// relDir: 图片相对于 imagesDir 的子目录(如 travel/wugongshan)
 18func convertToAVIF(imagesDir, relDir, filename string) (bool, error) {
 19	var srcPath string
 20	// 1. 拼接子目录路径
 21	fullRelDir := filepath.Join(imagesDir, relDir)
 22
 23	// 2. 按优先级查找源文件
 24	for _, ext := range srcPriorities {
 25		candidate := filepath.Join(fullRelDir, fmt.Sprintf("%s.%s", filename, ext))
 26		if _, err := os.Stat(candidate); err == nil {
 27			srcPath = candidate
 28			break
 29		}
 30	}
 31
 32	// 无可用源文件
 33	if srcPath == "" {
 34		return false, fmt.Errorf("无可用源文件(png/jpg/jpeg/webp)")
 35	}
 36
 37	// 3. 目标 AVIF 文件路径(保持原目录结构)
 38	avifPath := filepath.Join(fullRelDir, fmt.Sprintf("%s.avif", filename))
 39
 40	// 检查 AVIF 是否已存在,避免重复转换
 41	if _, err := os.Stat(avifPath); err == nil {
 42		fmt.Printf("ℹ️ AVIF 文件已存在,跳过: %s\n", avifPath)
 43		return true, nil
 44	}
 45
 46	// 4. 使用 ffmpeg 转换为 AVIF(适配 Arch Linux 的 libsvtav1 编码器)
 47	cmd := exec.Command(
 48		"ffmpeg",
 49		"-i", srcPath,          // 源文件
 50		"-c:v", "libsvtav1",    // Arch Linux 兼容的 AVIF 编码器
 51		"-crf", "30",           // 质量参数(0-63,值越小质量越好)
 52		"-preset", "6",         // 速度预设(0-13,值越小速度越慢/质量越高)
 53		"-g", "240",            // GOP size,适配图片转换
 54		"-pix_fmt", "yuv420p",  // 色彩格式,保证兼容性
 55		"-y",                   // 覆盖已存在的文件(前面已检查,此处为兜底)
 56		avifPath,
 57	)
 58
 59	// 执行转换并捕获输出
 60	output, err := cmd.CombinedOutput()
 61	if err != nil {
 62		return false, fmt.Errorf("转换失败: %v, 输出: %s", err, string(output))
 63	}
 64
 65	fmt.Printf("✅ 转换成功: %s → %s\n", srcPath, avifPath)
 66	return true, nil
 67}
 68
 69// replaceMDLinks 替换 Markdown 文件中的图片链接(支持深层目录)
 70func replaceMDLinks(mdPath, imagesDir string) (bool, error) {
 71	// 读取 Markdown 内容
 72	content, err := os.ReadFile(mdPath)
 73	if err != nil {
 74		return false, fmt.Errorf("读取 MD 文件失败: %v", err)
 75	}
 76	oldContent := string(content)
 77
 78	// 正则匹配 /images/ 下所有层级的图片链接:![xxx](/images/任意路径/filename.ext)
 79	// 关键修改:([^/.]+/)* 匹配任意层级的子目录(如 travel/wugongshan/)
 80	pattern := `(!\[[^\]]*\]\(/images/(([^/.]+/)*)([^/.]+))\.(webp|jpg|jpeg|png)(\))`
 81	re := regexp.MustCompile(pattern)
 82
 83	// 替换为 .avif 链接
 84	newContent := re.ReplaceAllString(oldContent, "${1}.avif${6}")
 85
 86	// 无内容变化
 87	if newContent == oldContent {
 88		return false, nil
 89	}
 90
 91	// 写入替换后的内容
 92	err = os.WriteFile(mdPath, []byte(newContent), 0644)
 93	if err != nil {
 94		return false, fmt.Errorf("写入 MD 文件失败: %v", err)
 95	}
 96
 97	fmt.Printf("✅ MD 链接替换完成: %s\n", mdPath)
 98	return true, nil
 99}
100
101func main() {
102	// 命令行参数
103	imagesDir := flag.String("images", "./images", "图片根目录路径")
104	mdDir := flag.String("md", ".", "Markdown 文件目录路径")
105	flag.Parse()
106
107	// 1. 验证图片目录是否存在
108	if _, err := os.Stat(*imagesDir); os.IsNotExist(err) {
109		fmt.Printf("❌ 图片目录不存在: %s\n", *imagesDir)
110		os.Exit(1)
111	}
112
113	// 2. 递归扫描图片目录所有层级,提取待转换文件(保留相对路径)
114	fmt.Println("===== 开始转换图片为 AVIF =====")
115	type imageItem struct {
116		relDir   string // 相对于 imagesDir 的子目录(如 travel/wugongshan)
117		filename string // 无后缀的文件名(如 IMG_2150)
118	}
119	var imageItems []imageItem
120	// 去重映射:key = relDir/filename,避免重复处理
121	processed := make(map[string]bool)
122
123	err := filepath.Walk(*imagesDir, func(path string, info os.FileInfo, err error) error {
124		if err != nil {
125			return err
126		}
127		// 跳过目录
128		if info.IsDir() {
129			return nil
130		}
131
132		// 过滤非目标格式文件
133		ext := strings.TrimPrefix(filepath.Ext(path), ".")
134		isTargetExt := false
135		for _, e := range srcPriorities {
136			if ext == e {
137				isTargetExt = true
138				break
139			}
140		}
141		if !isTargetExt {
142			return nil
143		}
144
145		// 计算相对路径和文件名
146		relPath, err := filepath.Rel(*imagesDir, path)
147		if err != nil {
148			fmt.Printf("⚠️ 无法计算相对路径: %s, 跳过\n", path)
149			return nil
150		}
151		// 拆分:relPath = relDir/filename.ext → relDir + filename
152		relDir := filepath.Dir(relPath)       // 相对子目录(如 travel/wugongshan)
153		baseName := filepath.Base(relPath)    // 带后缀的文件名(如 IMG_2150.webp)
154		filename := strings.TrimSuffix(baseName, filepath.Ext(baseName)) // 无后缀文件名
155
156		// 去重:避免同一文件(不同后缀)被重复处理
157		key := filepath.Join(relDir, filename)
158		if processed[key] {
159			return nil
160		}
161		processed[key] = true
162
163		imageItems = append(imageItems, imageItem{
164			relDir:   relDir,
165			filename: filename,
166		})
167		return nil
168	})
169
170	if err != nil {
171		fmt.Printf("❌ 扫描图片目录失败: %v\n", err)
172		os.Exit(1)
173	}
174
175	// 批量转换图片(支持深层目录)
176	convertCount := 0
177	for _, item := range imageItems {
178		success, err := convertToAVIF(*imagesDir, item.relDir, item.filename)
179		if success {
180			convertCount++
181		} else {
182			fullPath := filepath.Join(item.relDir, item.filename)
183			fmt.Printf("❌ 跳过 %s: %v\n", fullPath, err)
184		}
185	}
186
187	// 3. 替换 Markdown 中的图片链接(支持深层目录)
188	fmt.Println("\n===== 开始替换 MD 图片链接 =====")
189	mdCount := 0
190	modifiedMdCount := 0
191	err = filepath.Walk(*mdDir, func(path string, info os.FileInfo, err error) error {
192		if err != nil {
193			return err
194		}
195		// 仅处理 .md 文件
196		if !info.IsDir() && filepath.Ext(path) == ".md" {
197			mdCount++
198			modified, err := replaceMDLinks(path, *imagesDir)
199			if err != nil {
200				fmt.Printf("❌ 处理 MD 文件失败 %s: %v\n", path, err)
201			} else if modified {
202				modifiedMdCount++
203			} else {
204				fmt.Printf("ℹ️ 无需要替换的链接,跳过: %s\n", path)
205			}
206		}
207		return nil
208	})
209
210	if err != nil {
211		fmt.Printf("❌ 扫描 MD 目录失败: %v\n", err)
212		os.Exit(1)
213	}
214
215	// 4. 输出汇总信息
216	fmt.Println("\n===== 处理完成汇总 =====")
217	fmt.Printf("📸 图片转换:共找到 %d 个待处理文件,成功转换 %d 个 AVIF 文件\n", len(imageItems), convertCount)
218	fmt.Printf("📝 MD 替换:共扫描 %d 个 MD 文件,成功替换 %d 个文件的链接\n", mdCount, modifiedMdCount)
219}

AI 还挺厉害的,简单交互几次后,就能生成代码。

编译成可执行文件 img-convert-replace

1go build -o img-convert-replace img-convert-replace.go

自定义路径示例(比如图片目录是 /home/you/static/images md 文件目录是 /home/you/mds)

1./img-convert-replace -images /home/you/static/images -md /home/you/mds

友情提示,上述程序不适应于其它场景,每个站点逻辑不一样。比如 B 站这种切换 AVIF,肯定是个大工程。

如果你的需求很简单,那就不用写程序,用命令行处理也挺方便的,额外判断逻辑多就需要写程序处理。

2. 核验

转换完成后,git status、git diff 查看下,看看是否有问题,小心驶得万年船。

hugo 站点都是静态的,万一真的搞崩了,也能 git 回滚。

3. webserver 加上缓存仿盗链等

hugo 静态站点通常配合 Nginx、Caddy 来对外提供服务,可以配置下图片缓存和防盗链。

我用 caddy,diff 示例如下:

 1➜ git diff 7127 203d
 2diff --git a/Caddyfile b/Caddyfile
 3index 4c99e16..674de8b 100644
 4--- a/Caddyfile
 5+++ b/Caddyfile
 6@@ -86,13 +86,13 @@ mephisto.cc {
 7
 8        @static {
 9                file
10-               path *.ico *.css *.js *.gif *.jpg *.jpeg *.png *.svg *.webp *.avif
11+               path *.ico *.css *.js *.gif *.jpg *.jpeg *.png *.svg *.webp
12        }
13        header @static Cache-Control max-age=31536000
14
15        @invalid_referer {
16                file
17-               path *.webp *.png *.avif
18+               path *.webp *.png
19                not header Referer https://mephisto.cc*
20        }

丐版服务器小水管没办法,防君子不防小人。