RenderIO ffmpeg

通过 RenderIO API 实现云端视频、音频、图片的批量处理,支持转换、压缩、裁剪等操作。

已扫描
适合谁
需要批量处理视频的创作者、开发自动化媒体工作流的技术人员
不适合谁
无网络环境或无法访问外部 API 的用户、希望在本地完全离线处理文件的用户
国内可用性
需网络配置。可能需要网络配置或第三方服务可访问。
安装难度
新手友好(★☆☆)。基于终端操作、依赖、API Key 和本地环境要求的初步判断。

安装与下载

openclaw skills install @nevermind-s/renderio-ffmpeg-api

Skill 说明

命令、参数、文件名以原文为准

RenderIO API Skill

RenderIO 是一个基于 FFmpeg 的云服务 REST API。您通过 HTTP 发送 FFmpeg 命令,RenderIO 会在安全的云端沙箱中执行该命令,自动存储输出结果,并返回带签名的下载链接。

设置

# 将 API 密钥设置为环境变量
export RENDERIO_API_KEY="ffsk_your_api_key_here"

在 [renderio.dev/get-api-key](https://renderio.dev/get-api-key) 获取免费 API 密钥。

必须严格遵守的三条规则

  1. 占位符使用双大括号 —— 使用 {{in_video}} 而非 {in_video}
  2. 键名前缀规范 —— 输入键以 in_ 开头,输出键以 out_ 开头
  3. 所有命令中使用的键必须声明,且每个声明的键都必须出现在命令中
// 正确示例
{
  "ffmpeg_command": "-i {{in_video}} -c:v libx264 {{out_video}}",
  "input_files": { "in_video": "https://example.com/video.mp4" },
  "output_files": { "out_video": "result.mp4" }
}

// 错误示例 —— 单大括号,缺少 out_ 前缀
{
  "ffmpeg_command": "-i {video} result.mp4",
  "input_files": { "video": "https://example.com/video.mp4" }
}

核心工作流程

1. 提交命令

curl -X POST https://renderio.dev/api/v1/run-ffmpeg-command \
  -H "X-API-KEY: $RENDERIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ffmpeg_command": "-i {{in_video}} -vf scale=1280:720 -c:v libx264 -crf 23 {{out_video}}",
    "input_files": { "in_video": "https://example.com/input.mp4" },
    "output_files": { "out_video": "output-720p.mp4" }
  }'

响应:{ "command_id": "a1b2c3d4-..." }

2. 轮询任务状态

curl https://renderio.dev/api/v1/commands/$COMMAND_ID \
  -H "X-API-KEY: $RENDERIO_API_KEY"

状态值:QUEUEDPROCESSINGSUCCESSFAILED(始终大写)。

3. 获取输出文件下载地址

result.output_files.out_video.storage_url

TypeScript 实现

const API_KEY = process.env.RENDERIO_API_KEY!;
const BASE = "https://api.renderio.dev";

interface CommandResult {
  command_id: string;
  status: "QUEUED" | "PROCESSING" | "SUCCESS" | "FAILED";
  output_files: Record<string, {
    storage_url: string;
    filename: string;
    size_mbytes: number;
    duration?: number;
    codec?: string;
    width?: number;
    height?: number;
  }>;
  total_processing_seconds?: number;
  error?: string;
}

