SwingMusic 3.0.0 API 使用详解
SwingMusic 3.0.0 HTTP API 实测手册:JWT 鉴权与路由前缀、检索/详情/音频流/收藏/播放列表全链路 curl 实证、可编排 Python 演示脚本与五类对接排障
SwingMusic 3.0.0 API 使用详解
目标:本文面向需要以脚本或第三方程序对接 SwingMusic 的读者,梳理 v3.0.0 的 HTTP API 面貌——鉴权方式、路由前缀规律、检索与播放链路、收藏与播放列表写操作、OpenAPI 规范获取,并附 curl 最小调用集。全部请求与响应均在 Ubuntu 24.04 服务器实测通过(服务端口 8154,曲库 4 首、2 张专辑、2 位艺术家)。
一、环境信息
- 操作系统:Ubuntu 24.04(x86_64)
- SwingMusic:v3.0.0(部署见《SwingMusic 3.0.0 Docker Compose 部署指南》)
- 服务端口:8154(宿主)→ 1970(容器内)
- 曲库:4 首测试音频(2 张专辑:Tidal Lines、晨雾专辑;2 位艺术家:Blue Harbor、秋日山道)
- 演示账号:admin / q1w2e3r4
- 工具:curl 8.x + python3
二、前置条件
- 服务在线且曲库已扫描(探活与初始化状态检查):
curl -s -o /dev/null -w 'GET / -> %{http_code}\n' http://localhost:8154/
curl -s http://localhost:8154/onboarding-data
实测输出:
GET / -> 200
{"adminExists":true,"onboardingComplete":true,"rootDirsSet":true,"scanMessage":"","userHome":"/music"}
- 已有管理员账号(向导创建,或
swingmusic --password-reset重置口令)。
三、API 基础
3.1 协议与规范获取
SwingMusic 基于 Flask + flask_openapi3 构建,HTTP/1.1(无 WebSocket 业务端点),交互均为 JSON(音频流除外)。v3.0.0 提供完整 OpenAPI 文档站:
| 资源 | 路径 | 说明 |
|---|---|---|
| Swagger UI | GET /docs/ | 交互式接口文档页 |
| OpenAPI 规范 | GET /docs/openapi.json | 机读全量规范 |
curl -s -b /tmp/sm-ck.txt http://localhost:8154/docs/openapi.json -o /tmp/openapi.json \
&& python3 -c "
import json, collections
d = json.load(open('/tmp/openapi.json'))
paths = d['paths']
ops = [m for p in paths.values() for m in p if m in ('get','post','put','delete')]
print(d['info']['title'], '|', d['info']['version'])
print('paths:', len(paths), '| operations:', len(ops), '|', dict(collections.Counter(ops)))"
实测输出:
Swing Music API | 3.0.0
paths: 58 | operations: 95 | {'get': 61, 'post': 32, 'delete': 1, 'put': 1}
💡 技术拓展:SwingMusic 的 API 设计走「名词+动词路径」而非严格 RESTful——路径前缀表达资源域(/album、/artist、/getall…),动作用路径后缀表达(/favorites/add、/playlists/new)。对接时不必按 REST 语义猜状态码,以本文实测为准。
3.2 鉴权方式
全站 JWT Bearer 鉴权(中间件全局注入,静态资源与登录端点豁免)。流程:登录 → 取 accesstoken → 后续请求带 Authorization: Bearer <token>(或 Cookie 亦可)。
登录(无 /api 前缀,注意路由挂载在根路径):
curl -s -X POST http://localhost:8154/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"q1w2e3r4"}' \
-c /tmp/sm-ck.txt | python3 -c "import json,sys; d=json.load(sys.stdin); print('accesstoken[:36]:', d['accesstoken'][:36], '...')"
实测输出:
accesstoken[:36]: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
响应体实测含 accesstoken / refreshtoken / maxage / msg 四个字段(maxage: 2592000 即 Cookie 有效期 30 天,msg: Logged in as admin);业务令牌为 accesstoken,过期后用 /auth/refresh 换新。以下两种携带方式等价,本文为简洁统一用 Cookie(curl -b 从登录响应自动落盘):
# 方式一:Authorization 头
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8154/getall/albums
# 方式二:登录 Cookie(-c 保存,-b 回放)
curl -s -b /tmp/sm-ck.txt http://localhost:8154/getall/albums
未携带令牌访问业务接口的实测表现:
curl -s -o /dev/null -w 'noauth -> %{http_code}\n' http://localhost:8154/getall/albums
实测输出:
noauth -> 401
3.3 路由前缀总览
58 个路径按前缀分域(openapi.json 实测归纳):
| 前缀 | 域 | 代表端点 |
|---|---|---|
| /auth | 认证与配对 | /auth/login、/auth/profile/create、/auth/getpaircode、/auth/pair |
| /getall | 库列表分页 | /getall/albums、/getall/artists、/getall/folders、/getall/playlists |
| /album、/artist | 实体详情 | POST /album(详情+曲目)、GET /artist/<hash>、GET /artist/<hash>/albums |
| /search | 检索 | /search/top、/search/(loadmore 分页) |
| /file | 音频流 | GET /file/<trackhash>/legacy、GET /file/silence |
| /folder | 文件夹浏览 | POST /folder |
| /favorites | 收藏 | /favorites/add、/favorites/remove、/favorites/tracks |
| /playlists | 播放列表 | /playlists/new、/playlists/{id}/add、/playlists/{id}/delete |
| /notsettings | 管理 | /notsettings、/notsettings/update、/notsettings/trigger-scan |
| /home、/nothome | 首页聚合 | /nothome/recents/added 等 |
注意两组易混前缀:/home 与 /nothome、/settings 与 /notsettings——后者是前者「未登录态受限视图」的历史命名(响应结构一致,管理动作仅 /notsettings 组有)。
3.4 通用响应结构
- 列表接口统一
{total, start, items}三段(getall 系)或{top_result, tracks, albums, artists}(search); - 详情接口首键 info,曲目数组 tracks;
- 错误统一
{"error": "<原因>"}+ 对应 HTTP 状态码; - 参数校验失败返回 FastAPI 风格 422
{"detail":[...]}(实测见 4.3)。
四、核心端点实测
4.1 曲库列表(/getall)
按字段排序与翻页。start 是列表项偏移(支持 -1 倒序取末页,实测正常),sortby 可用 title/artist/album/pin/year/duration/playbitrate 等:
curl -s -b /tmp/sm-ck.txt 'http://localhost:8154/getall/albums?start=0&sortby=title&reverse=0' \
| python3 -c "import json,sys; d=json.load(sys.stdin); print('total:', d['total'], '| albums:', [(i['title'], i.get('albumhash')) for i in d['items']])"
实测输出:
total: 2 | albums: [('Tidal Lines', '0a5ebd99f07e6619'), ('晨雾专辑', '0437d10dc607ae05')]
同域端点:/getall/artists、/getall/folders、/getall/playlists、/getall/ genres 等,参数与响应结构一致。
4.2 专辑详情(POST /album)
专辑详情是 POST 语义(请求体传 hash 与可选 limit),同时返回信息与曲目数组:
curl -s -b /tmp/sm-ck.txt -X POST http://localhost:8154/album \
-H 'Content-Type: application/json' -d '{"albumhash":"0a5ebd99f07e6619","limit":2}' \
| python3 -c "
import json,sys
d = json.load(sys.stdin)
print('title:', d['info']['title'], '| year:', d['info'].get('date'), '| trackcount:', d['info']['trackcount'])
for t in d['tracks']: print(' track', t['track'], '-', t['title'], '| hash:', t['trackhash'])"
实测输出:
title: Tidal Lines | year: 2025 | trackcount: 2
track 2 - Sea Glass | hash: d1f72f5faf6c58cb
track 1 - Salt Air | hash: 64ae7697bcc37102
info 内同时含 og_title(清洗前原始标题)与 cleanAlbumTitle 生效后的 title。注意两点:①响应内 tracks 数组按曲目序号倒序返回(脚本侧需自行按 track 字段正排);②曲目 hash 会随音频内容/标签变化,编排时以详情接口实时返回为准,勿硬编码。
4.3 艺术家详情与专辑(GET /artist)
curl -s -b /tmp/sm-ck.txt http://localhost:8154/artist/49749c843ec9a947 \
| python3 -c "import json,sys; d=json.load(sys.stdin); print('artist:', d['artist']['name'], '| albumcount:', d['artist']['albumcount'])"
实测输出:
artist: Blue Harbor | albumcount: 1
响应为 {"artist": {...}, "albums": {"albums": [...], "appearances": [...], "compilations": [...], "singles_and_eps": [...]}} 双键结构,艺术家下挂四类专辑分组,脚本取数时注意层级。
参数错误时的 422 校验响应(artisthash 传了 15 位而非规定长度):
curl -s -b /tmp/sm-ck.txt http://localhost:8154/artist/49749c843ec9a9 -w '\nHTTP %{http_code}\n'
实测输出:
{"detail":[{"type":"string_too_long","loc":["path","artisthash"],"msg":"String should have at most 16 characters","input":"49749c843ec9a9","ctx":{"max_length":16}}]}
HTTP 422
4.4 检索(/search/top 与 /search/)
/search/top 一次给出 top_result 与三类分域结果,中文检索实测:
curl -s -b /tmp/sm-ck.txt 'http://localhost:8154/search/top?q=%E6%99%A8%E9%9B%BE' \
| python3 -c "
import json,sys
d = json.load(sys.stdin)
print('top:', d['top_result']['title'], '| album:', d['top_result'].get('album'))
print('albums:', len(d['albums']), '| artists:', len(d['artists']), '| tracks:', len(d['tracks']))"
实测输出:
top: 晨雾 | album: 晨雾专辑
albums: 1 | artists: 0 | tracks: 2
/search/ 按 itemtype 分页取更多(itemtype=tracks/albums/artists/folders),响应为 {"more": bool, "results": [...]} 平铺结构:
curl -s -b /tmp/sm-ck.txt 'http://localhost:8154/search/?q=Sea%20Glass&itemtype=tracks' \
| python3 -c "import json,sys; d=json.load(sys.stdin); print('results:', [t['title'] for t in d['results']])"
实测输出:
results: ['Sea Glass']
4.5 音频流(/file)
按 trackhash 取流,请求体/查询参数三要素:filepath(绝对路径,防越权)、quality(original 或 1411/1024/512/320/256/128/96)、container(mp3/aac/flac/webm/ogg)。原文件直出实测:
curl -s -b /tmp/sm-ck.txt -D - -o /dev/null \
'http://localhost:8154/file/64ae7697bcc37102/legacy?filepath=%2Fmusic%2FBlue%20Harbor%2FTidal%20Lines%2F01-Salt%20Air.mp3&quality=original&container=mp3' \
| head -8
实测输出(响应头节选):
HTTP/1.1 200 OK
content-disposition: attachment; filename="01-Salt Air.mp3"
content-type: audio/mpeg
content-length: 193902
last-modified: Fri, 30 Oct 2026 00:52:48 GMT
cache-control: no-cache
etag: "1793321568.695645-193902-1984696201"
Range 播放实测(返回 206 部分内容,拖动进度条依赖此能力):
curl -s -b /tmp/sm-ck.txt -r 0-99 -o /dev/null \
-w 'range -> %{http_code} type=%{content_type} got=%{size_download}B\n' \
'http://localhost:8154/file/64ae7697bcc37102/legacy?filepath=%2Fmusic%2FBlue%20Harbor%2FTidal%20Lines%2F01-Salt%20Air.mp3&quality=original&container=mp3'
实测输出:
range -> 206 type=audio/mpeg got=100B
⚠️ 实测提醒:quality/container 参数在 v3.0.0 已定义但被忽略——传 quality=128 实测返回的仍是 193902 字节原文件(200),按需转码路由在当前版本源码中处于注释停用状态;镜像内置 ffmpeg(7.1.4)目前服务于扫描期的音轨分析而非播放转码。选型如强依赖服务端转码,请以官方仓库后续版本说明为准。
4.6 收藏(/favorites)
三类对象(type=track/album/artist)的增查删,收藏按曲目实测(hash 用 4.2 实取的 Salt Air):
curl -s -b /tmp/sm-ck.txt -X POST http://localhost:8154/favorites/add \
-H 'Content-Type: application/json' -d '{"hash":"64ae7697bcc37102","type":"track"}'
实测输出:
{"msg":"Added to favorites"}
查询收藏曲目:
curl -s -b /tmp/sm-ck.txt 'http://localhost:8154/favorites/tracks?limit=3&start=0' \
| python3 -c "import json,sys; d=json.load(sys.stdin); print('total:', d['total'], '| tracks:', [t['title'] for t in d['tracks']])"
实测输出:
total: 1 | tracks: ['Salt Air']
取消收藏:
curl -s -b /tmp/sm-ck.txt -X POST http://localhost:8154/favorites/remove \
-H 'Content-Type: application/json' -d '{"hash":"64ae7697bcc37102","type":"track"}'
实测输出:
{"msg":"Removed from favorites"}
4.7 播放列表(/playlists)
创建(响应 201,返回 playlist 对象)、列表(data 包装)、删除(DELETE 方法):
curl -s -b /tmp/sm-ck.txt -X POST http://localhost:8154/playlists/new \
-H 'Content-Type: application/json' -d '{"name":"深夜电台"}' \
-o /tmp/newpl.json -w 'create -> %{http_code}\n'
head -c 120 /tmp/newpl.json; echo
curl -s -b /tmp/sm-ck.txt http://localhost:8154/playlists \
| python3 -c "import json,sys; d=json.load(sys.stdin); print('playlists:', [(p['id'], p['name'], p['count']) for p in d['data']])"
实测输出:
create -> 201
{"playlist":{"_last_updated":"","_score":0,"count":0,"duration":0,"extra":{},"has_image":false,"id":1,"image":"None","im
playlists: [(1, '深夜电台', 0)]
同名重复创建实测返回 409:
curl -s -b /tmp/sm-ck.txt -X POST http://localhost:8154/playlists/new \
-H 'Content-Type: application/json' -d '{"name":"深夜电台"}' -w '\nHTTP %{http_code}\n'
实测输出:
{"error":"Playlist already exists"}
HTTP 409
加曲用 /playlists/{id}/add,请求体字段是 itemhash(传 trackhash 会得到 422 缺参校验):
curl -s -b /tmp/sm-ck.txt -X POST http://localhost:8154/playlists/1/add \
-H 'Content-Type: application/json' -d '{"itemhash":"64ae7697bcc37102"}' -w '\nHTTP %{http_code}\n'
实测输出:
{"msg":"Done"}
HTTP 200
随后列表 count 实测变为 1。删除用 DELETE 方法(POST 会得到 405):
curl -s -b /tmp/sm-ck.txt -X DELETE http://localhost:8154/playlists/1/delete -w '\nHTTP %{http_code}\n'
实测输出:
{"msg":"Done"}
HTTP 200
其余管理端点:/playlists/{id}/update(改名/置顶设置)、/playlists/{id}/pin_unpin、/playlists/{id}/remove-tracks、/playlists/{id}/remove-img,均按 openapi.json 中各 schema 传参。
4.8 管理端点(/notsettings)
配置查询、运行时改参、触发扫描与根目录管理四件套(改参的键清单与生效机制详见《SwingMusic 3.0.0 配置参数详解》):
curl -s -b /tmp/sm-ck.txt -X PUT http://localhost:8154/notsettings/update \
-H 'Content-Type: application/json' -d '{"key":"scanInterval","value":10}'
实测输出:
{"msg":"Config updated!"}
curl -s -b /tmp/sm-ck.txt http://localhost:8154/notsettings/trigger-scan
实测输出:
{"msg":"Scan triggered!"}
五、可执行编排脚本
下面这个 Python 脚本串联完整业务链路,可直接落地复用:登录 → 检索 → 取专辑详情 → 收藏 → 建播放列表 → 取流播放(Range 前缀请求)。保存为 swingmusic_demo.py:
#!/usr/bin/env python3
"""SwingMusic 3.0.0 API 链路演示:登录→检索→详情→收藏→播放列表→取流。"""
import json, urllib.parse, urllib.request
BASE = "http://localhost:8154"
FILEPATH = "/music/Blue Harbor/Tidal Lines/01-Salt Air.mp3"
def call(path, data=None, token=None, method=None):
req = urllib.request.Request(BASE + path, method=method or ("POST" if data else "GET"))
req.add_header("Content-Type", "application/json")
if token:
req.add_header("Authorization", "Bearer " + token)
body = json.dumps(data).encode() if data else None
with urllib.request.urlopen(req, body) as r:
return json.loads(r.read())
# 1. 登录
tok = call("/auth/login", {"username": "admin", "password": "q1w2e3r4"})["accesstoken"]
print("1. login ok, token len =", len(tok))
# 2. 检索中文关键词
top = call("/search/top?q=%E6%99%A8%E9%9B%BE", token=tok)
print("2. top_result =", top["top_result"]["title"])
# 3. 专辑详情(按曲目序号取第一轨——注意响应内曲目顺序为倒序)
alb = call("/album", {"albumhash": "0a5ebd99f07e6619", "limit": 2}, token=tok)
t1 = sorted(alb["tracks"], key=lambda t: t["track"])[0]
print("3. album =", alb["info"]["title"], "| first track =", t1["title"])
# 4. 收藏该曲目并回查总数
call("/favorites/add", {"hash": t1["trackhash"], "type": "track"}, token=tok)
favs = call("/favorites/tracks?limit=1&start=0", token=tok)
print("4. favorites total =", favs["total"])
# 5. 建播放列表(先建空表再单曲追加,预置 tracks 参数不参与计数)
pl = call("/playlists/new", {"name": "API 演示列表"}, token=tok)
call(f"/playlists/{pl['playlist']['id']}/add", {"itemhash": t1["trackhash"]}, token=tok)
pls = call("/playlists", token=tok)
print("5. playlists =", [(p["name"], p["count"]) for p in pls["data"]])
# 6. 取流前 256 字节(Range 播放前缀)
req = urllib.request.Request(
f"{BASE}/file/{t1['trackhash']}/legacy?filepath={urllib.parse.quote(FILEPATH)}"
"&quality=original&container=mp3")
req.add_header("Authorization", "Bearer " + tok)
req.add_header("Range", "bytes=0-255")
with urllib.request.urlopen(req) as r:
print("6. stream =", r.status, r.headers["Content-Type"], len(r.read()), "bytes")
运行:
python3 swingmusic_demo.py
实测输出:
1. login ok, token len = 355
2. top_result = 晨雾
3. album = Tidal Lines | first track = Salt Air
4. favorites total = 1
5. playlists = [('API 演示列表', 1)]
6. stream = 206 audio/mpeg 256 bytes
💡 注意第 6 步:收藏与播放列表是可变操作,脚本每次运行 count/total 会随之增长;生产集成请先查后写(避免重复收藏)。
六、常见问题(FAQ)
Q1:接口 404?先查前缀。
SwingMusic 的 auth 蓝图挂载在根路径——登录是 /auth/login 而非 /api/auth/login(两形态实测只有前者 200);同理 getall/search/favorites/playlists 都不带 /api 前缀。全量路径以 GET /docs/openapi.json 为准。
Q2:401 但确定带了 token? 实测 401 一般三种:① token 没带上(Authorization 头拼写、Cookie 未回放);② 服务端 private.key/serverId 变更导致旧 token 全部失效(重建配置目录后必现,重新登录即可);③ 访问了管理端点但当前用户非 admin(/notsettings 组要求管理员,普通用户实测同样 401/404 依端点而定)。
Q3:422 校验错误怎么读?
FastAPI 风格 {"detail":[{type,loc,msg,ctx}]},loc 指明是 path/body 哪个参数越界(实测 artisthash 长度超限见 4.3)。对照 openapi.json 中该端点的 schema 修正入参即可。
Q4:播放接口报错找不到文件?
filepath 参数必须与该 trackhash 在库中的绝对路径完全一致(容器内视角,如 /music/...),且 URL 编码。不一致时实测返回 422/错误 JSON。做法:先查详情拿 trackhash 与 filepath,再原样拼流接口。
Q5:移动 App 怎么配对?
不手填 token:App 扫码/取码走 GET /auth/getpaircode(生成配对码)→ POST /auth/pair 交换会话,与本文 Bearer 流程并存,见 openapi.json /auth 域。
七、参考链接
- 官方仓库:https://github.com/swingmx/swingmusic (api/ 目录为各端点源码权威,v3.0.0)
- 本机接口文档:
http://<服务器IP>:8154/docs/(Swagger UI) - 本机 OpenAPI 规范:
http://<服务器IP>:8154/docs/openapi.json - 站内延伸:《SwingMusic 3.0.0 介绍与选型指南》、《SwingMusic 3.0.0 Docker Compose 部署指南》、《SwingMusic 3.0.0 配置参数详解》、《mStream 6.32.0 API 使用详解》