首页
/ copyparty bin/mtag 详解:用独立脚本扩展媒体标签(BPM、调性、哈希、EXIF 剥离)的 MTP 插件机制

copyparty bin/mtag 详解:用独立脚本扩展媒体标签(BPM、调性、哈希、EXIF 剥离)的 MTP 插件机制

2026-09-05 18:10:45作者:魏献源Searcher

bin/mtag/ 目录下的程序是 copyparty 媒体索引系统(up2k/mtag)的自定义标签插件集合:每个脚本以"接收一个文件绝对路径、向 stdout 输出标签值"的极简契约运行,通过 -mtp 选项挂接后,copyparty 会在索引时调用它们,把 BPM、音乐调性(Camelot)、音视频流哈希、PE 可执行文件信息等结果写进元数据库。读完后你将掌握如何把第三方检测工具接入 copyparty 的标签流水线、如何用 volflags 限定插件作用域,以及首次启用 mtp 时必须执行的清库重扫步骤。

机制前提:MTP 插件是什么

copyparty 的媒体索引有两类"解析器":内置的后端(Mutagen 或 FFprobe,见 mtag 后端选择逻辑),以及用户自供的外部程序-mtp(Media Tag Parser)选项注册的正是后者。

MParser 的解析逻辑看,一条 -mtp key=f,audio-key.py 的声明会被拆解为:

  • = 号左侧是标签名(可用逗号声明多个标签,此时脚本必须输出 JSON,见下文);
  • = 号右侧逐项解析修饰符,其中:
    • f:force,检测到的值覆盖已有标签值;
    • t<n>:超时秒数(默认 60);
    • e<ext>:只对指定扩展名的文件运行;
    • a[y|n|r|d]:音频要求——y 仅音频、n 仅非音频、r 要求、d 不关心;
    • c0-3:捕获 stdout/stderr 的数量;
    • k[t|m|n]:超时时杀主进程/进程树/不杀;
    • p<n>:优先级,值越大越晚执行(高优先级插件还能通过 stdin 拿到前序插件的 JSON 结果);
    • 最后剩余部分即脚本路径。

执行发生在 MTag.get_bin:脚本以 [python3, 脚本路径, 文件绝对路径] 方式调用(bin.py 结尾时自动补上 Python 解释器),stdout 去除首尾空白后写入标签;若标签名含逗号,则对 stdout 做 json.loads 并按标签名取字段——这就是 cksum.pyexe.py 输出 JSON 的原因,一个解析器可以为多个标签同时供值

批量扫描的调度在 up2k._run_all_mtp / _run_one_mtp:启动扫描时按卷(volume)逐一执行 mtp,并在 mt 表里用 t:mtp 标记记录进度(mtpq 计数)。一个关键行为在源码中有据可查:若某文件在 mt 表中已有任何标签,该文件的 mtp 会被跳过——这正是 README 强调"首次带新 mtp 选项启动时必须先用 -e2tsr 清掉标签"的原因。

功能前提:-e2ts 与后端依赖

bin/mtag/README.md 明确指出:这些插件要工作,copyparty 的 -e2ts(扫描已有文件的标签)必须可用,也就是至少安装 apt install ffmpegpip3 install mutagen 之一。对照 cfg.py 中的 e2 系列选项定义

