Config Tracker
自动检测并提交 OpenClaw 配置与工作区文件变更,实现零手动操作。
下载 642
通过 RenderIO API 实现云端视频、音频、图片的批量处理,支持转换、压缩、裁剪等操作。
openclaw skills install @nevermind-s/renderio-ffmpeg-api命令、参数、文件名以原文为准
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 密钥。
{{in_video}} 而非 {in_video}in_ 开头,输出键以 out_ 开头// 正确示例
{
"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" }
}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-..." }
curl https://renderio.dev/api/v1/commands/$COMMAND_ID \
-H "X-API-KEY: $RENDERIO_API_KEY"状态值:QUEUED → PROCESSING → SUCCESS 或 FAILED(始终大写)。
result.output_files.out_video.storage_urlconst 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);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"])可直接复制粘贴。替换 URL 和文件名即可。
{
"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" }
}{
"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" }
}{
"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" }
}{
"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" }
}{
"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" }
}{
"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" }
}
]
}# 配置一次
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_id 或 file_id 是否正确 |
500 | 服务器内部错误 | 可安全延迟后重试 |
RENDERIO_API_KEY 存储在环境变量中,不硬编码{{双大括号}}in_ 开头,输出键名以 out_ 开头QUEUED、PROCESSING、SUCCESS、FAILED 状态output_files.out_key.storage_url 获取输出文件地址FAILED 状态的异常情况api.renderio.dev(不使用其他域名)已收录 1 个 Skill