把视频项目接入生产系统

万象渲染为不同视频代码项目提供统一的项目管理、鉴权和异步任务 API。可以从内置模板快速生成首个视频,也可以接入已有项目并继续二创;渲染运行时的部署、扩缩容与结果存储由平台处理。

当前文档重点:统一接入协议,以及已经通过阿里云函数计算验证分片并发渲染链路。

支持的项目

平台按项目类型选择对应的构建与渲染适配器。两种项目都是上传构建产物创建版本, 渲染运行时的部署与扩缩容由平台处理。

项目类型输入接入方式运行时
hyperframes项目入口 + 渲染参数项目构建产物项目适配器
remotioncompositionId + inputProps版本化 bundleremotion-fc

计费方式

托管云渲染以最终输出的视频分钟数为用量单位。月消费达到更高档位后,当月全部渲染分钟使用对应单价,不采用累进分段计算。以下价格作为方案参考,实际开放档位以合同为准。

档位月消费区间单价适合场景
按量起步< ¥5,000¥0.30 / 分钟试用与小规模生产
稳定增长¥5,000–< ¥20,000¥0.25 / 分钟稳定业务调用
规模生产¥20,000–< ¥50,000¥0.20 / 分钟高频批量生成
大规模调用≥ ¥50,000¥0.15 / 分钟大客户与 API 批量任务
示例:当月渲染 20,000 分钟,适用 ¥0.25/分钟时,月费为 ¥5,000。最终档位和结算规则以企业合同为准。

系统架构

服务层统一处理 API Key、项目和版本解析、参数校验、租户配额与任务状态;运行时层根据项目类型执行实际渲染。

业务系统
Agent / App
→
万象渲染 API
鉴权 / 项目 / 任务
→
Runtime Adapter
按项目类型路由
→
Render Workers
并行执行
→
对象存储
状态 / 视频

快速开始

  1. 1

    创建项目与版本

    选择 hyperframes,并上传对应的构建产物。每次发布生成不可变的项目版本。

  2. 2

    创建 API Key

    API Key 只应保存在服务端环境变量或密钥管理服务中,不要暴露在浏览器代码里。

  3. 3

    提交渲染任务

    指定项目、版本、渲染入口和业务参数。接口异步返回 renderTaskId。

  4. 4

    查询或接收结果

    通过 renderTaskId 查询进度,完成后获得最终视频 URL;生产场景可以配置 Webhook。

认证方式

本地交互使用设备授权;Agent、CI 和服务端集成使用账号级 API Key,无需登录或打开 MassRender 网页。API Key 继承账号所属租户和账号权限;模板创建、更新与发布仍需对应模板管理权限。

CLI 登录

Authorization: Bearer <token>

适合开发者本机,massrender login 自动保存凭据。

Agent / CI

X-API-KEY: <key>

推荐自动化使用,只保存在密钥管理服务或环境变量。

Web 控制台

同源 Session Cookie

仅供浏览器控制台使用,不应复制到自动化脚本。

export MASSRENDER_API_URL=/api/v1
export MASSRENDER_API_KEY=***

npx @massrender/cli auth status --json
npx @massrender/cli whoami --json
npx @massrender/cli capabilities --json

公开 API

外部服务使用控制台生成的账号级 API Key,通过X-API-KEY请求头访问。任务归属于该账号所在租户,创建任务会从该租户结算费用;请勿在浏览器前端暴露密钥。

POST/api/v1/render-tasks

curl -X POST /api/v1/render-tasks \
  -H "X-API-KEY: $MASSRENDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "projectVersionId": "pv_01J...",
    "compositionId": "DataReport",
    "inputProps": { "headline": "2026 年度增长", "metricValue": 1280 }
  }'

GET/api/v1/render-tasks/:renderTaskId

curl /api/v1/render-tasks/rt_01J... \
  -H "X-API-KEY: $MASSRENDER_API_KEY"

{
  "renderTaskId": "rt_01J...",
  "status": "rendering",
  "progress": { "percent": 72, "completedChunks": 13, "totalChunks": 18 },
  "output": null
                }

OPENAPI 3.1 · 46 ENDPOINTS

展开端点可查看认证、参数、请求字段、响应 Schema 与示例。

下载 OpenAPI YAML
Base URL: /api/v1

RenderTasks

11
get/render-tasks游标分页查询渲染任务
post/render-tasks创建渲染任务
get/render-tasks/stats任务队列月度聚合指标
post/render-tasks/batch批量并行创建渲染任务
post/render-tasks/batch/import上传 Excel 与图片/视频素材并创建批量渲染
get/render-tasks/batches查询当前租户的批量渲染批次
get/render-tasks/batches/{renderBatchId}查询批次逐行状态
get/render-tasks/{renderTaskId}查询渲染任务
post/render-tasks/{renderTaskId}/cancel请求取消渲染任务
get/render-tasks/{renderTaskId}/download刷新渲染产物下载链接(实时重签 OSS URL,返回 200 + JSON)
post/render-tasks/upload-signature获取 bundle 上传签名(CLI 直传 OSS 用)

