图生视频:基于参考图生成视频任务
curl --request POST \
--url https://api.apiyi.com/v1/videos \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form model=sora-2 \
--form 'prompt=Animate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in' \
--form input_reference='@example-file'import requests
url = "https://api.apiyi.com/v1/videos"
files = { "input_reference": ("example-file", open("example-file", "rb")) }
payload = {
"model": "sora-2",
"prompt": "Animate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('model', 'sora-2');
form.append('prompt', 'Animate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in');
form.append('input_reference', '{
"fileName": "example-file"
}');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.apiyi.com/v1/videos', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.apiyi.com/v1/videos",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.apiyi.com/v1/videos"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.apiyi.com/v1/videos")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.apiyi.com/v1/videos")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "video_abc123def456",
"object": "video",
"model": "sora-2",
"status": "queued",
"progress": 0,
"created_at": 1712697600,
"completed_at": 1712697900,
"size": "1280x720",
"seconds": "8",
"quality": "standard"
}Sora 2(OpenAI)
圖生影片 API 參考
Sora 2 圖生影片 API 參考與線上除錯 — multipart 上傳 input_reference 參考圖,讓靜態畫面動起來。
POST
/
v1
/
videos
图生视频:基于参考图生成视频任务
curl --request POST \
--url https://api.apiyi.com/v1/videos \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form model=sora-2 \
--form 'prompt=Animate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in' \
--form input_reference='@example-file'import requests
url = "https://api.apiyi.com/v1/videos"
files = { "input_reference": ("example-file", open("example-file", "rb")) }
payload = {
"model": "sora-2",
"prompt": "Animate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('model', 'sora-2');
form.append('prompt', 'Animate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in');
form.append('input_reference', '{
"fileName": "example-file"
}');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.apiyi.com/v1/videos', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.apiyi.com/v1/videos",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.apiyi.com/v1/videos"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.apiyi.com/v1/videos")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.apiyi.com/v1/videos")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nsora-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nAnimate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"input_reference\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "video_abc123def456",
"object": "video",
"model": "sora-2",
"status": "queued",
"progress": 0,
"created_at": 1712697600,
"completed_at": 1712697900,
"size": "1280x720",
"seconds": "8",
"quality": "standard"
}右側的互動式 Playground 支援直接線上除錯。請在 Authorization 中填入你的 API Key(格式:
Bearer sk-xxx),上傳一張參考圖、輸入 prompt、選擇 model / size / seconds 後一鍵傳送即可。場景說明:本頁用於「基於參考圖生成影片」——上傳一張圖片作為影片的起始幀/視覺錨點,讓靜態畫面”動起來”。如果不需要參考圖,請使用 文生影片介面(同一端點,JSON 請求體)。
⚠️ 參考圖解析度必須與 size 完全一致
- 上傳圖片的畫素尺寸必須與請求的
size欄位完全一致(如size=1280x720則圖片必須是 1280×720) - 不一致會返回 400:
Inpaint image must match the requested width and height - 建議先用 ffmpeg / Pillow 在本地裁切到目標尺寸再上傳
- Content-Type 必須是
multipart/form-data(不是 JSON) - 僅支援 1 個檔案,欄位名固定
input_reference - 接受格式:
image/jpeg/image/png/image/webp
程式碼示例
Python(OpenAI SDK 直連)
from openai import OpenAI
import time
client = OpenAI(
api_key="sk-your-api-key",
base_url="https://api.apiyi.com/v1"
)
# 第 1 步:提交(OpenAI SDK 用 input_reference 引數自動走 multipart)
with open("./reference.png", "rb") as f:
video = client.videos.create(
model="sora-2",
prompt="Animate this scene: gentle waves lapping against the shore, leaves swaying in the breeze",
seconds="8",
size="1280x720",
input_reference=f
)
print(f"Video ID: {video.id}, status: {video.status}")
# 第 2 步:輪詢
while True:
video = client.videos.retrieve(video.id)
print(f"Status: {video.status}, progress: {getattr(video, 'progress', 0)}%")
if video.status == "completed":
break
if video.status == "failed":
raise RuntimeError(f"Generation failed: {video}")
time.sleep(15)
# 第 3 步:下載
client.videos.download_content(video.id).write_to_file("output.mp4")
print("Saved: output.mp4")
Python(原生 requests + multipart)
import requests
import time
API_KEY = "sk-your-api-key"
BASE_URL = "https://api.apiyi.com/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# 第 1 步:multipart 上傳(注意:image 解析度必須等於 size)
with open("./reference.png", "rb") as f:
resp = requests.post(
f"{BASE_URL}/videos",
headers=HEADERS, # 不要手動加 Content-Type,requests 會自動處理 multipart 邊界
data={
"model": "sora-2",
"prompt": "Animate this scene with cinematic camera push-in, soft golden hour lighting",
"seconds": "8",
"size": "1280x720"
},
files={
"input_reference": ("reference.png", f, "image/png")
},
timeout=60 # multipart 上傳大圖可能慢,超時建議 60 秒
).json()
video_id = resp["id"]
print(f"Video ID: {video_id}, status: {resp['status']}")
# 第 2 步:輪詢
deadline = time.time() + 900
while time.time() < deadline:
status_resp = requests.get(f"{BASE_URL}/videos/{video_id}", headers=HEADERS).json()
print(f"Status: {status_resp['status']}, progress: {status_resp.get('progress', 0)}%")
if status_resp["status"] == "completed":
break
if status_resp["status"] == "failed":
raise RuntimeError(f"Generation failed: {status_resp}")
time.sleep(15)
# 第 3 步:下載
with requests.get(f"{BASE_URL}/videos/{video_id}/content", headers=HEADERS, stream=True) as r:
r.raise_for_status()
with open("output.mp4", "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
print("Saved: output.mp4")
cURL
{/* 第 1 步:multipart 上傳 + 提交任務 */}
curl -X POST "https://api.apiyi.com/v1/videos" \
-H "Authorization: Bearer sk-your-api-key" \
-F "model=sora-2" \
-F "prompt=Animate this scene: gentle waves lapping, leaves swaying, cinematic" \
-F "seconds=8" \
-F "size=1280x720" \
-F "input_reference=@./reference.png;type=image/png"
{/* 第 2 步:輪詢 */}
curl -X GET "https://api.apiyi.com/v1/videos/video_abc123" \
-H "Authorization: Bearer sk-your-api-key"
{/* 第 3 步:下載 */}
curl -X GET "https://api.apiyi.com/v1/videos/video_abc123/content" \
-H "Authorization: Bearer sk-your-api-key" \
-o output.mp4
Node.js(原生 fetch + FormData)
import fs from 'node:fs';
import { fileFromPath } from 'formdata-node/file-from-path';
import { FormData } from 'formdata-node';
const API_KEY = 'sk-your-api-key';
const BASE_URL = 'https://api.apiyi.com/v1';
// 第 1 步:multipart 上傳
const form = new FormData();
form.set('model', 'sora-2');
form.set('prompt', 'Animate this scene with cinematic camera push-in, soft lighting');
form.set('seconds', '8');
form.set('size', '1280x720');
form.set('input_reference', await fileFromPath('./reference.png'));
const submitResp = await fetch(`${BASE_URL}/videos`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${API_KEY}` }, // 不要手動加 Content-Type
body: form
});
const { id: videoId } = await submitResp.json();
console.log(`Video ID: ${videoId}`);
// 第 2 步:輪詢
let status = 'queued';
while (status !== 'completed' && status !== 'failed') {
await new Promise(r => setTimeout(r, 15000));
const data = await (await fetch(`${BASE_URL}/videos/${videoId}`, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
})).json();
status = data.status;
console.log(`Status: ${status}, progress: ${data.progress ?? 0}%`);
}
if (status === 'failed') throw new Error('Generation failed');
// 第 3 步:下載
const contentResp = await fetch(`${BASE_URL}/videos/${videoId}/content`, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
fs.writeFileSync('output.mp4', Buffer.from(await contentResp.arrayBuffer()));
console.log('Saved: output.mp4');
瀏覽器 JavaScript
{/* 僅作演示,生產請走後端代理避免 Key 洩露 */}
const fileInput = document.getElementById('refImage'); // <input type="file" />
const file = fileInput.files[0];
const form = new FormData();
form.append('model', 'sora-2');
form.append('prompt', 'Animate this scene, gentle motion');
form.append('seconds', '4');
form.append('size', '1280x720');
form.append('input_reference', file);
const submitResp = await fetch('https://api.apiyi.com/v1/videos', {
method: 'POST',
headers: { 'Authorization': 'Bearer sk-your-api-key' },
body: form
});
const { id } = await submitResp.json();
console.log('Video ID:', id);
{/* 輪詢完成後,建議把影片 URL 交給後端代理處理,避免大檔案直接在瀏覽器下載 */}
引數說明速查
| 引數 | 型別 | 必填 | 預設 | 說明 |
|---|---|---|---|---|
model | string | 是 | — | sora-2(720p 標準)或 sora-2-pro(720p / 1024p / 1080p 多檔) |
prompt | string | 是 | — | 影片描述提示詞,建議描述”如何讓畫面動起來”(鏡頭運動、物體動作、光線變化) |
seconds | string | 否 | "4" | 影片時長,字串列舉:"4" / "8" / "12" |
size | string | 否 | 720x1280 | 輸出解析度,必須與 input_reference 圖片畫素完全一致 |
input_reference | file | 是 | — | 參考圖檔案,image/jpeg / image/png / image/webp,畫素必須等於 size |
詳細的引數約束、可選值、示例請檢視右側 Playground 中的欄位說明。
input_reference 欄位必須通過 multipart 檔案上傳,不接受 URL 或 base64。參考圖準備建議
1
確定目標解析度
根據用途先選
size:豎屏 720x1280、橫屏 1280x720、Pro 1080p 橫屏 1920x1080 等。2
本地裁切到精確畫素
用 Pillow / ffmpeg 把圖片裁切到目標尺寸:或 ffmpeg 一行:
from PIL import Image
img = Image.open("source.jpg")
img = img.resize((1280, 720), Image.LANCZOS) # 或先 crop 再 resize 保留比例
img.save("reference.png")
ffmpeg -i source.jpg -vf "scale=1280:720" reference.png
3
選合適的圖片格式
優先 PNG(無損,適合插畫 / 截圖),照片類用 JPEG 節省體積,需要透明通道用 WebP。
4
prompt 聚焦「動作」而不是「畫面」
參考圖已經定了畫面,prompt 應該重點描述如何讓它動起來:鏡頭推拉、物體運動、光線變化、人物表情等。例:
"Camera slowly pushes in, leaves gently swaying, sunlight flickering through branches"。響應格式
響應結構與 文生影片 完全相同:提交返回id + status: "queued",輪詢返回進度,完成後通過 /v1/videos/{id}/content 下載 MP4。
{
"id": "video_abc123def456",
"object": "video",
"model": "sora-2",
"status": "queued",
"progress": 0,
"created_at": 1712697600,
"size": "1280x720",
"seconds": "8",
"quality": "standard"
}
⚠️ 常見 400 錯誤
Inpaint image must match the requested width and height—— 參考影像素與size不一致,最常見。客戶端做好上傳前的尺寸校驗Invalid file format—— 上傳的不是 jpeg / png / webp,或檔案損壞Missing required parameter: input_reference—— multipart 欄位名拼錯(必須是input_reference,不是image或reference)seconds must be one of "4", "8", "12"—— 傳了數字4而非字串"4"
圖生影片與文生影片單價相同(按
seconds 計費),並不會因為多上傳了一張參考圖額外收費。詳見 概覽頁定價表。授權
在 API易控制台获取的 API Key(必须配置 Sora2官转 分组 + 按量计费)
主體
multipart/form-data
模型 ID。sora-2 仅支持 720p;sora-2-pro 支持 720p / 1024p / 1080p
可用選項:
sora-2, sora-2-pro 视频生成提示词。重点描述如何让画面动起来:镜头运动、物体动作、光线变化
範例:
"Animate this scene: gentle waves lapping, leaves swaying, cinematic camera push-in"
参考图文件,作为视频的起始帧/视觉锚点。
- 接受格式:
image/jpeg/image/png/image/webp - 像素必须等于
size,否则报错Inpaint image must match the requested width and height - 仅支持 1 个文件,字段名固定
input_reference
视频时长,字符串枚举:"4" / "8" / "12"
可用選項:
4, 8, 12 输出分辨率,必须与 input_reference 图片像素完全一致:
sora-2(仅 720p):720x1280/1280x720sora-2-pro额外:1024x1792/1792x1024/1080x1920/1920x1080
可用選項:
720x1280, 1280x720, 1024x1792, 1792x1024, 1080x1920, 1920x1080 回應
任务已提交,返回 video_id 与 queued 状态
任务 ID,用于后续轮询和下载
範例:
"video_abc123def456"
对象类型,固定 video
範例:
"video"
本次任务使用的模型 ID
範例:
"sora-2"
任务状态:
queued—— 已提交,排队等待in_progress—— 正在生成completed—— 完成,可下载(/v1/videos/{id}/content)failed—— 失败(不计费),可重试
可用選項:
queued, in_progress, completed, failed 範例:
"queued"
生成进度百分比(0–100),不严格线性
範例:
0
任务创建 Unix 时间戳(秒)
範例:
1712697600
任务完成 Unix 时间戳(秒),仅 completed 状态返回
範例:
1712697900
实际输出分辨率(与请求的 size 一致)
範例:
"1280x720"
实际生成时长(与请求的 seconds 一致)
範例:
"8"
画质档位(standard 对应 sora-2,high 对应 sora-2-pro)
範例:
"standard"
這個頁面有幫助嗎?
⌘I