async function runFFmpeg(
  command: string,
  inputFiles: Record<string, string>,
  outputFiles: Record<string, string>,
): Promise<CommandResult> {
  const submitRes = await fetch(`${BASE}/api/v1/run-ffmpeg-command`, {
    method: "POST",
    headers: { "X-API-KEY": API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({
      ffmpeg_command: command,
      input_files: inputFiles,
      output_files: outputFiles,
    }),
  });

  if (!submitRes.ok) {
    const err = await submitRes.json();
    throw new Error(`提交失败: ${err.message}`);
  }

  const { command_id } = await submitRes.json();

  // 每 2 秒轮询一次
  while (true) {
    await new Promise((r) => setTimeout(r, 2000));

    const pollRes = await fetch(`${BASE}/api/v1/commands/${command_id}`, {
      headers: { "X-API-KEY": API_KEY },
    });
    const result: CommandResult = await pollRes.json();

    if (result.status === "SUCCESS") return result;
    if (result.status === "FAILED") {
      throw new Error(`处理失败: ${result.error ?? "未知原因"}`);
    }
  }
}

// 使用示例
const result = await runFFmpeg(
  "-i {{in_video}} -vf scale=1280:720 -c:v libx264 -crf 23 {{out_video}}",
  { in_video: "https://example.com/input.mp4" },
  { out_video: "output-720p.mp4" },
);

console.log(result.output_files.out_video.storage_url);

Python 实现

import os
import time
import requests
from typing import Any

API_KEY = os.environ["RENDERIO_API_KEY"]
BASE = "https://api.renderio.dev"

def run_ffmpeg(
    command: str,
    input_files: dict[str, str],
    output_files: dict[str, str],
) -> dict[str, Any]:
    res = requests.post(
        f"{BASE}/api/v1/run-ffmpeg-command",
        headers={"X-API-KEY": API_KEY},
        json={
            "ffmpeg_command": command,
            "input_files": input_files,
            "output_files": output_files,
        },
    )
    res.raise_for_status()
    command_id = res.json()["command_id"]

    while True:
        time.sleep(2)
        result = requests.get(
            f"{BASE}/api/v1/commands/{command_id}",
            headers={"X-API-KEY": API_KEY},
        ).json()

        if result["status"] == "SUCCESS":
            return result
        if result["status"] == "FAILED":
            raise RuntimeError(f"处理失败: {result.get('error', '未知原因')}")

# 使用示例
result = run_ffmpeg(
    "-i {{in_video}} -vf scale=1280:720 -c:v libx264 -crf 23 {{out_video}}",
    {"in_video": "https://example.com/input.mp4"},
    {"out_video": "output-720p.mp4"},
)
print(result["output_files"]["out_video"]["storage_url"])

常用 FFmpeg 配方

可直接复制粘贴。替换 URL 和文件名即可。

将 MP4 转换为 WebM

{
  "ffmpeg_command": "-i {{in_video}} -c:v libvpx-vp9 -crf 30 -b:v 0 {{out_video}}",
  "input_files": { "in_video": "https://example.com/input.mp4" },
  "output_files": { "out_video": "output.webm" }
}

将视频缩放至 720p

{
  "ffmpeg_command": "-i {{in_video}} -vf scale=1280:720 -c:v libx264 -crf 23 {{out_video}}",
  "input_files": { "in_video": "https://example.com/input.mp4" },
  "output_files": { "out_video": "720p.mp4" }
}

提取音频为 MP3

{
  "ffmpeg_command": "-i {{in_video}} -vn -acodec libmp3lame -ab 192k {{out_audio}}",
  "input_files": { "in_video": "https://example.com/input.mp4" },
  "output_files": { "out_audio": "audio.mp3" }
}

压缩视频(减小文件大小)

{
  "ffmpeg_command": "-i {{in_video}} -c:v libx264 -crf 28 -preset slow {{out_video}}",
  "input_files": { "in_video": "https://example.com/input.mp4" },
  "output_files": { "out_video": "compressed.mp4" }
}

在第5秒生成缩略图

{
  "ffmpeg_command": "-i {{in_video}} -ss 5 -vframes 1 {{out_thumb}}",
  "input_files": { "in_video": "https://example.com/input.mp4" },
  "output_files": { "out_thumb": "thumbnail.jpg" }
}

裁剪视频(从第10秒到第30秒)

{
  "ffmpeg_command": "-i {{in_video}} -ss 10 -to 30 -c copy {{out_video}}",
  "input_files": { "in_video": "https://example.com/input.mp4" },
  "output_files": { "out_video": "trimmed.mp4" }
}

添加图片水印

{
  "ffmpeg_command": "-i {{in_video}} -i {{in_logo}} -filter_complex \"overlay=10:10\" {{out_video}}",
  "input_files": {
    "in_video": "https://example.com/input.mp4",
    "in_logo": "https://example.com/logo.png"
  },
  "output_files": { "out_video": "watermarked.mp4" }
}

转换为 GIF(宽度 480px,帧率 12fps)

{
  "ffmpeg_command": "-i {{in_video}} -vf \"fps=12,scale=480:-1:flags=lanczos\" {{out_gif}}",
  "input_files": { "in_video": "https://example.com/input.mp4" },
  "output_files": { "out_gif": "output.gif" }
}

静音视频(移除音频轨道)

{
  "ffmpeg_command": "-i {{in_video}} -an -c:v copy {{out_video}}",
  "input_files": { "in_video": "https://example.com/input.mp4" },
  "output_files": { "out_video": "muted.mp4" }
}

将两个视频并排叠加

{
  "ffmpeg_command": "-i {{in_left}} -i {{in_right}} -filter_complex \"[0:v][1:v]hstack=inputs=2\" {{out_video}}",
  "input_files": {
    "in_left": "https://example.com/left.mp4",
    "in_right": "https://example.com/right.mp4"
  },
  "output_files": { "out_video": "side-by-side.mp4" }
}

先上传本地文件

当用户拥有本地文件(而非 URL)时,需先上传:

async function uploadFile(filePath: string): Promise<string> {
  const form = new FormData();
  form.append("file", new Blob([await fs.readFile(filePath)]), path.basename(filePath));

  const res = await fetch("https://renderio.dev/api/v1/files/upload", {
    method: "POST",
    headers: { "X-API-KEY": API_KEY },
    body: form,
  });

  const data = await res.json();
  return data.storage_url; // 使用此值作为 input_files 的值
}

链式命令(顺序处理流程)

使用前一步的输出作为下一步的输入。在后续步骤的 input_files 中通过 {{out_key}} 引用前一步的输出。

POST /api/v1/run-chained-ffmpeg-commands
{
  "commands": [
    {
      "ffmpeg_command": "-i {{in_video}} -vf scale=1280:720 {{out_resized}}",
      "input_files": { "in_video": "https://example.com/input.mp4" },
      "output_files": { "out_resized": "resized.mp4" }
    },
    {
      "ffmpeg_command": "-i {{in_resized}} -c:v libx264 -crf 28 {{out_final}}",
      "input_files": { "in_resized": "{{out_resized}}" },
      "output_files": { "out_final": "final.mp4" }
    }
  ]
}

并行命令(多个独立操作)

POST /api/v1/run-multiple-ffmpeg-commands
{
  "commands": [
    {
      "ffmpeg_command": "-i {{in_video}} -vf scale=1920:1080 {{out_1080p}}",
      "input_files": { "in_video": "https://example.com/input.mp4" },
      "output_files": { "out_1080p": "1080p.mp4" }
    },
    {
      "ffmpeg_command": "-i {{in_video}} -vf scale=1280:720 {{out_720p}}",
      "input_files": { "in_video": "https://example.com/input.mp4" },
      "output_files": { "out_720p": "720p.mp4" }
    }
  ]
}

Webhook 设置(替代轮询)

# 配置一次
curl -X PUT https://renderio.dev/api/v1/webhook-config \
  -H "X-API-KEY: $RENDERIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://your-server.com/renderio-webhook"}'

# 你的端点接收到的 payload:
# {
#   "data": {
#     "command_id": "...",
#     "status": "SUCCESS",
#     "output_files": { "out_video": { "storage_url": "...", ... } }
#   },
#   "timestamp": 1712345678000
# }

错误参考

错误码含义解决方法
400请求错误检查占位符语法和键名前缀
401认证失败检查 X-API-KEY 头部和密钥有效性
429请求频率超限等待 Retry-After 指定的秒数后重试
404找不到资源检查 command_idfile_id 是否正确
500服务器内部错误可安全延迟后重试

生成代码时的检查清单

  • [ ] RENDERIO_API_KEY 存储在环境变量中,不硬编码
  • [ ] 占位符语法使用 {{双大括号}}
  • [ ] 输入键名以 in_ 开头,输出键名以 out_ 开头
  • [ ] 轮询循环处理 QUEUEDPROCESSINGSUCCESSFAILED 状态
  • [ ] 通过 output_files.out_key.storage_url 获取输出文件地址
  • [ ] 处理 HTTP 错误和 FAILED 状态的异常情况
  • [ ] 域名为 api.renderio.dev(不使用其他域名)
NS
@nevermind-s

已收录 1 个 Skill

相关推荐