前言
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 | |
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 插件中心新增:
demo_plugin | |
本地 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 的本地脚本"也能成为数据源;也会在插件中心加上"调试调用"按钮,方便在不重启导入任务的情况下单点验证插件逻辑。
今天的分享就这些了,感谢大家的阅读,如果文章中存在错误的地方欢迎指正!
往期精彩推荐
• 声仓 1.2 版本 UI 主题插件使用说明!开启你的个性主题吧! • 声仓 Beta 版本更新计划说明! • 声仓即将发布 1.2 版本!数据插件上线;桌面端安装包体积减小 90%! • 更多精彩文章欢迎关注我的公众号
夜雨聆风