> ## 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>
  **場景說明**：本頁用於「基於參考圖生成影片」——上傳一張圖片作為影片的起始幀/視覺錨點，讓靜態畫面"動起來"。如果不需要參考圖，請使用 [文生影片介面](/zh-Hant/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>

## 響應格式

響應結構與 [文生影片](/zh-Hant/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` 計費），並不會因為多上傳了一張參考圖額外收費。詳見 [概覽頁定價表](/zh-Hant/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官转 分组 + 按量计费）

````