FfmpegTasks

2
post/ffmpeg-tasks创建通用 FFmpeg 任务
get/ffmpeg-tasks/{taskId}查询 FFmpeg 任务状态

RenderTemplates

6
get/render-templates列出可渲染的模板目录
post/render-templates创建或更新当前租户的私有渲染模板
get/render-templates/{templateId}查询单个模板详情(匿名可读)
put/render-templates/{templateId}更新当前租户的私有模板配置
delete/render-templates/{templateId}删除当前租户的私有模板
post/render-templates/{templateId}/publish发布模板(draft → published)

RenderProjects

4
get/render-projects列出当前租户的项目
post/render-projects创建渲染项目(CTR-006 幂等:按 slug 找或创建)
get/render-projects/{projectId}按 ID 查询项目
get/render-projects/by-slug/{slug}按 slug 查项目(CTR-006 §4.2)

PreviewSessions

3
post/preview-sessions创建短期 HyperFrames 预览会话
put/preview-sessions/{sessionId}更新预览变量
delete/preview-sessions/{sessionId}销毁预览会话

Marketplace

3
get/marketplace/templates浏览已发布市场模板
get/marketplace/templates/{slug}按 slug 查询市场模板详情
get/marketplace/templates/{templateId}/releases/{releaseId}查询一个公开的不可变 Release

Auth

4
post/auth/deviceCLI 设备授权 — 请求 device code
get/auth/device/statusCLI 轮询设备授权状态
post/auth/device/confirm用户在浏览器确认 CLI 授权
post/auth/device/logoutCLI 登出(注销 session)

Account

1
get/account返回当前用户、租户与认证类型

Billing

4
get/account/balance查询预付费余额与累计渲染消费
get/usage/summary查询本月、上月和近 30 天用量汇总
get/usage/daily查询最多 90 天的逐日用量
get/pricing查询当前租户生效的渲染价格

Capabilities

1
get/capabilities发现服务端引擎、质量档位和自动化能力

TempAssets

7
get/temp-assets列出当前租户未过期临时素材 + 配额
post/temp-assets上传临时素材(RFC 0004)
post/temp-assets/upload-token获取临时素材直传 OSS 凭证(大文件直传)
get/temp-assets/quota租户临时素材配额快照
post/temp-assets/{assetId}/complete完成临时素材直传注册
get/temp-assets/{assetId}查询单素材元数据(租户隔离,跨租户 404)
delete/temp-assets/{assetId}删除素材并释放配额(租户隔离,跨租户 404)

CLI 快速开始

万象渲染 CLI 让你从终端管理模板、上传 bundle 和发起渲染,自动处理鉴权和轮询。

0. 安装 CLI(首次使用)

推荐:直接用 npx,免安装免 PATH

npx @massrender/cli

或全局安装:npm install -g @massrender/cli,安装后即可使用 massrender 命令 (当前版本 v0.2.2,要求 Node.js ≥ 22)。

1. 登录

npx @massrender/cli login  # 打开浏览器完成授权

2. 查看可用模板

npx @massrender/cli template list

3. Agent 创建并等待任务

npx @massrender/cli render create \
  --template data-report \
  --props '{"headline":"2026 年度增长","metricValue":1280}' --json

npx @massrender/cli render wait <renderTaskId> --output=jsonl
npx @massrender/cli render download <renderTaskId> --file result.mp4 --json

CLI 命令参考

下列资源命令都支持--output text|json|jsonl和 --json。

身份与能力

massrender auth status
验证凭据,并返回认证来源和 API 地址
massrender whoami
返回当前用户、租户与认证类型
massrender capabilities
返回引擎、质量档位、功能和输出格式

模板与项目

massrender template list
列出当前凭据可用的模板
massrender template get <id-or-key>
按模板 ID 或 key 查询详情
massrender template schema <id-or-key>
输出 inputProps 的 JSON Schema
massrender project list
列出当前租户的渲染项目
massrender project get <id-or-slug>
查询项目、composition 和最新可用版本
massrender project upload [dir]
上传并验证项目,可选注册模板

渲染任务

massrender render create
创建任务后立即返回,适合 Agent 编排
massrender render list
按状态、项目和时间范围分页查询
massrender render get <id>
获取完整任务快照
massrender render wait <id>
轮询至完成、失败、取消或超时
massrender render download <id>
刷新下载 URL,或保存到本地文件
massrender render cancel <id>
请求取消任务(尽力而为,不保证一定停下)
massrender render run
创建并等待完成的一体化交互命令

