影片生成
影片生成概覽
提交影片任務、查詢進度並下載成品。
本頁使用已實作的 OpenAI 相容影片路由,示範 Sora 類型通道的提交、輪詢與下載。先在帳戶中選擇支援此路由的影片模型;其他通道的參數與回包可能不同。
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/videos | Submit |
| GET | /v1/videos/{id} | Poll status |
| GET | /v1/videos/{id}/content | Download |
多語言 API 呼叫
執行方式見多語言範例說明。先設定 API_KEY,並替換模型、檔案網址及 ID 占位值;四種方式會顯示相同請求的原始回應。
這組範例只提交任務,成功回應中的 id 需保留供後續查詢。
bash
curl --fail-with-body --silent --show-error --max-time 180 \
--request POST \
--url "https://api.tokatlas.ai/v1/videos" \
--header "Authorization: Bearer $API_KEY" \
--form model=YOUR_ENABLED_SORA_MODEL_ID \
--form 'prompt=A ceramic cup on a table, steam rising, slow camera movement' \
--form seconds=4 \
--form size=720x1280python
import os
import requests
headers = {
'Authorization': 'Bearer ' + os.environ["API_KEY"],
}
files = {'model': (None, 'YOUR_ENABLED_SORA_MODEL_ID'),
'prompt': (None, 'A ceramic cup on a table, steam rising, slow camera movement'),
'seconds': (None, '4'),
'size': (None, '720x1280')}
response = requests.request(
'POST', 'https://api.tokatlas.ai/v1/videos', headers=headers,
files=files,
timeout=180,
)
response.raise_for_status()
print(response.text)js
if (!process.env.API_KEY) throw new Error("Set API_KEY first.");
const form = new FormData();
form.append("model", "YOUR_ENABLED_SORA_MODEL_ID");
form.append("prompt", "A ceramic cup on a table, steam rising, slow camera movement");
form.append("seconds", "4");
form.append("size", "720x1280");
const response = await fetch("https://api.tokatlas.ai/v1/videos", {
method: "POST",
headers: {
"Authorization": "Bearer " + process.env.API_KEY,
},
body: form,
signal: AbortSignal.timeout(180_000),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
console.log(await response.text());java
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
public class Example {
public static void main(String[] args) throws Exception {
String apiKey = System.getenv("API_KEY");
if (apiKey == null || apiKey.isBlank()) {
throw new IllegalArgumentException("Set API_KEY first.");
}
String boundary = "TokatlasExampleBoundary";
String payload =
"--TokatlasExampleBoundary\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nYOUR_ENABLED_SORA_MODEL_ID\r\n" +
"--TokatlasExampleBoundary\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nA ceramic cup on a table, steam rising, slow camera movement\r\n" +
"--TokatlasExampleBoundary\r\nContent-Disposition: form-data; name=\"seconds\"\r\n\r\n4\r\n" +
"--TokatlasExampleBoundary\r\nContent-Disposition: form-data; name=\"size\"\r\n\r\n720x1280\r\n" +
"--TokatlasExampleBoundary--\r\n";
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30)).build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.tokatlas.ai/v1/videos"))
.timeout(Duration.ofSeconds(180))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.method("POST", HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new IllegalStateException("HTTP " + response.statusCode() + ": "
+ response.body());
}
System.out.println(response.body());
}
}查詢任務
將 YOUR_TASK_ID 替換為提交回應的 id。請間隔查詢,直到 status 為 completed 或 failed;不要重複提交新任務。
bash
curl --fail-with-body --silent --show-error --max-time 180 \
--request GET \
--url "https://api.tokatlas.ai/v1/videos/YOUR_TASK_ID" \
--header "Authorization: Bearer $API_KEY"python
import os
import requests
headers = {
'Authorization': 'Bearer ' + os.environ["API_KEY"],
}
response = requests.request(
'GET', 'https://api.tokatlas.ai/v1/videos/YOUR_TASK_ID', headers=headers,
timeout=180,
)
response.raise_for_status()
print(response.text)js
if (!process.env.API_KEY) throw new Error("Set API_KEY first.");
const response = await fetch("https://api.tokatlas.ai/v1/videos/YOUR_TASK_ID", {
method: "GET",
headers: {
"Authorization": "Bearer " + process.env.API_KEY,
},
signal: AbortSignal.timeout(180_000),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
console.log(await response.text());java
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
public class Example {
public static void main(String[] args) throws Exception {
String apiKey = System.getenv("API_KEY");
if (apiKey == null || apiKey.isBlank()) {
throw new IllegalArgumentException("Set API_KEY first.");
}
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30)).build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.tokatlas.ai/v1/videos/YOUR_TASK_ID"))
.timeout(Duration.ofSeconds(180))
.header("Authorization", "Bearer " + apiKey)
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new IllegalStateException("HTTP " + response.statusCode() + ": "
+ response.body());
}
System.out.println(response.body());
}
}下載成品
僅在狀態為 completed 時執行。使用同一任務 ID,成功後會保存 video.mp4。
bash
curl --fail-with-body --silent --show-error --max-time 180 \
--request GET \
--url "https://api.tokatlas.ai/v1/videos/YOUR_TASK_ID/content" \
--header "Authorization: Bearer $API_KEY" \
--output video.mp4python
import os
from pathlib import Path
import requests
headers = {
'Authorization': 'Bearer ' + os.environ["API_KEY"],
}
response = requests.request(
'GET', 'https://api.tokatlas.ai/v1/videos/YOUR_TASK_ID/content', headers=headers,
timeout=180,
)
response.raise_for_status()
Path('video.mp4').write_bytes(response.content)
print("Saved video.mp4")js
import { writeFile } from "node:fs/promises";
if (!process.env.API_KEY) throw new Error("Set API_KEY first.");
const response = await fetch("https://api.tokatlas.ai/v1/videos/YOUR_TASK_ID/content", {
method: "GET",
headers: {
"Authorization": "Bearer " + process.env.API_KEY,
},
signal: AbortSignal.timeout(180_000),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
await writeFile("video.mp4", Buffer.from(await response.arrayBuffer()));
console.log("Saved video.mp4");java
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
import java.nio.file.*;
public class Example {
public static void main(String[] args) throws Exception {
String apiKey = System.getenv("API_KEY");
if (apiKey == null || apiKey.isBlank()) {
throw new IllegalArgumentException("Set API_KEY first.");
}
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30)).build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.tokatlas.ai/v1/videos/YOUR_TASK_ID/content"))
.timeout(Duration.ofSeconds(180))
.header("Authorization", "Bearer " + apiKey)
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<byte[]> response = client.send(
request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new IllegalStateException("HTTP " + response.statusCode() + ": "
+ new String(response.body(), java.nio.charset.StandardCharsets.UTF_8));
}
Files.write(Path.of("video.mp4"), response.body());
System.out.println("Saved video.mp4");
}
}1. 準備
安裝 Python 3,設定金鑰與已開通的 Sora 相容模型 ID。範例使用 4 秒、720×1280;模型必須支援這組設定。
bash
python3 -m pip install requests
export API_KEY='YOUR_TOKATLAS_API_KEY'
export VIDEO_MODEL_ID='YOUR_ENABLED_SORA_MODEL_ID'2. 提交、等待與下載
儲存為 generate_video.py,執行 python3 generate_video.py。腳本立即保存 video-task.txt;再次執行會接續查詢相同任務。要建立新任務,先將舊 ID 檔改名備份。請勿在尚未確認前重複提交。
python
import os
import time
from pathlib import Path
import requests
base = os.environ.get("API_BASE_URL", "https://api.tokatlas.ai").rstrip("/")
headers = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
task_file = Path("video-task.txt")
if task_file.exists():
task_id = task_file.read_text().strip()
else:
response = requests.post(
base + "/v1/videos", headers=headers,
files={"model": (None, os.environ["VIDEO_MODEL_ID"]),
"prompt": (None, "A ceramic cup on a table, steam rising, slow camera movement"),
"seconds": (None, "4"), "size": (None, "720x1280")},
timeout=120,
)
response.raise_for_status()
body = response.json()
task_id = body["id"]
task_file.write_text(task_id)
print("Task:", task_id)
for attempt in range(120):
response = requests.get(base + f"/v1/videos/{task_id}", headers=headers, timeout=60)
response.raise_for_status()
task = response.json()
status = task.get("status")
print(status, task.get("progress", ""))
if status == "completed":
with requests.get(base + f"/v1/videos/{task_id}/content",
headers=headers, stream=True, timeout=120) as download:
download.raise_for_status()
with Path("video.mp4").open("wb") as output:
for chunk in download.iter_content(1024 * 1024):
output.write(chunk)
print("Saved video.mp4")
break
if status == "failed":
raise RuntimeError(task.get("error") or task)
if status not in {"queued", "in_progress"}:
raise RuntimeError(f"Unexpected task response: {task}")
time.sleep(5)
else:
raise TimeoutError("Task still pending; run again to resume polling the saved ID")3. 判斷結果
queued 與 in_progress 表示尚未完成;completed 後下載為 video.mp4,failed 時查看 error。達到輪詢次數上限只代表本機停止等待,不表示任務失敗;稍後重新執行可繼續查詢。
排錯
400:核對模型、秒數及尺寸;不要把圖像的1:1直接填入影片size。401/403:確認金鑰及影片模型權限。- 查詢不到任務:使用提交時傳回的公開
id,並使用同一帳戶的金鑰。 - 下載失敗:先確認狀態為
completed;保留任務 ID 與錯誤本文以便排查。 - 網路超時或
5xx:若 ID 已保存,重新查詢即可;提交階段沒有收到 ID 時,先查看請求記錄再決定是否重送。
