AI 工具

Mubert 曲目授权:生成前的 License 校验

发布前用本地门禁核对 license 快照:状态 active、功能开启、时长上限、商用与署名意愿。不能替代正式合同。

BINBHO · 图文教程

发布前用本地门禁核对 license 快照:状态 active、功能开启、时长上限、商用与署名意愿。不能替代正式合同。

先看清成品

  • 合规样例 ok;streaming/超长/未商用/未署名 → 多条 reasons。
  • 2 项 pytest 通过。

合规提醒:Mubert 曲目授权取决于套餐与合同;免费/低档常需署名且用途受限。发布前核对 license 与是否允许商用/变现平台。曲目下载 URL 会过期,及时落盘。本文不编造套餐价格。

运行说明:无正式 MUBERT_CUSTOMER_ID / ACCESS_TOKEN。A/B 用 mock;对 music-api.mubert.com 无效凭证实测 HTTP 401。C 为本地 license 门禁。2026-10-23(Asia/Shanghai)pytest 通过。

做完之后你会得到什么?

成果 说明
license_guard.py 门禁
样例 JSON license/use
test_111c.py 测试

需要准备哪些环境?

项目 说明
Python 3.10+

关键事实

事实 来源
GET /api/v3/service/licenses 返回功能与限额 官方 License
-1 表示无限 官方 Docs
免费档署名与用途限制以官网为准 产品说明

文件结构

111-mubert-C-src/
├── license_guard.py
├── sample_license.json
├── sample_use_ok.json
├── sample_use_bad.json
└── test_111c.py

第 1 步:门禁

#!/usr/bin/env python3
"""Local license gate before publishing Mubert tracks.

Checks a license snapshot JSON (from GET /api/v3/service/licenses) plus your use case:
commercial flag, attribution, duration vs max_track_duration, feature enabled.
Does NOT replace the official contract — only a pre-flight checklist.
"""
from __future__ import annotations
import argparse, json, sys
from pathlib import Path


def check(license_obj: dict, use: dict) -> dict:
    reasons = []
    data = license_obj.get("data") or license_obj
    status = data.get("status")
    if status != "active":
        reasons.append(f"license status={status}")
    features = set(data.get("features") or [])
    if data.get("allow_all_features"):
        features |= {"track", "ttm", "streaming"}
    need = use.get("feature", "track")
    if need not in features and not data.get("allow_all_features"):
        reasons.append(f"feature {need} not in license")
    limits = data.get("license_limits") or {}
    max_dur = limits.get("max_track_duration")
    dur = int(use.get("duration", 0))
    if max_dur not in (None, -1) and dur > int(max_dur):
        reasons.append(f"duration {dur} > max_track_duration {max_dur}")
    if use.get("commercial") and not use.get("plan_allows_commercial", False):
        reasons.append("commercial use not confirmed for this plan")
    if use.get("needs_attribution") is True and not use.get("will_attribute", False):
        reasons.append("attribution required but will_attribute=false")
    return {"ok": not reasons, "reasons": reasons, "status": data.get("status"), "feature": need}


def main(argv=None) -> int:
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("license_json")
    ap.add_argument("use_json")
    args = ap.parse_args(argv)
    lic = json.loads(Path(args.license_json).read_text())
    use = json.loads(Path(args.use_json).read_text())
    r = check(lic, use)
    print(json.dumps(r, ensure_ascii=False, indent=2))
    return 0 if r["ok"] else 1


if __name__ == "__main__":
    raise SystemExit(main())

第 2 步:测试

import json, subprocess, sys
from pathlib import Path
HERE = Path(__file__).resolve().parent

def run(*extra):
    return subprocess.run([sys.executable, str(HERE/"license_guard.py"), *extra], capture_output=True, text=True)

def test_ok():
    r = run(str(HERE/"sample_license.json"), str(HERE/"sample_use_ok.json"))
    assert r.returncode == 0
    assert json.loads(r.stdout)["ok"] is True

def test_bad():
    r = run(str(HERE/"sample_license.json"), str(HERE/"sample_use_bad.json"))
    assert r.returncode == 1
    body = json.loads(r.stdout)
    assert body["ok"] is False
    assert len(body["reasons"]) >= 2

第 3 步:运行

$ pytest 111c
..                                                                       [100%]
2 passed in 0.06s

怎么检查结果?

  • ok 样例 exit 0。
  • bad 样例 reasons ≥ 2。

调试示例

「同事说可以商用」。 不够。以当前 license_limits 与合同邮件为准,脚本要求显式布尔。

常见问题

现象 原因 处理
多 license 每个产品线单独快照 分凭证
月配额 看 monthly_*_limit 用尽就停
署名文案 视频简介写 Mubert 按套餐要求

模板规则

  • 无合同不商用。
  • 超时长先改参数。
  • 门禁进发布流水线。

项目资源

用途 地址
API Docs(官方) https://mubert.com/api/docs
API 产品页 https://mubert.com/api
Mubert https://mubert.com

授权清楚,才敢把配乐推进成片。