金额与用量

massrender billing balance
查询预付费余额与累计渲染消费
massrender billing usage
查询月度汇总;--daily 查询最多 90 天逐日明细
massrender billing pricing
查询当前质量档位和每分钟价格
# 余额、价格与最多 90 天逐日用量
massrender billing balance --json
massrender billing pricing --json
massrender billing usage --daily --from 2026-07-01 --to 2026-07-30 --json

# 筛选任务;from/to 必须同时提供且范围不超过一个月
massrender render list --status completed --limit 50 --json
massrender render list --cursor <nextCursor> --json

# 每次创建都会新建一个渲染任务(后扣费,提交不冻结资金)
massrender render create --template data-report --json

机器输出与错误处理

JSON 模式固定返回成功或失败信封。Agent 应先检查进程退出码,再读取ok和 error.retryable,不要解析人类文本。

成功

{
  "ok": true,
  "data": { "renderTaskId": "rt_01J...", "status": "queued" },
  "meta": { "requestId": "...", "timestamp": "..." }
}

失败

{
  "ok": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "请求过于频繁",
    "retryable": true
  },
  "meta": { "requestId": "...", "timestamp": "..." }
}
退出码含义建议
0命令成功继续后续步骤
1CLI 本地错误检查参数、文件和本地环境
2API 拒绝且不可重试修正参数、状态或权限
3未认证或无权限更新 API Key / Token
4可重试错误或等待超时指数退避后重试
5渲染失败或取消读取任务 error 后决定是否新建任务

任务生命周期

accepted→queued→rendering→combining→completed

任务失败时返回 failed 和结构化错误信息。客户端应使用指数退避轮询,并以 renderTaskId 作为幂等与问题排查标识。

模板参数不生效排查(HyperFrames)

编写 HyperFrames 模板 .html 时,如果模板里的 JS 读参数用错了 window 属性名, 会导致「预览里改参数生效、走正式渲染任务出来的视频没生效」的分裂表现。这一节说明成因和唯一需要的一行代码修复。

TL;DR:模板脚本里读变量必须写成三段兜底:
window.__hyperframes?.getVariables?.() || window.__hfVariables || window.__HV_VARS__ || {}

症状:怎么判断踩到了这个坑

  • 在 studio 预览 / 本地 preview 里改参数是生效的;
  • 但走正式 /render-tasks 出来的视频里,文本参数(title / subtitle / 文案 等)没变,还是默认值;
  • 有些颜色参数(accent、bg 等)却生效了——因为它们同时通过 CSS var(--xxx) 走了另一条通道;
  • 后端 API 请求体里 inputProps 的值是对的(浏览器网络面板里能看到改后的值)。

原因:预览和正式渲染注入的名字不一样

@hyperframes/producer 在正式渲染时,会同时做两件事把 inputProps 注入到页面:

通道面向谁注入形式
① 编译期CSS var(--xxx)<style data-hf-composition-variables>:root { --accent: <值>; ... }</style>
② 运行时JS 代码window.__hfVariables = {...}(通过 evaluateOnNewDocument)

而 studio / 本地预览 / ff-server 预览接口为了兼容历史模板,另外挂了两个别名。四个环境注入的 window 属性对照如下:

环境__hyperframes__hfVariables__HV_VARS__
HyperFramesStory 本地预览✓✗✓
MassRender studio 预览✓✗✓
ff-server 预览接口✗✓✓
正式渲染(producer)✗✓✗

如果模板脚本里只读 window.__HV_VARS__(很多老 demo 就是这样写的),正式渲染时 variables 就是空 {},所有 JS 里读的字段就都回退到默认值。 颜色能生效纯属侥幸——它同时借道通道 ① 的 CSS 自定义属性被注入了一遍。

解决方案:一行改动,三段兜底

❌ 错误写法(会踩坑)

// 只读一个别名,正式渲染下拿不到值
const variables = window.__HV_VARS__ || {};

// 更糟:不做保护,__hyperframes 不存在时会 TypeError
const variables = window.__hyperframes.getVariables();

✅ 正确写法(推荐所有模板都用这一段)

// 变量注入通道优先级:
//   1. window.__hyperframes.getVariables()  —— @hyperframes/producer runtime 官方 API
//                                              (sub-composition 作用域 / 未来 runtime 版本)
//   2. window.__hfVariables                —— 当前生产渲染管线 (evaluateOnNewDocument) 注入的名字
//   3. window.__HV_VARS__                  —— HyperFramesStory 预览 shim / MassRender 预览注入的名字
const variables =
  window.__hyperframes?.getVariables?.() ||
  window.__hfVariables ||
  window.__HV_VARS__ ||
  {};

