robb3n's blog 春耕雨读

入库自动化:一首歌进曲库要过的几道闸

11 分钟约 3,000 字
个人曲库系统 · 整理第 4 篇 · 共 4 篇目录
  1. 01从文件夹到曲库:音乐文件标签最佳实践2026-10-17
  2. 04入库自动化:一首歌进曲库要过的几道闸正在读
  3. 05流派树是怎么长出来的待发布
  4. 06给五千首歌补标签待发布
专题页 →

《从文件夹到曲库》讲的是一首歌入库后该长什么样。这一篇讲怎么让每一首新歌都稳定地长成那样。

现在我入库只说一句「入库:<歌名>」,剩下的由 Agent 跑完:认歌、查重、取音频、过闸门、写标签、配封面和歌词、算响度、上传。我只在几个分叉上拍板。曲库快五千首了,靠手工一首首做,步骤会漏,口径也会慢慢漂移。

要点
  1. 流程的次序本身就是规则:先落位、先查重,最后才算响度
  2. 每道检查只有三种结果:通过、失败、待人定
  3. 工具会静默出错,完成看回读,不看退出码
  4. 人只拍板分叉,拍过的板写进文件,不靠记忆
  5. 已经入库的文件不顺手改
范围

音频从哪来不在本文范围,下面从「已经确定要哪首歌」写起。工具链以我自己的为例,换成别的工具,道理一样。

拓扑

指挥机   → 发指令、做确认
工作机   → 曲库母本;下载、打标签、算响度都在这里
网盘     → 母本的副本
服务器   → Navidrome 挂载网盘,凌晨自动扫描新增

工具分工:

工具 负责
tagify 我自用的标签工具:写元数据、按流派词表归一化、自动补齐祖先标签1
metaflac 补写 tagify 管不到的字段(语种、作品来源)
rsgain 算响度,写 ReplayGain
ffmpeg / ffprobe 核验音频:解码、位深、时长、频谱

次序本身就是规则

步骤 为什么排在这里
1. 认歌 先确定是哪一首、哪个版本。有精确 id(收藏、歌单、链接)就直接用,不再按名字搜一遍
2. 落位 目录决定文件名,也决定按专辑还是散曲写标签,所以在下载之前
3. 查重 在下载之前。曲库按批次分目录,同一首歌早年进了一个目录、这次落到另一个,「目标已存在」拦不住
4. 取音频 —
5. 闸门 在上传之前。传完再发现是错的,上行就白烧了
6. 写标签 身份字段、流派、语种、作品来源
7. 封面和歌词 封面按专辑取一次,歌词逐首取
8. 响度 最后算。前面的标签清洗可能把它清掉
9. 归位、上传 复制,不移动;上传后对数量

查重最容易做错的是判据。重复看的是录音,不是曲名:同名翻唱、remix、不同录音都会撞名。命中之后比艺人和时长:

  • 同一艺人、时长差 ≤2 秒:同一录音,跳过。专辑名不一样也跳过,合辑、再版里收的常常就是同一个录音
  • 差 2–5 秒,专辑也不同:单曲版和专辑版、重制版,交给我定
  • 差 >5 秒:另一个录音,照常入库
查重前先归一化

繁简、全半角、大小写、标点都统一了再比。库里那首是《生物股长 - ブルーバード (青鸟)》,只按一种写法查就会漏。

闸门:通过、失败、待人定

每道检查只有三种结果。证据不够下结论的,一律是「待人定」,交给我,不让 Agent 自己发明豁免。前提不成立的(比如来源没给文件大小)记「不适用」,不能混成「通过」。

检查 拦的是
完整解码一遍,比对 FLAC 里存的 MD5 坏文件
codec、位深、采样率 请求无损却静默降级成了 mp3
大小 ≈ 来源给的大小 下载被截断
时长 ≈ 平台给的参考时长,差 ≤3 秒 下错了版本、挂错了歌
频谱 有损转码再包成 FLAC

两条容易忽略的纪律:

  • 不能拿被检查的文件给自己作证。拿它自己的时长当参考时长,每首都「误差 0 秒」,这道闸就形同虚设
  • 频谱只是证据,不是结论。128k 的 MP3 在 16kHz 处截断,320k 的在 19–20kHz,可有些母带本身就贴着上限陡降,和 320k 的转码很难分开。阈值要拿自己库里来源可靠的文件当基线校准;离阈值近的写成「证据弱」,交给人判

「待人定」不等于每次都问。某一类我给过统一规则(比如「疑似转码的一律不收」),就写进规则里,以后照规则办,台账里写明按的是哪条。

静默失败:退出码 0 不代表成功

整条流水线里最危险的不是报错,是不报错。下面这些都是退出码 0、看起来一切正常:

