乐于分享
好东西不私藏

声仓 AudioDock 数据插件使用说明!元数据不求人方案

声仓 AudioDock 数据插件使用说明!元数据不求人方案

前言

AudioDock(声仓)1.2 版本上线了数据插件(也就是后端的 metadata-plugins 系统),让你可以通过挂载外部 HTTP 服务的方式,在导入阶段自动补全任意字段。这篇文章就来讲清楚它是什么、怎么写、怎么配置、怎么用。

正文

一、数据插件是什么

数据插件本质上是一组用户可配置的元数据增强规则,在 AudioDock 服务端扫描文件时按顺序触发,把外部数据源返回的内容合并到入库结果上。它的设计目标有几个:

  • • 零侵入扩展:插件不是写死在服务端的,而是通过一份 JSON 配置管理。新加一个数据源不需要改代码、不需要重启服务。
  • • 插件级隔离:每个插件独立配置超时、重试、优先级、过滤条件,失败只影响自己,不会拖垮整次导入。
  • • 可观测:每次调用都会写入 pluginLog 表,记录调用耗时、状态、错误信息,方便排查。
  • • 链式合并:可以同时挂多个插件,它们会按 priority 从小到大依次跑,前一个插件的输出会作为下一个插件的输入。

目前桌面端、小程序端、mobile 端都已支持数据插件中心页面!

二、HTTP 插件协议(推荐)

当前已完整实现的插件类型是 http——服务端把文件的基础信息以 JSON 形式 POST 给你的 endpoint,由你返回需要补充的字段。executable 和 builtin 类型已在配置里预留接口,会在后续版本补齐。

2.1 请求体(PluginInput)

服务端在每次文件入库前,会发出如下结构:

{  "path": "/data/music/artist/album/01-track.mp3",  "originalPath": "/data/music/artist/album/01-track.mp3",  "fileName": "01-track.mp3",  "relativePath": "artist/album/01-track.mp3",  "fileHash": "md5-or-blake3",  "size": 5242880,  "mtime": "2026-01-15T03:21:09.000Z",  "type": "music",  "metadata": {    "title": "01-track",    "artist": "未知",    "album": "未知",    "albumArtist": "未知",    "duration": 240,    "year": 0,    "trackNo": 1,    "lyrics": "",    "cover": "/cache/cover/xxx.jpg"  },  "folder": { "name": "album", "path": "/data/music/artist/album" }}
  • • type 取值:music / audiobook / mv,由入库阶段决定。
  • • metadata.* 是文件内嵌元数据已经能解析出来的字段,可能为空。
  • • fileHash 会在命中缓存场景下传入,便于插件做幂等。
  • • folder 描述当前文件所在的目录(来自目录入库或 strm 展开后的真实路径)。
2.2 响应体(PluginOutput)

你的服务只要返回一个 JSON 对象,写了哪些字段就补哪些字段,没写的就跳过:

{  "title": "真实歌曲名",  "artist": "真实艺人",  "album": "真实专辑",  "albumArtist": "真实专辑艺人",  "duration": 245,  "year": 2024,  "trackNo": 1,  "lyrics": "[00:00.00] 歌词全文 ...",  "tags": ["Pop", "Ballad"],  "cover": {    "source": "url",    "value": "https://example.com/cover.jpg"  },  "provider": "my-music-brain",  "confidence": 0.92,  "albumDescription": "专辑简介(首次写入生效)",  "albumTags": ["Studio Album", "2024"],  "artistDescription": "艺人简介(首次写入生效)",  "artistTags": ["Male Vocalist"]}

几个关键点:

  • • Track 级别字段(title/artist/album/duration/year/trackNo/lyrics/cover):会按元数据优先级策略合并到入库结果。
  • • cover.source 支持 url(服务端会下载到缓存目录,支持 data:base64 URL)和 local(已是本地路径,直接使用)。
  • • tags:服务端会去重合并,不会覆盖文件已有的标签。
  • • albumDescription / artistDescription:仅在该字段尚未存在时写入,不会覆盖已有内容。
  • • provider / confidence 会写进数据库,方便后期追溯"这条元数据来自哪里"。

三、配置项详解

在插件中心新增一条插件时,可以看到这些字段:

字段
说明
id
插件唯一标识,必须匹配 ^[A-Za-z0-9_-]+$,保存后不可修改
name
展示用名称,可重复
enabled
是否启用,关闭即跳过该插件
priority
数字越小越先执行;多条插件按升序串联
type
当前支持 http;executable / builtin 预留中
endpoint
HTTP 插件的 URL,必填
timeout
单次调用超时时间(毫秒),默认 30000,最小 1000
retry
失败重试次数,默认 0
filter.types
限定生效的资源类型,可多选 music / audiobook / mv
filter.pathPattern
可选正则,仅匹配 path 或 originalPath 的文件会进入插件