// 后面照常用 variables.title / variables.subtitle / variables.accent ...

完整最小示例

<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta data-composition-id="my-card" data-width="640" data-height="360">
  <style>
    body { margin: 0; background: #07111f; color: #fff; font-family: system-ui, sans-serif; }
    #stage { width: 640px; height: 360px; display: grid; place-content: center; text-align: center; }
    #badge { color: var(--accent); font-weight: 700; letter-spacing: .2em; }
    h1 { color: var(--accent); margin: 0; font-size: 48px; }
    p { color: #bfd7ea; }
  </style>
</head>
<body>
  <main id="stage"
        data-composition-id="my-card"
        data-duration="3"
        data-composition-variables='[
          {"id":"title","type":"string","label":"标题","default":"Hello"},
          {"id":"subtitle","type":"string","label":"副标题","default":"World"},
          {"id":"accent","type":"color","label":"强调色","default":"#38bdf8"}
        ]'>
    <div>
      <div id="badge">BRAND</div>
      <h1 id="title">Hello</h1>
      <p id="subtitle">World</p>
    </div>
  </main>

  <script>
    // ✅ 三段兜底:预览、正式渲染、未来 runtime 都能拿到值
    const variables =
      window.__hyperframes?.getVariables?.() ||
      window.__hfVariables ||
      window.__HV_VARS__ ||
      {};

    // CSS 变量:producer 在 <head> 里已经注入 :root { --accent: <值> },
    // 这一行是给预览环境或旧运行时的兜底,保持写法一致。
    document.documentElement.style.setProperty('--accent', variables.accent || '#38bdf8');
    document.getElementById('title').textContent = variables.title || 'Hello';
    document.getElementById('subtitle').textContent = variables.subtitle || 'World';
  </script>
</body>
</html>

发布前自检清单

  • <script> 里读变量那一行,是否用了推荐的三段兜底?
  • 除了 CSS var(--xxx),所有 JS 读的字段都通过 variables.<id> 取值,且给了 || '默认值' 兜底?
  • 至少跑过一次正式 render(不是预览),确认 title / subtitle / 数组 等纯 JS 驱动的字段确实生效?

只在预览里跑不能证明模板参数在生产是好的——预览和生产的注入名字不一样。

通用 FFmpeg 执行器

除了模板渲染,万象渲染还提供通用的 FFmpeg 执行能力:你只传「外部 URL 输入」+ 「命令数组」+「输出文件名」,服务端把输入取到受控存储后在云函数上执行 ffmpeg。 适用于转码、加水印、截图、媒体信息查询等任意 ffmpeg 任务,无需预先制作模板。

安全模型

  • • ffmpeg 进程永不直连网络,输入由服务端 fetch 到受控存储
  • • 输入 URL 必须为公网 http(s);私网和元数据地址会被拒绝
  • • 命令参数经从严白名单校验(option/filter/值域默认拒绝)
  • • -i 只能引用已声明的输入文件名

异步与会员体系

  • • 创建返回 202 + taskId,执行在云函数上异步进行
  • • 用 GET 轮询拿终态;callbackUrl 回调不保证送达,不应作为唯一判定来源
  • • 不依赖会员订阅,不因套餐过期或调用次数上限拦截
  • • 计费机制以销售合同为准,本文档不承诺具体结算方式

FFmpeg 命令

FFmpeg 子命令在 massrender ffmpeg 命名空间下, 与渲染命令同样支持 --json 机器输出。

FFmpeg 任务

massrender ffmpeg create
创建通用 FFmpeg 任务(异步提交后立即返回 taskId)
massrender ffmpeg get <taskId>
查询任务状态、产物下载 URL 或 ffprobe 结果
massrender ffmpeg wait <taskId>
轮询至完成或失败(--interval / --timeout 控制节奏)

ffmpeg create 参数

参数取值说明
--modeffmpeg | ffprobe执行模式;ffmpeg=转码/处理,ffprobe=媒体信息查询。默认 ffmpeg。
--profilestandard | highmem | gpu规格档位。目前只开放 standard(2 vCPU / 3GB / 600 秒),highmem 与 gpu 尚未开通,传这两个值会被拒绝。
--input"name=url"输入文件声明,可重复;URL 必须为 http(s)。如 --input "main.mp4=https://cdn/a.mp4"。
--argsshell 字符串ffmpeg/ffprobe 参数(支持引号);-i 只能引用 --input 声明的文件名。
--output-file文件名输出文件名(mode=ffmpeg 必填,且 --args 最后一个参数必须指向它)。
--callback-urlURL可选;任务进入终态时 POST 到该 URL 通知。
# 转码:1080p → 720p h264
massrender ffmpeg create \
  --input "main.mp4=https://cdn.example.com/input.mp4" \
  --args "-i main.mp4 -vf scale=-2:720 -c:a aac out.mp4" \
  --output-file out.mp4

# 多输入加水印(--args 支持引号)
massrender ffmpeg create \
  --input "main.mp4=https://cdn.example.com/input.mp4" \
  --input "logo.png=https://cdn.example.com/logo.png" \
  --args "-i main.mp4 -i logo.png -filter_complex [0:v][1:v]overlay=W-w-10:10 -c:a copy out.mp4" \
  --output-file out.mp4

# ffprobe 查询媒体信息(无需 --output-file)
massrender ffmpeg create --mode ffprobe \
  --input "main.mp4=https://cdn.example.com/input.mp4" \
  --args "-v error -print_format json -show_format -show_streams -i main.mp4"

# 轮询至终态并以 JSON 输出
massrender ffmpeg wait <ffmpeg-id> --json

FFmpeg RESTful API

外部服务通过 X-API-KEY或 Bearer Token 访问,认证方式与渲染任务一致。成功创建返回 202,任务异步执行; 用 GET 轮询状态。callbackUrl 回调不重试、不保证送达, 客户端应以 GET 轮询结果为准,不要只依赖回调。

POST/api/v1/ffmpeg-tasks

# ffmpeg 模式:转码
curl -X POST /api/v1/ffmpeg-tasks \
  -H "X-API-KEY: $MASSRENDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "inputs": {"main.mp4": "https://cdn.example.com/input.mp4"},
    "commands": [["-i", "main.mp4", "-c:v", "libx264", "-pix_fmt", "yuv420p", "out.mp4"]],
    "output": {"fileName": "out.mp4"},
    "mode": "ffmpeg",
    "profile": "standard"
  }'

