> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apiyi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 图生视频 API 参考

> Sora 2 图生视频 API 参考与在线调试 — multipart 上传 input_reference 参考图，让静态画面动起来。

<Info>
  右侧的交互式 Playground 支持直接在线调试。请在 **Authorization** 中填入你的 API Key（格式：`Bearer sk-xxx`），上传一张参考图、输入 prompt、选择 model / size / seconds 后一键发送即可。
</Info>

<Tip>
  **场景说明**：本页用于「基于参考图生成视频」——上传一张图片作为视频的起始帧/视觉锚点，让静态画面"动起来"。如果不需要参考图，请使用 [文生视频接口](/api-capabilities/sora-2/text-to-video)（同一端点，JSON 请求体）。
</Tip>

<Warning>
  **⚠️ 参考图分辨率必须与 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`
</Warning>

## 代码示例

### Python（OpenAI SDK 直连）

```python theme={null}
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）

```python theme={null}
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

```bash theme={null}
{/* 第 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）

```javascript theme={null}
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

```javascript theme={null}
{/* 仅作演示，生产请走后端代理避免 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`** |

<Tip>
  详细的参数约束、可选值、示例请查看右侧 Playground 中的字段说明。**`input_reference` 字段必须通过 multipart 文件上传**，不接受 URL 或 base64。
</Tip>

## 参考图准备建议

<Steps>
  <Step title="确定目标分辨率">
    根据用途先选 `size`：竖屏 `720x1280`、横屏 `1280x720`、Pro 1080p 横屏 `1920x1080` 等。
  </Step>

  <Step title="本地裁切到精确像素">
    用 Pillow / ffmpeg 把图片裁切到目标尺寸：

    ```python theme={null}
    from PIL import Image
    img = Image.open("source.jpg")
    img = img.resize((1280, 720), Image.LANCZOS)  # 或先 crop 再 resize 保留比例
    img.save("reference.png")
    ```

    或 ffmpeg 一行：

    ```bash theme={null}
    ffmpeg -i source.jpg -vf "scale=1280:720" reference.png
    ```
  </Step>

  <Step title="选合适的图片格式">
    优先 PNG（无损，适合插画 / 截图），照片类用 JPEG 节省体积，需要透明通道用 WebP。
  </Step>

  <Step title="prompt 聚焦「动作」而不是「画面」">
    参考图已经定了画面，prompt 应该重点描述**如何让它动起来**：镜头推拉、物体运动、光线变化、人物表情等。例：`"Camera slowly pushes in, leaves gently swaying, sunlight flickering through branches"`。
  </Step>
</Steps>

## 响应格式

响应结构与 [文生视频](/api-capabilities/sora-2/text-to-video#响应格式) **完全相同**：提交返回 `id` + `status: "queued"`，轮询返回进度，完成后通过 `/v1/videos/{id}/content` 下载 MP4。

```json theme={null}
{
  "id": "video_abc123def456",
  "object": "video",
  "model": "sora-2",
  "status": "queued",
  "progress": 0,
  "created_at": 1712697600,
  "size": "1280x720",
  "seconds": "8",
  "quality": "standard"
}
```

<Warning>
  **⚠️ 常见 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"`
</Warning>

<Info>
  图生视频与文生视频**单价相同**（按 `seconds` 计费），并不会因为多上传了一张参考图额外收费。详见 [概览页定价表](/api-capabilities/sora-2/overview#模型定价)。
</Info>


## OpenAPI

````yaml api-reference/sora-2-image-to-video-openapi.yaml POST /v1/videos
openapi: 3.1.0
info:
  title: Sora 2 图生视频 API
  description: >
    OpenAI Sora 2 / Sora 2 Pro 图生视频接口（multipart/form-data 上传 `input_reference`
    参考图）。


    - **参考图分辨率必须与 `size` 完全一致**，否则报错 `Inpaint image must match the requested
    width and height`

    - 接受图片格式：`image/jpeg` / `image/png` / `image/webp`

    - 单价与文生视频相同（按 `seconds` 计费），并不会因为多上传一张图额外收费

    - **异步任务式端点**：本端点只提交任务，需配合 `GET /v1/videos/{id}` 轮询和 `GET
    /v1/videos/{id}/content` 下载


    **认证方式**：在请求头中添加 `Authorization: Bearer YOUR_API_KEY`


    **API Key 配置**：必须在 API易控制台把分组切到 **Sora2官转**、计费模式切到 **按量计费**


    **获取 API Key**：访问 [API易控制台](https://api.apiyi.com/token) 创建令牌
  version: 1.0.0
servers:
  - url: https://api.apiyi.com
    description: 主要端点
  - url: https://vip.apiyi.com
    description: 备用端点
security:
  - bearerAuth: []
paths:
  /v1/videos:
    post:
      tags:
        - 视频生成
      summary: 图生视频：基于参考图生成视频任务
      description: |
        提交一个 Sora 2 图生视频任务。客户端需走 multipart/form-data 上传一个参考图文件 + 文本字段。

        - 必填：`model`、`prompt`、`input_reference`
        - 可选：`seconds`（默认 `"4"`）、`size`（默认 `"720x1280"`）
        - **`input_reference` 图片像素必须等于 `size`**，建议上传前用 ffmpeg / Pillow 裁切
        - 响应字段、轮询/下载流程与文生视频完全一致
      operationId: generateSora2ImageToVideo
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/Sora2ImageToVideoRequest'
            example:
              model: sora-2
              prompt: >-
                Animate this scene: gentle waves lapping, leaves swaying,
                cinematic camera push-in
              seconds: '8'
              size: 1280x720
      responses:
        '200':
          description: 任务已提交，返回 video_id 与 queued 状态
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Sora2VideoTask'
        '400':
          description: 参数非法（参考图分辨率与 size 不匹配最常见、文件格式不支持、seconds 超范围）
        '401':
          description: 未授权 - API Key 无效
        '403':
          description: 内容审核拦截 / 计费模式不是按量计费 / 分组未选 Sora2官转
        '413':
          description: 上传图片过大
        '429':
          description: 请求频率超限或余额不足
        '500':
          description: 上游 OpenAI 网关错误，建议重试 1–2 次
      security:
        - bearerAuth: []
components:
  schemas:
    Sora2ImageToVideoRequest:
      type: object
      required:
        - model
        - prompt
        - input_reference
      properties:
        model:
          type: string
          description: 模型 ID。`sora-2` 仅支持 720p；`sora-2-pro` 支持 720p / 1024p / 1080p
          enum:
            - sora-2
            - sora-2-pro
          default: sora-2
        prompt:
          type: string
          description: 视频生成提示词。**重点描述如何让画面动起来**：镜头运动、物体动作、光线变化
          example: >-
            Animate this scene: gentle waves lapping, leaves swaying, cinematic
            camera push-in
        seconds:
          type: string
          description: 视频时长，**字符串枚举**：`"4"` / `"8"` / `"12"`
          enum:
            - '4'
            - '8'
            - '12'
          default: '4'
        size:
          type: string
          description: >
            输出分辨率，**必须与 `input_reference` 图片像素完全一致**：


            - `sora-2`（仅 720p）：`720x1280` / `1280x720`

            - `sora-2-pro` 额外：`1024x1792` / `1792x1024` / `1080x1920` /
            `1920x1080`
          enum:
            - 720x1280
            - 1280x720
            - 1024x1792
            - 1792x1024
            - 1080x1920
            - 1920x1080
          default: 720x1280
        input_reference:
          type: string
          format: binary
          description: >
            参考图文件，作为视频的起始帧/视觉锚点。


            - 接受格式：`image/jpeg` / `image/png` / `image/webp`

            - **像素必须等于 `size`**，否则报错 `Inpaint image must match the requested
            width and height`

            - 仅支持 1 个文件，字段名固定 `input_reference`
    Sora2VideoTask:
      type: object
      properties:
        id:
          type: string
          description: 任务 ID，用于后续轮询和下载
          example: video_abc123def456
        object:
          type: string
          description: 对象类型，固定 `video`
          example: video
        model:
          type: string
          description: 本次任务使用的模型 ID
          example: sora-2
        status:
          type: string
          description: |
            任务状态：
            - `queued` —— 已提交，排队等待
            - `in_progress` —— 正在生成
            - `completed` —— 完成，可下载（`/v1/videos/{id}/content`）
            - `failed` —— 失败（不计费），可重试
          enum:
            - queued
            - in_progress
            - completed
            - failed
          example: queued
        progress:
          type: integer
          description: 生成进度百分比（0–100），不严格线性
          example: 0
        created_at:
          type: integer
          description: 任务创建 Unix 时间戳（秒）
          example: 1712697600
        completed_at:
          type: integer
          description: 任务完成 Unix 时间戳（秒），仅 completed 状态返回
          example: 1712697900
        size:
          type: string
          description: 实际输出分辨率（与请求的 `size` 一致）
          example: 1280x720
        seconds:
          type: string
          description: 实际生成时长（与请求的 `seconds` 一致）
          example: '8'
        quality:
          type: string
          description: 画质档位（`standard` 对应 sora-2，`high` 对应 sora-2-pro）
          example: standard
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: 在 API易控制台获取的 API Key（必须配置 Sora2官转 分组 + 按量计费）

````