选项 含义
e2d 启用数据库;文件可搜索 + 上传可撤销
e2ds 启动时扫描可写目录中的新文件(隐含 -e2d
e2dsa 启动时扫描所有目录(隐含 -e2d
e2ts 启动时扫描已有文件的标签(隐含 -e2t
e2tsr 删除数据库中全部元数据(完整重扫,隐含 -e2ts

README 同时提醒:如果你的目标只是"上传后跑个脚本",可以完全绕开这套复杂机制,改用更简单的 event hooks(不需要 -e2ts 也不需要 ffmpeg)。

插件清单:按许可证与实现方式分类

README 把插件按"依赖的许可证兼容性"分成三档,这一分法对在生产环境选用插件时判断法律风险很实用。

引入 GPL 代码的插件(import GPL)

  • audio-bpm.py:检测音乐 BPM。实现流程为:先调 ffmpeg 把音频重采样为 22050 Hz 单声道 f32le 裸数据(只取前 360 秒)写入临时文件,再用 vamp.collect(d, 22050, "beatroot-vamp:beatroot")(BeatRoot Vamp 插件,GPL2)拿 beat 时间戳,然后去掉前 20%、保留到 75% 的间隔序列,用 60 * (len(bds) / sum(bds)) 估算 BPM 并打印两位小数;若 BeatRoot 失败则降级到 vamp-example-plugins:fixedtempo。代码注释里标注了主路径约 98%、降级路径约 73% 的测试准确率(针对 jcore 曲库)。
  • audio-key.py:检测音乐调性。同样先用 ffmpeg 截取前 300 秒转 s16 格式,优先用 PyPI 的 keyfinder(Mixxx 的 libkeyfinder 分支,GPL3)输出 Camelot 表示(如 8B),否则回退调用 keyfinder-cli -n camelot

调用 GPL 外部程序的插件(法律上对多数用途无碍)

  • media-hash.py:生成音视频流内容的校验和。原理是 ffmpeg -f framemd5 - 逐帧计算 md5,再按流序号对帧哈希做 SHA-512,取前 12 字节转 URL-safe base64,输出 {"ahash": ..., "vhash": ...}。它的价值在于内容级指纹:文件改名、改封装、改文件名不影响流哈希,可配合 up2k 的 dedup 思路做重复媒体识别。

  • image-noexif.py:上传照片时剥离 EXIF/IPTC/XMP 元数据。它调用 exiftool -exif:all= -iptc:all= -xmp:all= -P -o noexif/,把去元数据副本写入 noexif/ 子目录,再与原文件逐字节比较,输出 clean(原本无元数据)、exif(已剥离)或 failed。README 中解释过为何写子目录而不是原地修改:原地改动会导致 up2k.db 里保留旧哈希直到下次重扫,重复上传会被改名保留为副本;换成"原图备份到 exif/ 子目录 + 原地修改"的方案则会导致重扫前 db 与新文件不同步、后续相同图片被 dedup 链接到修改后的副本。该脚本自带一条完整示例配置:

    -v/mnt/nas/pics:pics:rwmd,ed:c,e2ts,mte=+noexif:c,mtp=noexif=ejpg,ejpeg,ad,bin/mtag/image-noexif.py
    

    其中 mte=+noexif:cnoexif 追加为已知标签,ejpg,ejpeg 限定扩展名,ad 表示"不管 FFmpeg 是否认为它是音频都解析"。

无任何许可证问题的插件

  • cksum.py:计算多种校验和,配置串 crc32 md5 md5b sha1 sha1b sha256 sha256b sha512/240 sha512b/240b 后缀表示 base64 编码、/n 表示截断到 n 位,输出 JSON。文件自身的 docstring 给出了用法示例:-mtp crc32,md5,sha1,sha256b=ad,bin/mtag/cksum.py(多标签 + ad 不关心音频类型)。
  • exe.py:抓取 .exe/.dll 的 PE 信息,是"单解析器多标签"的官方示例。用 pefile 解析(自定义 PE2 子类 关掉了一堆昂贵的目录解析以提速),输出 arch(x86/x64)、built(编译时间戳)、ui(GUI/cmdline)、cksum(可选头校验和),若存在版本资源还会追加 ver(FileVersion/ProductVersion)和 orig(OriginalFilename/InternalName)。
  • wget.py:把 copyparty 变成文件下载器——向网页的 message/pager 表单 POST 一个 URL,即以 put-xxx.bin(内容为 urlencoded 的 URL,msg= 前缀)落盘,mtp 触发后脚本校验协议(仅 http/https/ftp/ftps)、wget --trust-server-names 下载,期间留下 -- DOWNLOADING name 占位文件,成功后删除 .bin、失败留下 -- FAILED TO DOWNLOAD name。注意该文件头部已标注 DEPRECATED,README 指明其替代品是 event hook 版本

危险插件:very-bad-idea.py

very-bad-idea.pymeadup.js 插件配合,可以把 copyparty 变成一个粗糙但极灵活的"Chromecast 克隆":

  • 任何通过 Android 端应用上传的文件或链接都会在服务器上被执行——这意味着任何拿到上传通道的人都可能投递恶意代码,README 的原话是"protect this with a password and keep it on a LAN!"(务必加密码、且只留在内网);
  • 它会顺带给 basic-upload 标签页加一个虚拟键盘,方便"沙发派"遥控操作;
  • README 同时推荐了 kamelåså 作为更成熟且安全得多的替代方案(该段为原文档的外部项目推荐,此处仅转述,不保证当前状态)。

依赖安装

两条路线:

  1. install-deps.sh:支持 Windows(msys2-mingw64)/Linux/macOS(macOS 需 macports)。脚本会 pip install --user keyfinder vamp,然后按需从源码构建 mixxxdj/libkeyfinder(装到 ~/pe/keyfinder)、vamp-plugin-sdk(装到 ~/pe/vamp-sdk)、beatroot-vamp 插件(装到 ~/vamp),并用 patchelf --set-rpath 修 keyfinder 的 .so 加载路径,甚至会在 Python 侧的 keyfinder 模块顶部注入一行 ctypes.cdll.LoadLibrary(...)。也支持 install-deps.sh keyfinder|vamp|soundtouch 单装。
  2. 直接用发行版包(README 认为更优):发行版包 numpy vamp-plugin-sdk beatroot-vamp mixxx-keyfinder ffmpeg,加 pip 的 keyfinder vamp

从 copyparty 接入

全卷生效

启动时带上扫描选项,再任意组合 mtp:

copyparty -e2dsa -e2ts \
  -mtp key=f,audio-key.py \
  -mtp .bpm=f,audio-bpm.py \
  -mtp ahash,vhash=f,media-hash.py

要点(与源码行为一一对应):

  • f 使新值覆盖已有标签(对应 MParser.force);
  • 标签名里的 . 前缀表示数值型标签——内置 tagmap 中 .bpm.tn.dur 等都是这类,见 MTag 的 tagmap 定义.bpm 会同时匹配原始标签 bpm/tbpm/tmpo/tbpkey 会匹配 initial-key/tkey/key
  • 脚本路径按启动 copyparty 的当前目录解析,不在该目录就写相对/绝对路径(如 bin/mtag/audio-key.py);
  • 首次启用新 mtp 时,用 -e2tsr 代替 -e2ts-e2tsr 会删库重扫(见 cfg.py 选项表),否则已有标签的文件会整卷跳过 mtp。

仅对单个卷生效(volflags)

把选项挂在 -v 的卷条目后面即可,:c 前缀表示"仅对本卷生效":

copyparty -v /mnt/nas/music:/music:r:c,e2dsa:c,e2ts \
  :c,mtp=key=f,audio-key.py \
  :c,mtp=.bpm=f,audio-bpm.py \
  :c,mtp=ahash,vhash=f,media-hash.py

实用技巧:定向删除标签并重扫

README 给了一个 sqlite 技巧:删除 blog* 前缀下所有文件的标签并让它们重新进入扫描队列(mt 表存标签,up 表存文件,w 是 16 进制路径 id):

sqlite3 .hist/up2k.db "delete from mt where w in (select substr(w,1,16) from up where rd like 'blog%')";

删除后这些文件不再满足"已有标签即跳过"的条件,下一次 mtp 扫描就会重新为它们跑全部解析器。

小结与选型建议

  • 只想"上传后触发脚本"(如 image-noexif 的用途):优先考虑 event hooks,免去 -e2ts/ffmpeg 门槛;
  • 想让整个存量曲库获得 BPM/Camelot 标签:-e2dsa -e2ts(首次 -e2tsr)+ 两个 audio-*.py,BPM 插件需要 vamp 栈,调性插件需要 keyfinder;
  • 想做内容级查重:media-hash.py 的流哈希优于文件哈希,能穿透改名与重封装;
  • 需要多标签一次性写入:参照 cksum.py/exe.py 的 JSON 契约写自己的脚本,用逗号标签名注册。
登录后查看全文
热门项目推荐
相关项目推荐