# → 202 Accepted
{"taskId": "ffmpeg-3f0e6a1f-...","status": "queued", "error": null}
# ffprobe 模式:媒体信息查询(无 output 字段)
curl -X POST /api/v1/ffmpeg-tasks \
  -H "X-API-KEY: $MASSRENDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "inputs": {"main.mp4": "https://cdn.example.com/input.mp4"},
    "commands": [["-v","error","-print_format","json","-show_format","-show_streams","-i","main.mp4"]],
    "mode": "ffprobe",
    "profile": "standard"
  }'

GET/api/v1/ffmpeg-tasks/:taskId

curl /api/v1/ffmpeg-tasks/ffmpeg-3f0e6a1f-... \
  -H "X-API-KEY: $MASSRENDER_API_KEY"

{
  "taskId": "ffmpeg-3f0e6a1f-...",
  "status": "completed",
  "finalHttpUrl": "https://signed.example.com/.../out.mp4",
  "output": {
    "sizeInBytes": 60098, "width": 1280, "height": 720,
    "codec": "h264", "durationSeconds": 3.0
  },
  "probeResult": null,
  "error": null, "errorCode": null
}

错误码

HTTPcode含义
400FFMPEG_INPUT_URL_FORBIDDEN输入 URL 协议非 http(s),或指向私网/元数据/localhost 地址
400FFMPEG_COMMAND_URL_FORBIDDENcommands 里嵌入了网络 URL/协议(-i 只能引用已声明的输入文件名)
400FFMPEG_VALIDATION_ERROR请求体或命令校验失败(option/filter 白名单、值域等)
404FFMPEG_TASK_NOT_FOUND任务不存在,或归属校验不通过(任务归属另一租户)
503RENDER_SERVICE_UNAVAILABLE渲染服务暂时不可用,可重试

任务进入 failed 终态时,errorCode字段携带任务级原因(FFMPEG_TIMEOUT、FFMPEG_INPUT_FETCH_FAILED、FFMPEG_EXEC_FAILED 等)。 下方结构化参考由 OpenAPI 规范自动生成。

临时素材(素材库)

除了「外部 URL」输入,万象渲染还提供临时素材能力: 客户先把生产素材(视频、图片等)上传,服务端返回一个内网oss://链接。该链接只能回传给 ffmpeg 任务的 inputs 或渲染任务的inputProps 媒体变量作为消费凭证, 不能当作 HTTP URL 在浏览器直接打开。

生命周期与配额

  • • 素材存活默认 24h(TTL),过期后自动清理,不计入配额
  • • 配额按租户计,默认 10G;上传超过配额返回 409
  • • 单文件默认上限 2G,超过返回 413
  • • 删除素材立即释放配额

安全模型

  • • 素材按租户隔离,跨租户读取或删除一律返回 404
  • • 文件名经服务端 sanitize:路径分隔符、控制字符、保留控制名会被拒绝
  • • oss:// 链接为内网消费凭证,bucket 私有,浏览器不可访问
  • • 上传并发按租户限流,防止单租户占满 worker

