MediaCMS 9.4.0 REST API 使用详解

详解 MediaCMS 9.4.0 的 /api/v1 REST 接口:三种认证、媒体上传与转码全生命周期、搜索与资源接口的实测请求回显,附 Python 自动化集成示例脚本。

MediaCMSMediaCMS2026/10/29

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 前需确认以下事项:

  1. 服务已就绪:部署完成且首页可访问(部署过程见 《MediaCMS 9.4.0 Docker Compose 部署指南》)。验证命令与实测回显:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8137/
200
  1. 账号与权限:调用写操作(上传、编辑、删除、评论)需普通用户及以上身份;上传资格受 CAN_ADD_MEDIA 配置约束(配置详解见 《MediaCMS 9.4.0 配置参数详解》)。管理员账号在初始化时由 ADMIN_USER/ADMIN_PASSWORD 创建。

  2. 认识两个文档页面: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/whoamiGET当前登录用户身份与权限
/api/v1/user/tokenGET获取 DRF Token
/api/v1/loginPOST登录(multipart 表单:username/password/…)
/api/v1/logoutGET注销 Session
/api/v1/mediaGET/POST媒体列表 / 上传媒体
/api/v1/media/{friendly_token}GET/POST/PUT/DELETE媒体详情 / 管理动作 / 更新 / 删除
/api/v1/media/{friendly_token}/commentsGET/POST评论列表 / 发表评论
/api/v1/media/{friendly_token}/tagsGET/POST标签查询 / 添加
/api/v1/media/{friendly_token}/actionsGET可执行动作清单
/api/v1/searchGET全站搜索(?q= 关键词)
/api/v1/categoriesGET分类列表(裸数组,无分页包裹)
/api/v1/tagsGET标签列表(分页对象)
/api/v1/playlistsGET/POST播放列表
/api/v1/usersGET用户列表(分页对象,管理员视角)
/api/v1/encode_profilesGET转码配置(裸数组,带斜杠 /encode_profiles/ 实测可用)
/swagger/ 与 /docs/api/GETSwagger 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 工具(管理员菜单入口),无需自行编写脚本。

九、参考链接