Skip to content

fix: 修复曲目排序链路(标签解析 / 查询排序 / 列表排序) - #452

Open
Jeasin wants to merge 3 commits into
SPlayer-Dev:devfrom
Jeasin:fix/album-track-order
Open

Jeasin wants to merge 3 commits into
SPlayer-Dev:devfrom
Jeasin:fix/album-track-order

Conversation

@Jeasin

@Jeasin Jeasin commented Nov 7, 2026

Copy link
Copy Markdown

改动类型

  • 缺陷修复(fix)

是否包含破坏性变更

  • 否

改动说明

Closes #451。

Issue 里「音轨识别错误」和「专辑排序是乱的」看着像两件事,实际是同一条排序链路上的三个断点。
任何一处断开,用户看到的都是「顺序不对」,所以一起修:

# 层级 断点 现象
1 标签解析 "1/17".parse::<u16>() 失败 音轨号落库为 NULL
2 查询 getAlbumTracks 没有 ORDER BY 专辑内顺序 = 并行扫描的完成顺序
3 列表排序 排序键相同后按标题兜底 点「按曲目编号」等于按标题

1. 音轨号恒为 None

metadata::extract_tags 直接对 ffmpeg 输出的 track 值做整串解析:

let track = dict_get(dict, "track").and_then(|s| s.parse().ok());

MP4/M4A 的 trkn(以及 MP3 的 TRCK)允许写成「序号/总数」,ffmpeg 会把整串原样输出。
用 issue 附件里那首实测:

$ ffprobe -v quiet -show_entries format_tags -of json "01 Come Together (2019 Mix).m4a"
{ "format": { "tags": { ..., "track": "1/17" } } }

"1/17".parse::<u16>() 必然失败,于是 track 落库为 NULL。改为只取斜杠前的序号:

fn parse_track_number(raw: &str) -> Option<u16> {
    raw.split('/')
        .next()
        .and_then(|head| head.trim().parse().ok())
}

补了三个用例:1/17、7 / 12(分隔符两侧空白)、"" / "/17" / "A/17" / "-1"(非法值应丢弃)。

顺带说明一个容易误判的点:标签编辑面板用的 readTrackTags 走的是 lofty 的 tag.track(),
一直是对的;只有扫描器用的 extract_tags(dict) 会踩到 "1/17"。
所以「标签面板能看见序号、但排序不生效」并不矛盾——那是两套实现。

2. 专辑曲目没有 ORDER BY

getAlbumTracks 只有一句 SELECT ... WHERE album = ?。曲目按并行扫描的完成顺序入库,
不写排序时取出来的就是这个顺序,既与音轨号无关,也会随扫描时机变化:

ORDER BY track IS NULL, track, path

缺音轨号的排末尾,同号时以路径兜底(多碟共用语序时也能稳定区分)。

3. 「按曲目编号」退化为「按标题」

SongList 的排序比较器把音轨号写成 (a.track ?? 0) - (b.track ?? 0):没有音轨号(或同号)时
所有键都相等,落到末尾的 textCollator.compare(a.title, b.title),结果自然与「按标题」一致。

改成:缺音轨号的恒排末尾(不随排序方向翻到最前),同号时按路径兜底——文件名前缀本就带曲序。

if (field === "track") {
  const left = a.track ?? null;
  const right = b.track ?? null;
  if (left === null) return right === null ? compareByPath(a, b) : 1;
  if (right === null) return -1;
  const byTrack = (left - right) * direction;
  return byTrack !== 0 ? byTrack : compareByPath(a, b);
}

其它排序字段的比较逻辑与兜底未改动。

关联 Issue

Closes #451