素材 RESTful API

通过 X-API-KEY 或 Bearer Token 访问, 认证方式与渲染任务一致。租户由登录态自动注入,客户端无需也无法指定租户。

POST/api/v1/temp-assets

# 上传原始字节(raw body,不要 JSON 包装;文件名建议 percent-encode)
curl -X POST /api/v1/temp-assets \
  -H "X-API-KEY: $MASSRENDER_API_KEY" \
  -H "X-File-Name: hero.mp4" \
  -H "Content-Type: video/mp4" \
  --data-binary @hero.mp4

# → 201 Created
{
  "assetId": "ta_0123456789abcdef0123456789abcdef",
  "fileName": "hero.mp4",
  "sizeBytes": 1048576,
  "contentType": "video/mp4",
  "sha256": "a".repeat(64),
  "url": "oss://framefuture-video/remotion-fc-renders/temp-assets/tenant-xxx/ta_0123.../hero.mp4",
  "expiresAt": "2026-08-07T08:00:00.000Z",
  "quota": {"usedBytes": 1048576, "quotaBytes": 10737418240, "remainingBytes": 10736369664, "assetCount": 1}
}

GET/api/v1/temp-assets · /api/v1/temp-assets/quota

# 列出未过期素材 + 配额
curl /api/v1/temp-assets -H "X-API-KEY: $MASSRENDER_API_KEY"

# 只取配额快照
curl /api/v1/temp-assets/quota -H "X-API-KEY: $MASSRENDER_API_KEY"

GET/DELETE/api/v1/temp-assets/:assetId

# 单素材元数据
curl /api/v1/temp-assets/ta_0123456789abcdef0123456789abcdef -H "X-API-KEY: $MASSRENDER_API_KEY"

# 删除素材并释放配额
curl -X DELETE /api/v1/temp-assets/ta_0123456789abcdef0123456789abcdef \
  -H "X-API-KEY: $MASSRENDER_API_KEY"

# → 200 {"deleted": true, "tenantRef": "tenant-xxx", "quota": {...}}

消费方式

上传返回的 oss:// 链接可回传给:

# FFmpeg 任务:把素材作为输入(inputs 值直接用 oss:// 链接)
curl -X POST /api/v1/ffmpeg-tasks \
  -H "X-API-KEY: $MASSRENDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "inputs": {"main.mp4": "oss://framefuture-video/remotion-fc-renders/temp-assets/tenant-xxx/ta_0123.../hero.mp4"},
    "commands": [["-i", "main.mp4", "-c:v", "libx264", "-pix_fmt", "yuv420p", "out.mp4"]],
    "output": {"fileName": "out.mp4"},
    "mode": "ffmpeg", "profile": "standard"
  }'

错误码

HTTPcode含义
400VALIDATION_ERROR缺/非法租户头、非法文件名、空 body
409TEMP_ASSET_QUOTA_EXCEEDED租户配额超限(默认 10G)
413TEMP_ASSET_FILE_TOO_LARGE单文件超过上限(默认 2G)
404TEMP_ASSET_NOT_FOUND素材不存在,或不属于当前租户
429TEMP_ASSET_UPLOAD_BUSY租户并发上传超过上限,稍后重试
502TEMP_ASSET_UPSTREAM_ERRORff-server 无法到达渲染服务(网络层失败),可重试
503RENDER_SERVICE_UNAVAILABLE渲染服务暂时不可用,可重试

下方结构化参考由 OpenAPI 规范自动生成。

prepare 阶段

很多视频在正式渲染前需要先做一步准备:抽音轨、做语音识别、按文稿打轴、算出字幕分页。 万象渲染把这步交给你自己的代码——项目里声明一个 prepare 入口, 平台在渲染前执行它,把它返回的 props 作为这次渲染的inputProps。平台在这个过程中提供 ffmpeg、 ASR、存储和受限 HTTP 四类调用,怎么组合、分页规则是什么,都由你的代码决定。

任务状态

  • • 提交后平台先跑 prepare,这段时间任务对外是 queued,没有分片进度
  • • prepare 跑完才进入渲染,之后的状态和进度与不含 prepare 的任务一致
  • • prepare 期间不占用渲染并发,只占队列位置
  • • 终态时 usage.prepare 给出这次 prepare 的耗时、ffmpeg/ASR 调用次数、是否命中缓存和 props 体积
  • • 不含 prepare 的项目行为不变

