API 文档
同一份目录,网页与程序都能使用
公开接口无需密钥,可搜索、预览和下载。写入接口使用独立的管理密钥,上传后即可检索,不需要重新部署页面。文件存于 Cloudflare R2,目录存于 D1。
https://product-film-library-mxtec.pages.dev/api/v1
curl --get "https://product-film-library-mxtec.pages.dev/api/v1/assets" \ --data-urlencode "q=收纳" \ --data "type=video" --data "page_size=20"
检索和读取
| 方法 | 路径 | 用途 |
|---|---|---|
| GET | /assets | 搜索、筛选和分页 |
| GET | /assets/{id} | 详情、文件链接和 ETag |
| GET / HEAD | /assets/{id}/content | 读取原文件;支持 Range、If-Range 和 ETag |
| GET / HEAD | /assets/{id}/download | 以附件方式下载 |
| GET / HEAD | /assets/{id}/thumbnail/assets/{id}/storyboard | 预览图片,缺失时返回 404 |
| GET | /facets · /stats | 动态分类、平台、标签与容量 |
| GET | /export?format=json/export?format=csv | 导出完整筛选结果,最多 10000 条 |
筛选参数
q 搜索标题、描述、产品、品牌、分类、平台和标签;多个词以空格分隔并同时匹配。platform、category、tag 精确匹配。
| 参数 | 可选值 / 说明 |
|---|---|
| type | video / image / audio / document |
| orientation | landscape / portrait / square,按尺寸判断;缺失尺寸不参与此筛选 |
| min_duration / max_duration | 秒,包含边界;未记录时长的条目不匹配 |
| min_width / min_height | 最小像素尺寸 |
| created_after / created_before | 入库时间范围,建议使用带时区 ISO 8601 时间 |
| updated_after | 最后修改时间下限,包含边界,可用于增量同步 |
| sort / order | created_at / updated_at / title / duration / size / views / likes / post_views;asc / desc |
| page / page_size | 页码从 1 开始,每页 1–100 条,默认 24 条 |
| status | 默认 ready;pending / archived / all 需要管理密钥 |
平台与分类不是固定枚举,新增条目会自动出现在筛选项中。各排序均附带 ID 作为稳定次序;并发入库时分页内容可能变化,完整导出请使用 /export。原始 ID 保留,新素材采用 UUID,不应从 ID 推断平台。
指标分开保存:video_views 是视频播放,post_views 是帖子查看,community_score 是社区得分,backers 和 raised 为众筹结果。缺失字段或 null 表示未知,不能当成零。
鉴权与密钥
管理接口使用 Authorization: Bearer <管理密钥>。它是本素材库的密钥,与 Cloudflare 账号密钥分开。服务器只保存密钥摘要;浏览页面不会得到管理密钥。
本机已配置 Windows 用户级加密凭据,命令行客户端可自动读取。程序可从环境变量 MEDIA_LIBRARY_API_KEY 读取密钥。请在本机项目目录运行:
# PowerShell:赋值给当前进程,不打印密钥 $env:MEDIA_LIBRARY_API_KEY = & .\scripts\get-token.ps1 node .\scripts\library.mjs auth
其他设备需要自行配置此环境变量。不要把密钥写进网页、公开仓库或链接参数。GET /auth 可检查连接;GET /events 返回最近 100 条管理操作记录;使用 ?after_id=123&limit=100 按 ID 升序读取后续变更,以响应中的 next_after_id 继续读取。变更记录与目录修改在同一数据库事务内提交,包含归档、恢复和永久删除。
上传文件
先创建记录,再向返回的地址上传原始字节。服务端按 SHA-256 去重,R2 验证文件校验和,验证成功才会公开入库。失败记录保持待上传状态,可重新上传或归档。
POST /api/v1/assets
Authorization: Bearer <管理密钥>
Content-Type: application/json
Idempotency-Key: <本次操作的唯一值>
{
"title": "产品结构演示",
"category": "家居与家具",
"tags": ["结构展示", "连续演示"],
"source": {"url": "https://example.com/original"},
"file": {
"name": "demo.mp4",
"mime_type": "video/mp4",
"size_bytes": 1234567,
"sha256": "<文件的 64 位 SHA-256>"
}
}首次返回 201;相同文件返回 200 和 duplicate: true,不会复制文件,也不会覆盖已有元数据。同一 Idempotency-Key 与不同请求体组合返回 409。
PUT /api/v1/assets/{id}/content
Authorization: Bearer <管理密钥>
Content-Type: video/mp4
Content-Length: 1234567
<原始二进制文件,不使用 multipart/form-data>如果上传完成后连接中断,可调用 POST /assets/{id}/complete,服务端会检查已存文件并完成入库。原文件内容不可就地替换;不同文件应创建新素材。上传视频不会在云端转码,画幅、时长由客户端提供;管理页会尝试读取浏览器可识别的文件信息。
预览图片使用 PUT /assets/{id}/thumbnail 或 /storyboard,提供 If-Match、X-Content-SHA256、Content-Length 和图片 Content-Type。支持 JPEG、PNG、WebP,每张最大 4 MiB。浏览器不支持的媒体编码仍可下载原文件。
修改、归档与恢复
先读取详情,保存响应的 ETag,再在修改请求中传入 If-Match。缺少时返回 428,版本过期时返回 412,避免两个程序互相覆盖。
PATCH /api/v1/assets/{id}
Authorization: Bearer <管理密钥>
If-Match: "<id>:2"
Content-Type: application/json
{"title":"新的素材标题","tags":["开箱","功能演示"]}顶层字段局部更新;tags 数组与 source、metrics 对象整体替换。修改嵌套对象前先读取并合并要保留的字段。
| 方法 | 路径 | 作用 |
|---|---|---|
| DELETE | /assets/{id} | 归档,保留文件 |
| POST | /assets/{id}/restore | 恢复归档 |
| DELETE | /assets/{id}/purge | 永久删除,必须先归档,并提供 If-Match 与值等于素材 ID 的 X-Confirm-Purge |
归档从公开目录和受管读取接口隐藏素材;已有 R2 公开直链、缓存或已下载副本不会因此撤回。永久删除清除目录记录、R2 原文件和通过 API 写入的预览,不能恢复。待上传和归档记录仍占用本库容量预算,可按需明确执行永久删除。
可运行的客户端
import { MediaLibrary } from './client.js';
const library = new MediaLibrary();
const result = await library.list({ q: '收纳', type: 'video' });
for await (const asset of library.iterate({ category: '家居与家具' })) {
console.log(asset.title, asset.urls.download);
}
// 写入时传 apiKey;先读取,再带 ETag 更新
const writer = new MediaLibrary({ apiKey: process.env.MEDIA_LIBRARY_API_KEY });
const current = await writer.get('<id>');
await writer.update(current.data.id, { tags: ['功能演示'] }, current.etag);import os
from client import MediaLibrary
library = MediaLibrary() # 读取无需密钥
for asset in library.iterate(q="收纳", type="video"):
print(asset["title"], asset["urls"]["download"])
writer = MediaLibrary(api_key=os.environ["MEDIA_LIBRARY_API_KEY"])
writer.upload("demo.mp4", {"title": "产品结构演示", "tags": ["结构展示"]})本机命令行
node scripts/library.mjs list "收纳" node scripts/library.mjs get X11 node scripts/library.mjs upload "D:/素材/demo.mp4" metadata.json node scripts/library.mjs update <id> patch.json node scripts/library.mjs archive <id> node scripts/library.mjs restore <id>
命令行写入自动读取已配置的加密凭据;环境变量 MEDIA_LIBRARY_URL 可覆盖服务地址。全部客户端源码可检查,不依赖 ChatGPT 登录。
限制与错误处理
单文件上传上限 90 MiB(94,371,840 字节),本库容量预算 8 GB,包含原文件、预览和待上传预留。请求 JSON 最多 64 KiB,每条最多 30 个标签。此版本使用单请求上传,不提供多段续传;网络中断可重试同一上传记录。
预算为本库写入保护,不能替代 Cloudflare 账号的全局计费上限。R2、D1 和 Pages Functions 额度按账号与套餐共享;当前使用免费额度内的配置,没有升级套餐。可在 Cloudflare 控制台检查用量。
| HTTP | 处理建议 |
|---|---|
| 400 / 422 | 检查参数、字段、大小与校验和 |
| 401 | 提供有效的管理密钥 |
| 404 | 资源不存在、尚未入库,或已归档 |
| 409 | 状态或幂等键冲突;读取最新记录后决定下一步 |
| 411 / 413 / 415 | 补充 Content-Length,缩小文件,或使用受支持格式 |
| 412 / 428 | 重新读取 ETag,并携带 If-Match |
| 503 | 服务暂时不可用;稍后重试,创建操作保持同一幂等键 |
| 507 | 库容量预算不足,先清理不需要的文件 |
{"error":{"code":"revision_conflict","message":"素材已被修改,请重新读取后再保存。","request_id":"..."}}保留 request_id 便于排查。API 版本通过 /api/v1 固定;未来破坏性字段变化使用新版本,已有播放与下载路径继续兼容。