MediaCMS 9.4.0 REST API 使用详解
详解 MediaCMS 9.4.0 的 /api/v1 REST 接口:三种认证、媒体上传与转码全生命周期、搜索与资源接口的实测请求回显,附 Python 自动化集成示例脚本。
MediaCMS 9.4.0 REST API 使用详解
目标:系统讲解 MediaCMS 9.4.0 的 REST API——端点地图、三种认证方式、媒体全生命周期操作(上传/编辑/转码动作/删除)、搜索与其他资源接口,并提供可在生产中复用的调用示例。全部请求与响应均在 Ubuntu 24.04 实测环境的 9.4.0 实例上真实执行回显。
一、环境信息
本文所有 API 调用均在以下环境实测执行:
- 操作系统:Ubuntu 24.04.5 LTS(x86_64)
- MediaCMS 版本:9.4.0(应用内
cms/version.py实测VERSION = "9.4.0") - 服务地址:
http://127.0.0.1:8137(Docker 部署,宿主机端口 8137 映射 Web 服务 80) - 客户端工具:curl 8.5.0;Python 3.13.16(集成示例)
- 管理员账号:
admin(密码q1w2e3r4,实测环境演示用) - API 基址:
/api/v1/(Django REST Framework 实现)
二、前置条件
开始调用 API 前需确认以下事项:
- 服务已就绪:部署完成且首页可访问(部署过程见 《MediaCMS 9.4.0 Docker Compose 部署指南》)。验证命令与实测回显:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8137/
200
-
账号与权限:调用写操作(上传、编辑、删除、评论)需普通用户及以上身份;上传资格受
CAN_ADD_MEDIA配置约束(配置详解见 《MediaCMS 9.4.0 配置参数详解》)。管理员账号在初始化时由ADMIN_USER/ADMIN_PASSWORD创建。 -
认识两个文档页面:9.4.0 内置交互式 API 文档——Swagger UI(
/swagger/)与 ReDoc(/docs/api/),实测两者均返回 200,可直接在浏览器查看端点定义。注意实测部署中/swagger.json的裸 JSON 导出返回错误页(HTML),如需机器可读 schema 建议以浏览器访问 Swagger UI 页面获取。
三、API 总览
3.1 协议约定
实测归纳的接口约定(与 DRF 默认行为一致):
- 基址与版本:所有接口挂在
/api/v1/前缀下,无/api/v2/(实测 404)。 - 数据格式:请求与响应均为 JSON;两个例外——上传与更新走
multipart/form-data(POST /api/v1/media、PUT /api/v1/media/{friendly_token}),登录走表单字段。 - 分页:列表接口统一返回
{"count", "next", "previous", "results"},默认每页 50 条;用?page=N&page_size=M翻页,page_size实测无上限钳制(生产请自控以避免大结果集)。 - 认证:同一接口支持 Session Cookie、HTTP Basic、DRF Token 三种方式,写操作必须有认证。
- 语义化状态码:201 创建成功、204 删除成功、400 参数或资源不存在、401 未认证、403 无权限、405 方法不允许。
- 字段名注意:友好短码统一叫
friendly_token(媒体唯一标识,8 位大小写字母数字),媒体详情路由用friendly_token而非数字 ID。
3.2 端点地图
实测确认可用的端点清单:
| 端点 | 方法 | 说明 |
|---|---|---|
/api/v1/whoami | GET | 当前登录用户身份与权限 |
/api/v1/user/token | GET | 获取 DRF Token |
/api/v1/login | POST | 登录(multipart 表单:username/password/…) |
/api/v1/logout | GET | 注销 Session |
/api/v1/media | GET/POST | 媒体列表 / 上传媒体 |
/api/v1/media/{friendly_token} | GET/POST/PUT/DELETE | 媒体详情 / 管理动作 / 更新 / 删除 |
/api/v1/media/{friendly_token}/comments | GET/POST | 评论列表 / 发表评论 |
/api/v1/media/{friendly_token}/tags | GET/POST | 标签查询 / 添加 |
/api/v1/media/{friendly_token}/actions | GET | 可执行动作清单 |
/api/v1/search | GET | 全站搜索(?q= 关键词) |
/api/v1/categories | GET | 分类列表(裸数组,无分页包裹) |
/api/v1/tags | GET | 标签列表(分页对象) |
/api/v1/playlists | GET/POST | 播放列表 |
/api/v1/users | GET | 用户列表(分页对象,管理员视角) |
/api/v1/encode_profiles | GET | 转码配置(裸数组,带斜杠 /encode_profiles/ 实测可用) |
/swagger/ 与 /docs/api/ | GET | Swagger UI / ReDoc 文档页面 |
💡 技术拓展:DRF 是 Django 生态最主流的 REST 框架,其路由风格通常自动为列表端点补尾斜杠(
/api/v1/categories/请求/api/v1/categories会 301 跳转)。实测 MediaCMS 的列表接口对无斜杠直接返回 200,差异不影响 curl 使用,但脚本里固定一种写法更稳。
四、认证与身份
4.1 获取 Token 并验证身份
用 HTTP Basic Auth 调 Token 端点(账号密码经 -u 传递):
curl -s -u admin:q1w2e3r4 http://127.0.0.1:8137/api/v1/user/token
实测回显:
{"token":"76e5d2baccd73a0f405549ce05d21912ca7f917d"}
拿到 Token 后即可在后续请求中带 Authorization: Token <值> 头。验证身份用 whoami(两种方式等价):
curl -s -u admin:q1w2e3r4 http://127.0.0.1:8137/api/v1/whoami
curl -s -H "Authorization: Token 76e5d2baccd73a0f405549ce05d21912ca7f917d" http://127.0.0.1:8137/api/v1/whoami
实测回显(管理员身份):
{"username":"admin","admin":true,"advancedUser":true,"author_name":"","name":"","email":"admin@localhost","media":[],"logo_url":"/media/userlogos/user.jpg","description":"","title":"admin","playlists":[],"user_id":2}
4.2 未认证调用的行为
写操作不带认证时返回 401,实测:
curl -s -X POST http://127.0.0.1:8137/api/v1/media -F "media_file=@testvideo.mp4" -w '\nHTTP %{http_code}\n'
实测回显:
{"detail":"Authentication credentials were not provided."}
HTTP 401
⚠️ 注意:三种认证方式中,Token 最适合脚本与 CI;Session Cookie 适合浏览器内交互(登录页表单提交后获得);Basic Auth 简单但每次请求都传密码,仅限内网调试。
五、媒体接口详解
5.1 上传媒体
POST /api/v1/media,multipart 表单,文件字段名固定为 media_file(实测传错字段名会被 DRF 校验拒绝):
TOKEN=76e5d2baccd73a0f405549ce05d21912ca7f917d
curl -s -X POST http://127.0.0.1:8137/api/v1/media \
-H "Authorization: Token $TOKEN" \
-F "media_file=@/opt/data/quzuo/tmp/mediacms/testvideo.mp4" \
-F "title=G23续作探针视频-上传转码链路实测"
实测回显(节选,关注关键字段):
{
"friendly_token": "iisVw8isO",
"title": "G23续作探针视频-上传转码链路实测",
"media_type": "video",
"state": "public",
"encoding_status": "pending",
"url": "/view?m=iisVw8isO",
"user": "admin"
}
字段解读:encoding_status 初始为 pending,转码由 Celery 异步执行(实测 6 秒小视频完成);state 为媒体三态(public/unlisted/private,默认值由 PORTAL_WORKFLOW 决定)。若上传时携带的文件类型不在 ALLOWED_MEDIA_UPLOAD_TYPES 内会返回 400。
5.2 查询媒体列表与详情
列表(含全站检索建议直接用搜索接口,见 5.6):
curl -s -H "Authorization: Token $TOKEN" 'http://127.0.0.1:8137/api/v1/media?page_size=1&page=1'
实测回显(节选):
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"friendly_token": "WpOLkLg33",
"title": "转码链路验证测试视频",
"media_type": "video",
"state": "public",
"encoding_status": "success"
}
]
}
详情(GET /api/v1/media/{friendly_token},返回体较大,节选关键字段):
curl -s -H "Authorization: Token $TOKEN" http://127.0.0.1:8137/api/v1/media/WpOLkLg33
实测回显(节选):
{
"friendly_token": "WpOLkLg33",
"title": "转码链路验证测试视频",
"original_media_url": "/media/original/user/admin/824d679743bd4b55be34049bf234dca7.testvideo.mp4",
"encoding_status": "success",
"hls_info": {
"master_file": "/media/hls/824d679743bd4b55be34049bf234dca7/master.m3u8",
"144_playlist": "/media/hls/824d679743bd4b55be34049bf234dca7/media-1/stream.m3u8",
"240_playlist": "/media/hls/824d679743bd4b55be34049bf234dca7/media-2/stream.m3u8",
"iframe_playlist": "/media/hls/824d679743bd4b55be34049bf234dca7/iframe.m3u8"
},
"encodings_info": {
"2160": {"title": "2160p"},
"144": {"title": "144p", "status": "success",
"url": "/media/encoded/2/admin/824d679743bd4b55be34049bf234dca7.824d679743bd4b55be34049bf234dca7.testvideo.mp4.mp4"},
"240": {"title": "240p", "status": "success"}
}
}
字段解读:encodings_info 的键是分辨率档位(2160/1440/1080/720/480/360/240/144 固定出现,未转码的档位只有 title),每档含转码进度 progress 与 MP4 产物地址;hls_info 给出 HLS 主清单与各档流清单。集成系统应以 hls_info.master_file 作为播放地址来源,而不是手工拼路径。
播放验证——直接 GET HLS 主清单(200 即流可用):
curl -s -o /dev/null -w '%{http_code} (%{size_download}B)\n' \
http://127.0.0.1:8137/media/hls/824d679743bd4b55be34049bf234dca7/master.m3u8
实测回显:
200 (922B)
5.3 更新媒体(PUT,multipart)
PUT /api/v1/media/{friendly_token} 走 multipart 表单(与上传同构,不支持 PATCH,实测返回 405):
curl -s -X PUT -H "Authorization: Token $TOKEN" \
-F "title=G23续作探针视频-上传转码链路实测" \
-F "description=G23续作代理于2026-10-29上传,实测API上传-转码-HLS播放链路" \
http://127.0.0.1:8137/api/v1/media/iisVw8isO -w '\nHTTP %{http_code}\n'
实测回显(节选):
{
"title": "G23续作探针视频-上传转码链路实测",
"description": "G23续作代理于2026-10-29上传,实测API上传-转码-HLS播放链路"
}
HTTP 201
PATCH 方法的实测行为(确认不支持):
curl -s -X PATCH -H "Authorization: Token $TOKEN" -H 'Content-Type: application/json' \
-d '{"description":"try patch"}' http://127.0.0.1:8137/api/v1/media/iisVw8isO
实测回显:
{"detail":"Method \"PATCH\" not allowed."}
5.4 管理动作(POST encode / review)
对已存在的媒体 POST /api/v1/media/{friendly_token}(multipart:type + result),触发重新转码或审核标记:
curl -s -X POST -H "Authorization: Token $TOKEN" \
-F "type=encode" http://127.0.0.1:8137/api/v1/media/WpOLkLg33 -w '\nHTTP %{http_code}\n'
实测回显(节选):
{
"action": "encode",
"size": 1000,
"manual": true
}
HTTP 201
type=review + result=true 则把 reviewed 置真(配合 MEDIA_IS_REVIEWED = False 的审核工作流使用),实测同样返回 201。
5.5 删除媒体
DELETE /api/v1/media/{friendly_token} 成功返回 204 无响应体:
curl -s -X DELETE -H "Authorization: Token $TOKEN" \
http://127.0.0.1:8137/api/v1/media/iisVw8isO -w 'HTTP %{http_code}\n'
实测回显:
HTTP 204
删除后再查同一资源的实测回显(注意状态码是 400 而非 404):
curl -s -H "Authorization: Token $TOKEN" http://127.0.0.1:8137/api/v1/media/iisVw8isO -w '\nHTTP %{http_code}\n'
实测回显:
{"detail":"media file does not exist"}
HTTP 400
5.6 搜索
GET /api/v1/search?q=<关键词>,返回分页的媒体结果(支持中文关键词实测命中):
curl -s -H "Authorization: Token $TOKEN" 'http://127.0.0.1:8137/api/v1/search?q=%E8%BD%AC%E7%A0%81'
实测回显(节选):
{
"count": 1,
"results": [
{"friendly_token": "WpOLkLg33", "title": "转码链路验证测试视频", "media_type": "video"}
]
}
⚠️ 注意:搜索结果仅含当前用户有权限看到的媒体(匿名用户只能搜到 public 状态内容)。
5.7 评论与标签子资源
评论列表与创建(创建用 JSON,返回 201):
curl -s -X POST -H "Authorization: Token $TOKEN" -H 'Content-Type: application/json' \
-d '{"text":"G23续作代理 API 评论实测"}' \
http://127.0.0.1:8137/api/v1/media/WpOLkLg33/comments
实测回显:
{"add_date":"2026-10-29T10:30:43.382551Z","text":"G23续作代理 API 评论实测","parent":null,"author_thumbnail_url":"/media/userlogos/user.jpg","author_profile":"/user/admin/","author_name":"","media_url":"/view?m=WpOLkLg33","uid":"f580725c-e4a6-4ebb-8c84-7c864c23715f"}
标签子资源挂 /api/v1/media/{friendly_token}/tags(GET 查询 / POST 添加),动作清单挂 /api/v1/media/{friendly_token}/actions。
六、其他资源接口
6.1 分类与标签
两个接口的响应结构不同——分类是裸数组,标签是分页对象,集成时注意解析差异:
curl -s -H "Authorization: Token $TOKEN" http://127.0.0.1:8137/api/v1/categories
curl -s -H "Authorization: Token $TOKEN" http://127.0.0.1:8137/api/v1/tags
实测回显(categories,裸数组):
[{"title":"分类A","url":"/api/v1/categories/","description":"","add_date":"2026-10-28T12:36:29.330780Z","parent":null,"user":"admin","id":1}]
实测回显(tags,分页对象,节选):
{
"count": 1,
"results": [
{"title":"test","description":"","media_count":1,"id":1}
]
}
⚠️ 注意:实测部署中若后台尚未创建任何分类,
categories返回空数组[]——空结果与「接口异常」要靠状态码区分(200 即正常)。
6.2 用户、播放列表与转码配置
curl -s -u admin:q1w2e3r4 http://127.0.0.1:8137/api/v1/users
实测回显(节选):
{
"count": 1,
"results": [
{"username":"admin","name":"","email":"admin@localhost","date_added":"2026-10-28T12:36:29.438011Z","is_superuser":true,"advancedUser":true,"media_count":1}
]
}
播放列表 /api/v1/playlists(GET 列表 / POST 创建)、转码配置 /api/v1/encode_profiles/(GET 裸数组)结构类似,实测均正常返回。转码配置内容与逐项含义见配置篇 3.3 节,此处不重复。
七、代码集成示例
以下 Python 脚本覆盖「认证→上传→轮询转码→取播放地址」的完整集成路径,可直接用于生产(仅依赖标准库 + requests):
import time
import requests
BASE = "http://127.0.0.1:8137" # MediaCMS 服务地址
USER, PASSWORD = "admin", "q1w2e3r4" # 生产环境请从环境变量读取
# 1. 认证:Basic 换 Token(Token 长期有效,建议缓存复用)
r = requests.get(f"{BASE}/api/v1/user/token", auth=(USER, PASSWORD))
token = r.json()["token"]
headers = {"Authorization": f"Token {token}"}
# 2. 上传视频(multipart,字段名固定 media_file)
with open("testvideo.mp4", "rb") as f:
r = requests.post(f"{BASE}/api/v1/media", headers=headers,
files={"media_file": f}, data={"title": "自动化上传示例"})
media = r.json()
print("friendly_token:", media["friendly_token"], "| encoding_status:", media["encoding_status"])
# 3. 轮询转码状态(pending → success;失败为 fail)
ft = media["friendly_token"]
for _ in range(120): # 最长等 20 分钟
d = requests.get(f"{BASE}/api/v1/media/{ft}", headers=headers).json()
if d["encoding_status"] == "success":
break
time.sleep(10)
else:
raise RuntimeError("transcode timeout")
# 4. 取 HLS 播放地址
master = d["hls_info"]["master_file"] # 如 /media/hls/<哈希>/master.m3u8
print("HLS:", f"{BASE}{master}")
脚本要点解读:Token 应缓存复用而非每次请求重新获取;轮询间隔按视频时长调节(实测 6 秒小视频 10 秒间隔已绰绰有余);生产中把 BASE 与凭据改为环境变量注入,避免硬编码。
八、常见问题(FAQ)
Q1:上传返回 400「no such table」或空响应体?
多为数据库未初始化完成(migrations 服务尚未跑完)或 Redis 不可达导致 Celery 链路异常。先 docker compose ps 确认各服务健康,再看 celery_worker 日志。
Q2:上传字段名写 file 或 video 报错?
MediaCMS 9.4.0 的上传字段名固定为 media_file,错误响应会明确提示 {"media_file":["No file was submitted."]}(实测),按提示改字段名即可。
Q3:为什么改标题用 PUT 而不是 PATCH?
实测 PATCH /api/v1/media/{friendly_token} 返回 {"detail":"Method \"PATCH\" not allowed."}——该视图只放行 GET/POST/PUT/DELETE 四方法。PUT 需要 multipart 表单提交(与上传同构),JSON 体实测不被解析。
Q4:删除后再 GET 报 400 而不是 404?
这是 9.4.0 的实测行为:资源不存在统一返回 {"detail":"media file does not exist"} 且状态码 400。集成判断「资源是否还在」时用响应体文本或区分 200/400,不要依赖 404。
Q5:返回的 URL 都是相对路径(如 /media/...)?
API 返回的媒体地址以 /media/、/view?m= 等相对路径为主,集成方需用部署时配置的 FRONTEND_HOST 拼接完整地址(配置详解见配置篇 5.1 节)。
Q6:有官方 API 文档页面吗?
有。浏览器访问 /swagger/(Swagger UI)或 /docs/api/(ReDoc)查看端点定义;实测两者 200 可用,但 /swagger.json 裸 JSON 导出在本部署返回错误页,如需 schema 建议以页面为准。
Q7:怎么批量导入已有视频库? 循环调用上传接口即可(参考第七节脚本);存量平台(YouTube/Kaltura/Panopto)迁移可直接用门户内置的 Migrations 工具(管理员菜单入口),无需自行编写脚本。
九、参考链接
- 官方管理员文档:https://github.com/mediacms-io/mediacms/blob/main/docs/admins_docs.md
- Django REST Framework 文档:https://www.django-rest-framework.org/
- Swagger UI(本实例内置):
http://127.0.0.1:8137/swagger/ - ReDoc(本实例内置):
http://127.0.0.1:8137/docs/api/ - 本站部署实操:《MediaCMS 9.4.0 Docker Compose 部署指南》
- 本站配置手册:《MediaCMS 9.4.0 配置参数详解》
- 本站选型分析:《MediaCMS 9.4.0 介绍与选型指南》