运行约束

  • • 入口是随制品发布的 ESM 文件,不超过 2 MiB;制品里没有 node_modules,依赖要自己打包进文件
  • • 单次预算 900 秒;超时或任务取消时 ctx.signal 中止,进行中的调用一并停止
  • • 平台凭证不会写进你的进程环境,能力调用由平台代为执行;但这层隔离挡不住恶意代码,不要执行来源不可信的 prepare 代码
  • • prepare 按租户开通,未开通时提交直接返回 403 PREPARE_NOT_ENABLED

声明方式随项目类型不同:Remotion 项目写在 package.json 的massrender.prepare 里;HyperFrames 项目写在项目清单的entries[].prepare 里。

// package.json(Remotion)
{
  "massrender": {
    "prepare": "prepare/main.mjs",
    "allowedHosts": ["api.example.com"]
  }
}

// prepare/main.mjs —— 最小骨架
export default async (ctx) => {
  const {video, script, language} = ctx.input;

  // ① 抽音轨
  const audio = await ctx.ffmpeg.run({
    inputs: {src: video.url},
    commands: [['-i', 'src', '-vn', '-c:a', 'aac', '-ar', '16000', '-ac', '1', 'narration.m4a']],
    output: {fileName: 'narration.m4a'},
  });

  // ② 有稿子打轴,没稿子纯识别
  const asr = script
    ? await ctx.asr.align({audio: audio.output.url, text: script, language})
    : await ctx.asr.transcribe({audio: audio.output.url, language});

  // ③ 返回的 props 直接成为渲染 inputProps
  return {
    props: {
      video,
      voiceover: audio.output,
      pages: [{
        text: asr.utterances.map((u) => u.text).join(''),
        start: 0,
        end: asr.audioSeconds ?? 0,
      }],
    },
    usage: {audioSeconds: asr.audioSeconds},
  };
};

能力清单

四类调用的凭据都在平台侧,你的代码只拿到调用入口。每次调用都计入usage,费用以平台侧计数为准。

能力作用要点
ctx.input本次请求的 inputProps另有 ctx.entryId / ctx.renderId / ctx.tenantRef
ctx.ffmpeg.run
ctx.ffmpeg.probe
执行 ffmpeg 命令 / 只取媒体信息输入为逻辑名 → URL,参数走服务端白名单;probe 不产出 URL
ctx.asr.align
ctx.asr.transcribe
按文稿对齐 / 纯识别,返回词级时间戳需要提供方可访问的公网音频地址,不建议直接传视频地址
ctx.storage.putJson
ctx.storage.getJson
按租户隔离的 JSON 键值存储,用来跨次运行保留昂贵中间结果(打轴、转写等)仅 JSON、单对象 ≤ 8 MiB、键为相对路径;只能读写本租户的数据,没有列举/删除接口,也不会自动过期——同一 key 重复写即覆盖
ctx.http.fetchGET 外部接口域名必须在声明里白名单化,未声明则完全禁用
ctx.log
ctx.signal
写日志 / 感知取消与超时长任务应监听 signal 提前收尾,避免被强制终止
关于 ctx.storage:它是给你自己的代码用的缓存, 不是素材库(临时素材)。存的是 JSON 而不是文件: 写对象用 putJson(key, value),读回getJson(key) 得原值、没有则null。数据按租户隔离,其他租户读不到;单次运行最多 4 个并发调用排队,超出即等待,等待可被ctx.signal 中止。视频、音频这类大文件请走 ffmpeg 输出或素材库,storage 不支持。
关于输出地址:ffmpeg 产出的 output.url形态由引擎决定——Remotion 为有效期 7 天的预签名 HTTPS,HyperFrames 为默认 24 小时有效的租户临时素材oss://。写进 props 的应当是url 而不是objectKey,渲染阶段才能直接消费。

返回值与缓存

return {
  props,                               // 必填:这次渲染的 inputProps
  cacheable: false,                    // 可选:是否允许平台缓存本次结果
  usage: {audioSeconds: 42.5},         // 可选:仅为提示,计费以平台计数为准
};
  • • props 必须满足 entry 的 inputSchema。该 schema 由项目的默认参数反推,且不允许额外字段。prepare 多返回一个 key 就会判为PREPARE_OUTPUT_INVALID,所以默认参数要覆盖 prepare 会返回的全部顶层字段(例如字幕组件返回 voiceover,默认参数里就要有这个 key)。
  • • 默认不缓存。设 cacheable: true 才按「制品 + prepare 路径 + entry + 输入」缓存 30 天; 但产出过 output.url 的那次运行永不缓存(URL 有效期远短于缓存)。 要复用昂贵计算,用 ctx.storage。
  • • ASR 结果平台另有缓存(默认 30 天),命中时不再向识别服务提交,但仍按命中时长计费—— 缓存省的是延迟和上游配额,不是这笔费用。
    命中条件很严格:缓存键包含音频地址本身。每次用ctx.ffmpeg.run 重新抽音轨都会得到新地址, 即使音频内容相同也不会命中。要复用结果,二选一:把音频地址固定下来 (每轮都指向同一个稳定地址,而不是重新抽),或用 ctx.storage 自己按内容缓存。
  • • props 超过 1 MiB 时平台会把它存到对象存储,任务详情里只保留引用。