症状 怎么发生的 怎么防
中文歌词变成一串 # metaflac 在 C locale 下写 CJK 字符;非交互 ssh 的 locale 默认就是 C 整段脚本 export LANG=C.UTF-8;回读核对,不数行数——行数一行不差
同一个字段出现两条 metaflac --set-tag 是追加,不是替换 先 --remove-tag 再写
Ke$ha 写成了 Ke $ 被 shell 展开;裸 & 会把命令切成后台任务加半个名字 值和路径都不拼进命令字符串,走 JSON 或参数数组
三位艺人变成一个 A;B;C ffmpeg 重编码 FLAC 时把多值字段合并成一个串,还丢掉封面 重编码后用标签库重写,再回读
没有响度标签 rsgain custom 默认只扫描,要加 -s i 才写;不带 -a 时还会删掉已有的专辑增益 写完回读增益字段
云盘上少了两首 网盘 CLI 目录级上传,回显「上传失败:」后面是空的 逐个文件传、逐个确认,传完对文件数
多出一首 xxx(1).flac 同名文件被静默改名上传,扫成重复曲目 上传固定带覆盖参数
一首歌在库里「消失」 文件名里有 ;,WebDAV 网关取内容时在这里截断:列得出来、读不出来 文件名避开 ; ? # %

歌词也有自己的一类。网易云的制作人员行要删,但它不一定是 作词 : XXX 的样子:全角冒号、不带空格、英文末尾的 Produced by …、开头的 《曲名》- 艺人 横幅、歌词制作者留下的 By 某人 日期,都出现过。所以按「这一行是不是在唱的内容」判,不按某个冒号的写法判。删完还要核一下时间轴:末尾时间戳该落在音频时长的 85%–100%,而且要用最后一行正文算——末尾的署名行会把比例撑高,掩盖歌词挂错了版本。

验到文件层还不够

有一次我说「歌词还是不对」,文件、网盘、挂载点、Navidrome 数据库四层都已经是对的——是播放器自己缓存了歌词。改完歌词复查,先清客户端缓存。

人在哪里拍板

Agent 做重复劳动,判断留给我。停下来问我的只有这几种:

  • 版本有歧义:同名的现场版、Remix、扩展版,或者平台上只有 Expanded Edition
  • 要为某位艺人新开目录(这是策展判断)
  • 两个来源的流派冲突,或者落不进流派树
  • 疑似重复里的中间那一档
  • 闸门给出「待人定」,又没有现成的规则

拍过的板写进一份决策文件:每项写「是什么、选了什么、为什么、哪天定的」,脚本和 Agent 都从那里读,不凭记忆填。比如粤语要不要从 cmn 里分出来,这种口径以后可能翻转,写死在流程里,翻转时就会写错。

已经入库的文件不顺手改

Navidrome 用曲名、音轨号、碟号、专辑、专辑艺人、年份这些字段算一首歌、一张专辑的身份。改了它们,播放次数、收藏、评分、歌单都会断开。《从文件夹到曲库》末尾提过这一点,落到流水线上就是一条硬规矩:新文件入库之前把字段定好,存量文件一个不碰,哪怕它不符合新口径。往已有专辑里补一首,算专辑增益要用到已有的几首,也是拷贝一份去算,不动原文件——别用硬链接,rsgain 原地改文件,会改到母本。

把流水线交给别人

有朋友想搭类似的东西。我的 skill 绑着我的三台机器和笔记,直接给没用,于是我把它提炼成一份提示词:不让对方照搬我的方案,而是让对方的 Agent 先摸清环境,在关键分叉上问它的用户,再一步步搭出他自己的流水线。

为了检验它,我让三个模型在沙盒里各跑了五轮:拿一份假曲库,埋进重复、假无损、截断、错封面、Ke$ha 这类陷阱,用一份写好的「用户回答」代替真人。几条收获:

  • 写成原则,不写成命令。最早的版本把坑写成「metaflac 会怎样」,碰到没装 metaflac 的机器就失灵。改成「写进去的非 ASCII 字符要回读核对」,再把 metaflac 作为一种症状,换什么工具都适用
  • 规则之间的矛盾会被字面执行放大。「音轨号要连续」和「缺的轨如实留空」各自都对,放在一起,最较真的那个模型一首都不入库
  • 每一轮都还能找到新问题,但越来越边缘。前两轮找的是会让整批卡死的矛盾,第五轮找的是「横幅里 & 和 and 要算一回事」。真用的时候旁边有人,这类细节一句话就定了,测到这里就够了

一套流水线有没有价值,最后还是看它能不能让我更省心地听到想听的歌:它负责不出错,我负责做判断。


  1. tagify 是我给自己写的工具,没有公开。想搭类似的流程,MusicBrainz Picard 或 beets 写标签,再配 rsgain 算响度,能覆盖大部分环节。 ↩︎