Files
SpineWallpaper/.scratch/wallpaper-optimization/issues/02-audio-and-image-size.md
T
Shuery 0ca78ed117 feat: 音频改回无损 FLAC(用户决定:壁纸在本地,不计体积)
撤销 issue 02 里的音频转码,把两条 FLAC 放回 dist。

- 恢复方式是**字节级还原**而非重新编码:masters/ 里两份文件的 git 对象哈希与基线
  `d8c7e49:dist/assets/audios/*.flac` 完全一致,即与已发布的 v2 是同一份文件。
  因此本次提交不新增任何 blob 存储——这两份内容早已在仓库历史里。
- `preset.js` 的音源指向改回 `.flac`。`pv37.mp3` 保持原样:它**没有无损源**,
  把有损数据转封装成 FLAC 只会涨体积,而一个 bit 的信息都不会回来。
- 体积基准由「≤35 MB」改为「不超过已发布的 v2(104.98 MB)」,实际 **96.94 MB**,
  比 v2 还小(图片部分的无损 WebP 收益仍在),不引入新的上传或订阅风险。

顺带修掉一个自己引入的 bug:改 `preset.js` 时漏写了文件名里的 `「」`,
浏览器请求 404 → `<audio>` 在 **64 ms** 内报 `MediaError.code = 4`(SRC_NOT_SUPPORTED),
看上去像"CEF 不支持 FLAC",实际是路径不存在。判定方法记进 ADR 0004:
把同一份字节交给 `decodeAudioData`,能解出正确时长就说明解码器没问题,问题在传输或路径。

验证:
- `test-props`:CEF 支持 audio/flac、无解码错误、readyState=4、时长 205.1 s,14 项音源断言全过。
- `test-acceptance` 新增第 ⑤ 节音频格式不变式:xilian 两个音源全为 flac、无 `.ogg`/`.opus`
  残留、`preset.js` 引用的每个音源文件都存在;三种比例 0 报错;预设往返不丢音频设置。
- 分带能量实测:FLAC 高频平滑延伸到 22 kHz(20–22 kHz 处 −50 dB),无有损断崖;
  与 Opus 版逐带相差 0.0–0.3 dB。即这是一个由偏好驱动、而非由测量差异驱动的决定。
2026-09-19 20:36:58 +08:00