错误码与排查

prepare 阶段的失败会让任务以 failed 结束,回调render.failed 的error.stage 为prepare,error.code 是下表之一。查询任务时读error.code 判断原因。

错误码含义处理
PREPARE_NOT_ENABLED租户未开通 prepare,提交即被拒绝联系平台开通;或提交一个未声明 prepare 的项目版本(平台不会跳过 prepare 继续渲染)
PREPARE_INPUT_INVALID传给平台的参数不合法:ffmpeg 的输入名/地址/输出名、ASR 缺音频或文稿、storage 的键,或 http.fetch 的请求本身对照「能力清单」里对应那一行的要求逐项检查。域名不在白名单报的是 MEDIA_UNREACHABLE,不是这个码
PREPARE_ENTRY_NOT_FOUND声明了 prepare,但制品里没有这个文件确认构建把入口打进了发布产物
PREPARE_CODE_FAILED代码抛错或没有返回结果看任务日志里 ctx.log 的输出和错误尾部
PREPARE_OUTPUT_INVALIDprops 不满足 entry 的 inputSchema补齐默认参数里的字段,去掉 schema 之外的多余 key
PREPARE_LIMIT_EXCEEDED超出内存、体积或并发类上限,不可重试减少一次性载入的数据量,分次写入
PREPARE_TIMEOUT900 秒预算耗尽,或任务被取消减少 ffmpeg/ASR 调用次数;ASR 结果会缓存,重跑通常更快
PREPARE_SANDBOX_UNAVAILABLE执行环境异常,非你的代码问题可重试;持续出现请联系平台
FFMPEG_TASK_FAILEDffmpeg 执行失败(含输入地址返回 4xx)确认输入地址可被服务端访问、命令参数在白名单内
MEDIA_UNREACHABLE输入不可达,或域名不在白名单改用公网可达地址;把域名加进 allowedHosts
ASR_PROVIDER_FAILED语音识别上游失败可重试;持续失败请联系平台

一个容易误判的场景:用命令行工具生成预签名地址做调试,如果工具把额外的计时行混进了输出,或把路径里的/ 编码成%2F,对象存储会返回 403SignatureDoesNotMatch,但在任务里看到的却是FFMPEG_TASK_FAILED: input URL returned HTTP 403。 判断方法:同一串地址用 curl 能取到、换另一个 HTTP 客户端却 403,问题在地址本身,不在权限。

FC 实现

使用阿里云函数计算 custom-container 运行时。编排器读取 composition metadata,并根据帧数动态拆分 chunk;多个 Worker 分片渲染,Combiner 使用 FFmpeg 合并最终 MP4。

无状态执行

bundle、job.json、chunk 和最终视频都保存在 OSS,函数实例可以弹性创建和回收。

动态并发

并发来自多个 FC Worker invocation;单个 Worker 内部保持顺序渲染,减少资源竞争。

浏览器复用

温实例复用 Chromium 进程,只为每次渲染创建页面,降低重复启动浏览器的固定开销。

可重入分片

稳定对象键和完成状态检查支持分片重试,Combiner 会根据 OSS 对象校正部分状态。

企业级 FC 自部署

对于要求数据不离开企业云账号、需要连接内网素材或希望独立管理资源成本的客户,万象渲染 可以协助将 Remotion FC 渲染链路部署到客户自己的阿里云环境。

企业业务系统
→
企业 API 网关
→
FC Orchestrator
→
FC Workers × N
→
企业 OSS

部署范围

FC custom-container、ACR 镜像、OSS 路径、VPC 网络、API 接入、日志与基础监控。

交付服务

环境评估、容量规划、部署验证、使用培训、版本升级和约定周期的技术支持。

价格:商务报价

通常由一次性实施费用与年度支持服务组成,阿里云资源费由企业自行承担。

咨询自部署

生产接入建议

  • 业务后端负责保存 API Key,前端不直接调用渲染接口。
  • 为每个项目使用不可变版本,发布新版本时保留回滚能力。
  • 对提交接口设置租户配额、并发限制和输入参数校验。
  • Webhook 接收方校验签名,并按 renderTaskId 实现幂等处理。
  • 长耗时任务使用异步状态,不要保持同步 HTTP 连接等待视频完成。

准备接入你的第一个项目?

联系我们评估项目类型、运行依赖、并发目标和结果交付方式。