典型的过滤组合示例:

  • • 只对音乐生效:filter.types = ["music"]
  • • 只对有声书生效:filter.types = ["audiobook"]
  • • 只对 无损/ 目录下的文件生效:filter.pathPattern = "^/data/music/无损/"

四、元数据优先级策略(新)

v1.2 系列引入了一个全局开关 metadataPriority,用来解决"插件返回的元数据和文件内嵌元数据打架"的问题:

  • • plugin(默认):插件返回的字段会覆盖文件内嵌字段。适合"我对外部数据源更信任"的场景,比如从正规平台拉来的元数据。
  • • embedded:保留文件内嵌字段,插件只补"缺失/未知"的字段。判定规则是把空字符串、未知、unknown、year <= 0、duration <= 0 这些都视为缺失。适合"我的本地标签是手工整理过的,不想被插件覆盖"。

无论哪种模式:

  • • tags 始终按大小写不敏感去重合并,不会清空已有标签。
  • • albumDescription / artistDescription 始终是"先到先得",写过一次就不再覆盖。

这个开关在插件中心页面顶部可以直接切换,会立即落盘并热更新。

五、一个最简 HTTP 插件示例

假设你用 Node.js 写一个最小可用的插件,监听 :18081/scrape,规则是"只要 filename 命中 rick-(\\d+).mp3,就把 title 改成 Never Gonna Give You Up Part $1":

// plugin-server.jsimport http from 'node:http';http.createServer(async (req, res) => {  if (req.method !== 'POST' || req.url !== '/scrape') {    res.writeHead(404).end();    return;  }  const chunks = [];  for await (const c of req) chunks.push(c);  const input = JSON.parse(Buffer.concat(chunks).toString('utf8'));  const m = (input.fileName || '').match(/^rick-(\d+)\.mp3$/i);  if (!m) { res.writeHead(200).end('{}'); return; }  res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify({    title: `Never Gonna Give You Up Part ${m[1]}`,    artist: 'Rick Astley',    album: 'Whenever You Need Somebody',    year: 1987,    provider: 'demo-plugin',    confidence: 1,  }));}).listen(18081);

在 AudioDock 插件中心新增:

字段
值
id
demo_plugin
name
本地 Demo 插件
类型
http
接口地址
http://localhost:18081/scrape
优先级
0
超时时间
5000
失败重试
0
生效资源类型
music

保存后,把名为 rick-01.mp3 的文件丢进扫描目录,重新入库就能在任务中心看到标题被改成 Never Gonna Give You Up Part 1,数据库里的 metadataSource 也会标记为 PLUGIN,metadataProvider 标记为 demo_plugin。

六、常见问题

Q1:插件调用出错会不会影响导入?不会。单个插件失败会被记入 pluginLog 表(status = error / timeout),文件依然按其它插件和原始元数据继续入库。

Q2:能挂多少个插件?没有硬性上限,但每个插件的 HTTP 调用是顺序执行的。建议把"通用补全"插件放在 priority 较小位置,把"专精某类资源"的插件放后面串联。

Q3:cover 想直接返回 base64 行不行?可以。cover.source = "url" 时,value 可以是 data:image/png;base64,XXX,服务端会自动解码并写入缓存。

Q4:能改已入库文件的元数据吗?本版本的数据插件只在导入阶段触发。对于已入库的曲目,请在曲目详情里手工调整,或删除后重新入库(导入任务中心有"重新扫描"入口)。

Q5:能监听 WebDAV / Strm 这类远程源吗?可以。数据插件对所有入库通道一视同仁——本地目录、WebDAV、Strm 展开后的 originalPath 都会传入 PluginInput。

最后

数据插件是 AudioDock 在"可扩展性"方向迈出的又一步。它的设计哲学和 UI 主题插件一致:用一份标准化的接口,把能力开放给社区作者,而不是把每一种数据源都写死在服务端。无论是接豆瓣、接 MusicBrainz、接自建刮削库,还是接企业内部的知识库,只要遵循 HTTP 协议,就能挂到 AudioDock 上。

后续我们会把 executable 类型的插件(本地命令行)补齐,让"不方便暴露 HTTP 的本地脚本"也能成为数据源;也会在插件中心加上"调试调用"按钮,方便在不重启导入任务的情况下单点验证插件逻辑。

今天的分享就这些了,感谢大家的阅读,如果文章中存在错误的地方欢迎指正!

往期精彩推荐

相关学习资料