85 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 02 体积:音频转码 + 未引用资源归位
Status: resolved,但**音频转码已被推翻**(见文末「修订」);图片部分保持有效
## 现状
- `dist` 合计 105.1 MB,其中音频占 77.3 MB:
- `昔涟立绘/audios/「再度和你」.flac` 37.3 MB / 205.1 s / 1.52 Mbps
- `昔涟立绘/audios/昔涟.flac` 37.7 MB / 186.7 s / 1.69 Mbps(此前零引用,现已声明为第二音源)
- `「成为昨日的明天」/audios/pv37.mp3` 2.3 MB / 81 s
- CEF 的解码白名单含 vorbis / libopus / flac / mp3 / pcm_*(不含 aac,`m4a` 大概率不可用)。
## 步骤
1. 无损母带移出 `dist/`,存到 `masters/`(已 gitignore,不入库)。
2. 两首 flac 转 Opus(`libopus` 128k VBR)。保留 `pv37.mp3` 原样。
3. 核对转码未截断、无解码错误。
4. 图片转码**仅在对拍证明无可见差异后**决定(atlas 页要整组转,并同步改 `.atlas` 里的页名)。
## 验收
- `dist` 总体积 ≤35 MB。
- `check-paths` 全绿;转码后的音频可完整解码。
- 每个比例的对拍像素无回归。
## 结果
`dist` 105.1 MB → **28.92 MB**。
- 音频:`再度和你.ogg` 3.35 MB、`昔涟.ogg` 3.16 MB,均通过完整解码与电平检查。
`昔涟.flac` 源文件末尾一帧损坏(186.671 s 处 invalid frame header),但全长音频都在。
- 图片:**全部走无损 WebP**(放弃有损路线,因为目标已达成,没必要冒险)。
- xilian 三张真 PNG RGBA → 无损 WebP:3.39→1.80、5.01→2.83、3.38→1.76 MB
- `kv37_xilian.png` → 无损 WebP 5.13→2.75 MB
- kv37 的五张图**本来就是 WebP,只是扩展名写成 `.png`**,本次只改名——这也顺带证明 WebP 在本机 WE 环境可用(线上版本一直在用)
- atlas 页名同步改为 `.webp`(`tools/atlas-pages.mjs`;注意这两个 .atlas 是 CRLF 行尾)
- 删除死文件 `dist/jsconfig.json`;`README.txt` 是 Workshop 描述(BBcode),保留。
## 验证
- `check-paths`:11/11 声明路径存在。
- 浏览器:0 个非 favicon 的 404。
- 无损 WebP 与源 PNG 的可见像素 **0 差异**。libwebp 会改写完全透明像素下的 RGB
(`tools/rawdiff.mjs` 证实 xilian 三张图有 32–49% 像素属此类),而本项目贴图是 Linear 过滤、
透明像素 RGB 会渗进边缘,所以额外做了渲染级对拍:与 PNG 版同冻结设置相比
**0.052/255、热力图全空**,确认无渗色。
- 全尺寸矩阵留档于 `tools/shots/step02/`。
## 教训
**先验证再删除**。中途 `-preset default` 让无损编码退化成有损(产物只剩 0.03–0.54 MB),
而源 PNG 当时已被删除;只因为源都在 git 里(`09ff322`)才得以还原。
另外 `-quality` 在这个 ffmpeg 的 libwebp 封装里被忽略,要用 `-q:v`;`-exact` 选项不存在。
## 修订:音频改回无损(用户决定)
用户要求「音频可使用无损格式,这个壁纸本身就在本地,无需考虑大小」。
于是**撤销本文第 2 步的音频转码**,把两条 FLAC 放回 `dist`。
- 恢复方式是**字节级还原**,不是重新编码:`masters/` 里那两条 FLAC 的 git 对象哈希
与基线 `d8c7e49:dist/assets/audios/*.flac` **完全一致**,即与已发布的 v2 装的是同一份文件。
- `preset.js` 的音源指向随之改回 `.flac`;`pv37.mp3` **没有无损源**,保持原样(也一直是 mp3)。
- 体积基准由「≤35 MB」改为「不超过已发布的 v2(104.98 MB)」。实际 **96.94 MB**——
比 v2 还小(图片那部分的无损 WebP 收益仍在),所以**不引入新的上传/订阅风险**。
- 图片部分**不受影响**:无损 WebP 本来就不牺牲画质,没有取舍可撤。
### 关于"转成无损"的边界
`pv37.mp3` 不能靠转封装变成无损:把有损数据装进 FLAC 只会让 2.3 MB 涨到十几 MB,
而一个 bit 的信息都不会回来。要它无损,只能换一个无损源文件。
### 踩到的坑:文件名里的「」
`「再度和你」.flac` 的**文件名带 `「」`**(v2 就是如此)。改完 `preset.js` 后我漏写了这两个字符,
浏览器请求 `再度和你.flac` → 服务端 404 → `<audio>` 在 **64 ms** 内直接报
`MediaError.code = 4`(SRC_NOT_SUPPORTED)。这个错误码极具误导性:看起来像"CEF 不支持 FLAC",
实际是路径不存在。排查时靠三条证据把方向掰了回来:
1. `decodeAudioData` 直接解同一份字节 → 成功(205.056 s / 48 kHz / 2 声道),证明解码器没问题;
2. `<audio>` 指向正确 URL 与 blob URL → 都正常出 `loadedmetadata`;
3. 页面自身元素的时间线里有一条 **404**,且 `currentSrc` 结尾是 `再度和你.flac`——少了 `「」`。
结论:**`MediaError.code=4` 要先查 404,再怀疑格式支持。** 收尾时已在 `test-acceptance.mjs`
第 ⑤ 节加了不变式——`preset.js` 引用的每个音源必须真实存在——防止再犯。