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

# 快速入门：参考素材生成视频

> 结合参考视频或图片与文本提示词生成视频。

上传参考视频并描述期望的镜头，即可生成延续参考素材主体或风格的新视频。向 `POST /v1/videos` 提交一次请求，再轮询 `GET /v1/videos/{video_id}` 获取成品。本 **H3 视频**模式自带原生音频，也支持参考图片；图片可单独使用，或与参考视频一起使用。

## 准备工作

* 在 [Viggle 控制台](https://portal.viggle.ai/keys)创建 API 密钥，并导出为 `VIGGLE_API_KEY`。
* 将参考视频保存为运行示例目录下的 `reference.mp4`。视频长度须为 0.5–600 秒，分辨率至少为 64×64 像素，且宽高均为偶数。
* cURL 示例需要安装 `jq`；Node 示例需要 Node.js 20+，并保存为 `.mjs` 文件；Python 示例需要通过 `pip install requests` 安装 `requests`。

示例会上传本地文件，并请求生成 5 秒视频。请修改 `prompt` 来描述期望的镜头。`prompt` 与 `quality` 均为必填；`low` 适合快速迭代，`high` 提供更高的保真度。

## 生成并轮询视频

<CodeGroup>
  ```bash cURL theme={null}
  #!/usr/bin/env bash
  set -euo pipefail

  response=$(curl --fail-with-body -sS "https://apis.viggle.ai/v1/videos" \
    -H "Authorization: Bearer $VIGGLE_API_KEY" \
    -F prompt="keep the same outfit and walk forward" \
    -F quality=high \
    -F reference_video=@./reference.mp4 \
    -F duration_s=5)
  video_id=$(echo "$response" | jq -er '.id')

  for _ in $(seq 1 60); do
    video=$(curl --fail-with-body -sS "https://apis.viggle.ai/v1/videos/$video_id" \
      -H "Authorization: Bearer $VIGGLE_API_KEY")
    status=$(echo "$video" | jq -r '.status')

    case "$status" in
      ready)
        echo "$video" | jq -r '.video_url'
        exit 0
        ;;
      failed|cancelled)
        echo "Video $status: $(echo "$video" | jq -c '.error')" >&2
        exit 1
        ;;
    esac
    sleep 5
  done

  echo "Timed out waiting for video $video_id" >&2
  exit 1
  ```

  ```javascript Node theme={null}
  import { readFile } from "node:fs/promises";

  const baseUrl = "https://apis.viggle.ai/v1";
  const headers = { Authorization: `Bearer ${process.env.VIGGLE_API_KEY}` };
  const TERMINAL = new Set(["ready", "failed", "cancelled"]);

  async function createVideo() {
    const form = new FormData();
    form.append("prompt", "keep the same outfit and walk forward");
    form.append("quality", "high");
    form.append("reference_video", new Blob([await readFile("./reference.mp4")], { type: "video/mp4" }), "reference.mp4");
    form.append("duration_s", "5");

    const response = await fetch(`${baseUrl}/videos`, {
      method: "POST",
      headers,
      body: form,
    });
    if (!response.ok) {
      const body = await response.json().catch(() => ({}));
      throw new Error(`HTTP ${response.status}: ${JSON.stringify(body.error ?? body)}`);
    }
    return response.json();
  }

  async function waitForVideo(id) {
    for (let attempt = 0; attempt < 60; attempt++) {
      const response = await fetch(`${baseUrl}/videos/${id}`, { headers });
      if (!response.ok) {
        const body = await response.json().catch(() => ({}));
        throw new Error(`HTTP ${response.status}: ${JSON.stringify(body.error ?? body)}`);
      }
      const video = await response.json();
      if (TERMINAL.has(video.status)) return video;
      await new Promise((resolve) => setTimeout(resolve, 5000));
    }
    throw new Error(`Timed out waiting for video ${id}`);
  }

  const created = await createVideo();
  const video = await waitForVideo(created.id);

  if (video.status !== "ready") {
    throw new Error(`Video ${video.status}: ${JSON.stringify(video.error)}`);
  }
  console.log(video.video_url);
  ```

  ```python Python theme={null}
  import os
  import time
  import requests

  base_url = "https://apis.viggle.ai/v1"
  headers = {"Authorization": f"Bearer {os.environ['VIGGLE_API_KEY']}"}
  TERMINAL_STATES = {"ready", "failed", "cancelled"}

  with open("./reference.mp4", "rb") as reference_video:
      response = requests.post(
          f"{base_url}/videos",
          headers=headers,
          data={
              "prompt": "keep the same outfit and walk forward",
              "quality": "high",
              "duration_s": 5,
          },
          files={"reference_video": ("reference.mp4", reference_video, "video/mp4")},
          timeout=120,
      )
  response.raise_for_status()
  video_id = response.json()["id"]

  for _ in range(60):
      response = requests.get(f"{base_url}/videos/{video_id}", headers=headers, timeout=30)
      response.raise_for_status()
      video = response.json()
      if video["status"] in TERMINAL_STATES:
          break
      time.sleep(5)
  else:
      raise TimeoutError(f"Timed out waiting for video {video_id}")

  if video["status"] != "ready":
      error = video.get("error") or {}
      raise RuntimeError(f"{video['status']}: {error.get('code')} {error.get('message')}")

  print(video["video_url"])
  ```
</CodeGroup>

## 响应

```json theme={null}
{
  "id": "vid_3f2a9c",
  "status": "queued",
  "progress": null,
  "created_at": "2026-08-24T09:12:03Z"
}
```

创建请求会立即返回视频 ID。示例每 5 秒轮询一次，在 `status` 为 `ready` 时输出 `video_url`；遇到 `failed`、`cancelled` 或轮询超时则停止。若轮询超时，可使用同一 ID 继续调用[获取视频](/zh/v1/api-reference/videos/get)。

打开输出的 URL 即可查看或下载视频。签名链接有效期为 1 小时，再次获取视频即可得到新的链接。

## 使用 URL 或参考图片

* **托管视频：** 将 `reference_video` 文件字段替换为 `reference_video_url`，填写可公开访问的视频 URL。文件和 URL 两种方式合计最多提供 1 个参考视频。
* **添加图片：** 在视频之外加入 `reference_image` 文件或 `reference_image_url` 字段。可重复这些字段，图片总数最多为 4 张。
* **仅使用图片：** 省略视频，提供至少 1 张参考图片，并保留 `prompt` 和 `quality`。

<Note>
  必须包含至少一个参考字段；全部省略会进入文本生成视频模式。参考字段不能与首尾帧字段或 Viggle-Animate 的驱动视频、角色图片字段混用。
</Note>

`duration_s` 控制生成视频的长度（3–15 秒，默认 `5`），与参考视频的长度无关。生成费用见[计费与保留期限](/zh/v1/pricing#h3-video)。

<CardGroup cols={2}>
  <Card title="从参考视频或图片生成视频" icon="clapperboard" href="/zh/v1/api-reference/videos/create-from-reference-video">
    查看全部参数、参考图片示例和校验错误。
  </Card>

  <Card title="使用 Viggle-Animate" icon="person-running" href="/zh/v1/api-reference/videos/create-from-character-animation">
    将驱动视频的动作应用到角色图片。
  </Card>
</CardGroup>