第 3 项没有对应 issue(在 #451 的复现过程中顺带发现,官方版同样存在),
如果维护者更希望拆分,它可以独立成 PR——不依赖前两个提交。

测试情况

本地 macOS arm64 / Node 24.22.2 / pnpm 12.15.0 / Rust 1.99.1:

  • cargo test -p audio-engine:121 passed / 0 failed / 3 ignored(含新增 3 个用例)
  • cargo fmt --check -p audio-engine、cargo clippy -p audio-engine --all-targets:通过(workspace 的 clippy 是 deny-all)
  • pnpm typecheck(tsc + vue-tsc)、pnpm lint、pnpm exec prettier --check .:通过
  • pnpm test:web 582 passed(72 files)、node 50 passed
  • 未改动 native/*/index.d.ts(napi 导出签名不变)

扫描器 A/B(两个真实编译产物,不是复刻逻辑)

用已发布版本与本地构建的 audio-engine.node 分别 scanDirs() 同一批文件:

文件 标签 旧版 本 PR
01 Come Together (2019 Mix).m4a 1/17 undefined 1
17 Her Majesty (2019 Mix).m4a 17/17 undefined 17
02 Throwaway.m4a 2 2 2

真实曲库(2332 首)

track IS NULL 共 557 首(m4a 515 / mp3 42),抽查确认差异只来自标签写法
(02 Throwaway.m4a 是 trkn = 2,01 Come Together (2019 Mix).m4a 是 trkn = 1/17)。

这两半都能独立生效:即使不重扫、track 仍为 NULL,path 兜底也能恢复正确顺序——

修复前:14, 08, 13, 15, 16, 04, 09, 03, 12, 17, 10, 06, 07, 02, 11, 01, 05
修复后:01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, 16, 17

打包验证

electron-builder --dir 出包后在实机上确认:专辑页默认顺序即音轨顺序(修复前是扫描完成顺序),
曲目编号排序与默认顺序一致。

已知影响范围

  • 已有曲库不会自动重算音轨号:增量扫描按 mtime + size 跳过未变化的文件,旧记录的
    track 仍是 NULL,需要「音乐库 → 更多 → 全量扫描」跑一次。上面的 path 兜底能覆盖
    大部分场景;要让老用户完全无感升级,需要再加一个「标签解析版本号」来失效旧缓存,
    改动面更大,没有夹带在这个 PR 里。
  • 多碟专辑仍会交错:disc 在前端类型里有,但扫描入库时并未写入,所以现在能排出的最好结果是
    1,1,2,2,3,3。碟号入库可以作为后续改进,与本 PR 无关。

自查清单

  • 本 PR 围绕同一个用户可见问题(曲目顺序不对),未夹带无关改动
  • 已在本地完整测试:cargo test / fmt / clippy / pnpm typecheck / lint / test / prettier --check 全绿,并出包实机核对排序
  • 改动涉及原生模块,已 pnpm build:native 并用新旧 audio-engine.node 做 A/B 验证;未手写 native/*/index.d.ts
  • 已向 dev 分支提交

MP4/M4A 的 trkn 与 MP3 的 TRCK 常写成 1/17,ffmpeg 会把整串原样输出,
直接 parse::<u16>() 失败使音轨号恒为 None:专辑内无法按音轨排序,
列表也会退化成按标题排序。

改为只取斜杠前的序号,并补充斜杠、空白与非法取值的用例。
曲目按并行扫描的完成顺序入库,getAlbumTracks 未写 ORDER BY,
专辑内顺序因而与音轨号无关且每次扫描都可能不同。
补上 ORDER BY track IS NULL, track, path:缺少音轨号的排在末尾,
同号时以路径兜底。
compare 里用 (track ?? 0) 相减,没有音轨号的曲目全部等于 0,
排序键相同后落到标题兜底,于是「按曲目编号」的结果与「按标题」一致,
看起来像该选项没生效。

改为:缺失音轨号的曲目恒排末尾(不随排序方向翻到最前),
音轨号相同时(多碟专辑的 1/1、2/2 就是这种情况)按文件路径兜底,
文件名前缀本就带曲序,比标题更接近预期。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

无法按音轨排序或音轨识别错误

1 participant