Генерация видео
Ниже приведён перевод и адаптация официального руководства OpenAI Sora с сохранением структуры и примеров, адаптированных под AITUNNEL.
Обзор
Sora — новейшая видеомодель OpenAI, создающая детальные динамичные клипы с аудио по тексту или изображениям. Видео‑API (preview) открывает эти возможности: позволяет программно создавать, расширять и ремиксировать видео. Доступно три ключевых действия:
- Создать видео (POST /v1/videos)
- Получить статус (GET /v1/videos/{video_id})
- Скачать видео (GET /v1/videos/{video_id}/content)
Модели
Sora 2
sora-2 — для скорости и гибкости. Подходит для быстрых итераций, концептов и соцсетей.
Sora 2 Pro
sora-2-pro — более стабильное и качественное выводимое видео. Подходит для продакшн‑задач и маркетинговых материалов.
Wan 2.6
wan2.6 — модель Qwen для текста и изображения в видео. Длительности 5, 10 или 15 секунд.
Wan 2.5
wan2.5 — модель Qwen для текста и изображения в видео. Длительности 5 или 10 секунд.
Генерация видео
Генерация — асинхронный процесс:
POST /v1/videosвозвращает объект задания сidи начальнымstatus- Либо опрашивайте
GET /v1/videos/{video_id}, либо используйте вебхуки - После
completedскачайте MP4GET /v1/videos/{video_id}/content
Начать рендер (curl)
curl -X POST "https://api.aitunnel.ru/v1/videos" \
-H "Authorization: Bearer $AITUNNEL_API_KEY" \
-H "Content-Type: multipart/form-data" \
-F prompt="Wide tracking shot of a teal coupe driving through a desert highway, heat ripples visible, hard sun overhead." \
-F model="sora-2-pro" \
-F size="1280x720" \
-F seconds="8"Ответ (пример):
{
"id": "video_68d7512d07848190b3e45da0ecbebcde004da08e1e0678d5",
"object": "video",
"created_at": 1758941485,
"status": "queued",
"model": "sora-2-pro",
"progress": 0,
"seconds": "8",
"size": "1280x720"
}Мониторинг прогресса
import OpenAI from 'openai';
const openai = new OpenAI({
baseURL: 'https://api.aitunnel.ru/v1',
apiKey: process.env.AITUNNEL_API_KEY
});
async function main() {
const video = await openai.videos.createAndPoll({
model: 'sora-2',
prompt: "A video of the words 'Thank you' in sparkling letters",
});
if (video.status === 'completed') {
console.log('Video successfully completed: ', video);
} else {
console.log('Video creation failed. Status: ', video.status);
}
}
main();import asyncio
import os
from openai import AsyncOpenAI
client = AsyncOpenAI(
base_url="https://api.aitunnel.ru/v1",
api_key=os.getenv("AITUNNEL_API_KEY")
)
async def main() -> None:
video = await client.videos.create_and_poll(
model="sora-2",
prompt="A video of a cat on a motorcycle",
)
if video.status == "completed":
print("Video successfully completed:", video)
else:
print("Video creation failed. Status:", video.status)
asyncio.run(main())Примечание о вебхуках: на проде используйте вебхуки (
video.completed,video.failed) вместо частого опроса.
Получение результатов
Скачать MP4 (curl)
curl -L "https://api.aitunnel.ru/v1/videos/video_abc123/content" \
-H "Authorization: Bearer $AITUNNEL_API_KEY" \
--output video.mp4Дополнительные ассеты (thumbnail, spritesheet)
# thumbnail
curl -L "https://api.aitunnel.ru/v1/videos/video_abc123/content?variant=thumbnail" \
-H "Authorization: Bearer $AITUNNEL_API_KEY" \
--output thumbnail.webp
# spritesheet
curl -L "https://api.aitunnel.ru/v1/videos/video_abc123/content?variant=spritesheet" \
-H "Authorization: Bearer $AITUNNEL_API_KEY" \
--output spritesheet.jpgИспользование изображений как референсов
Прикрепите изображение через input_reference, чтобы оно стало первым кадром видео. Разрешение изображения должно совпадать с параметром size. Поддерживаемые форматы: image/jpeg, image/png, image/webp.
Существует два способа передачи input_reference:
| Способ | Content-Type | Описание |
|---|---|---|
| Файл (multipart) | multipart/form-data | Изображение загружается напрямую как файл |
| JSON (image_url) | application/json | Передаётся URL или base64 data URL внутри объекта {"image_url": "..."} |
Распространённая ошибка
При использовании JSON поле называется image_url, а не file. Запрос с input_reference: {"file": "..."} вернёт ошибку Unknown parameter: 'input_reference.file'.
import fs from 'node:fs';
import OpenAI from 'openai';
const openai = new OpenAI({
baseURL: 'https://api.aitunnel.ru/v1',
apiKey: process.env.AITUNNEL_API_KEY
});
const fileStream = fs.createReadStream('image.png');
const video = await openai.videos.create({
model: 'sora-2-pro',
prompt: 'She turns around and smiles, then slowly walks out of the frame.',
size: '1280x720',
seconds: '8',
input_reference: await OpenAI.toFile(fileStream, 'image.png', { contentType: 'image/png' })
});
console.log(video.id, video.status);import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.aitunnel.ru/v1",
api_key=os.getenv("AITUNNEL_API_KEY")
)
with open("image.png", "rb") as img:
video = client.videos.create(
model="sora-2-pro",
prompt="She turns around and smiles, then slowly walks out of the frame.",
size="1280x720",
seconds="8",
input_reference=img,
)
print(video.id, video.status)import asyncio
import aiohttp
import aiofiles
import os
AITUNNEL_API_KEY = os.getenv("AITUNNEL_API_KEY")
BASE_URL = "https://api.aitunnel.ru/v1"
async def create_video_with_image(image_path: str, prompt: str) -> dict:
"""Создаёт задачу генерации видео с референсным изображением (multipart)."""
async with aiohttp.ClientSession() as session:
async with aiofiles.open(image_path, "rb") as f:
image_data = await f.read()
form = aiohttp.FormData()
form.add_field("model", "sora-2-pro")
form.add_field("prompt", prompt)
form.add_field("size", "1280x720")
form.add_field("seconds", "8")
form.add_field(
"input_reference",
image_data,
filename="image.jpeg",
content_type="image/jpeg",
)
async with session.post(
f"{BASE_URL}/videos",
headers={"Authorization": f"Bearer {AITUNNEL_API_KEY}"},
data=form,
) as resp:
resp.raise_for_status()
return await resp.json()
async def poll_video(video_id: str) -> dict:
"""Опрашивает статус задачи до завершения."""
async with aiohttp.ClientSession() as session:
while True:
async with session.get(
f"{BASE_URL}/videos/{video_id}",
headers={"Authorization": f"Bearer {AITUNNEL_API_KEY}"},
) as resp:
resp.raise_for_status()
data = await resp.json()
status = data.get("status")
progress = data.get("progress", 0)
print(f"Status: {status}, progress: {progress}%")
if status == "completed":
return data
if status == "failed":
raise RuntimeError(f"Video generation failed: {data}")
await asyncio.sleep(15)
async def download_video(video_id: str, output_path: str) -> None:
"""Скачивает готовое MP4."""
async with aiohttp.ClientSession() as session:
async with session.get(
f"{BASE_URL}/videos/{video_id}/content",
headers={"Authorization": f"Bearer {AITUNNEL_API_KEY}"},
) as resp:
resp.raise_for_status()
async with aiofiles.open(output_path, "wb") as f:
async for chunk in resp.content.iter_chunked(1024 * 64):
await f.write(chunk)
print(f"Saved to {output_path}")
async def main():
job = await create_video_with_image(
image_path="image.jpeg",
prompt="She turns around and smiles, then slowly walks out of the frame.",
)
video_id = job["id"]
print(f"Job created: {video_id}")
await poll_video(video_id)
await download_video(video_id, "output.mp4")
asyncio.run(main())import os
import base64
import asyncio
import aiohttp
import aiofiles
AITUNNEL_API_KEY = os.getenv("AITUNNEL_API_KEY")
BASE_URL = "https://api.aitunnel.ru/v1"
async def create_video_json(image_path: str, prompt: str) -> dict:
"""Создаёт задачу через JSON с base64 data URL в поле image_url."""
async with aiofiles.open(image_path, "rb") as f:
raw = await f.read()
b64 = base64.b64encode(raw).decode()
data_url = f"data:image/jpeg;base64,{b64}"
payload = {
"model": "sora-2-pro",
"prompt": prompt,
"size": "1280x720",
"seconds": "8",
"input_reference": {
"image_url": data_url # ключ именно "image_url", не "file"
},
}
async with aiohttp.ClientSession() as session:
async with session.post(
f"{BASE_URL}/videos",
headers={
"Authorization": f"Bearer {AITUNNEL_API_KEY}",
"Content-Type": "application/json",
},
json=payload,
) as resp:
resp.raise_for_status()
return await resp.json()curl -X POST "https://api.aitunnel.ru/v1/videos" \
-H "Authorization: Bearer $AITUNNEL_API_KEY" \
-H "Content-Type: multipart/form-data" \
-F prompt="She turns around and smiles, then slowly walks out of the frame." \
-F model="sora-2-pro" \
-F size="1280x720" \
-F seconds="8" \
-F input_reference="@sample_720p.jpeg;type=image/jpeg"Ремикс готовых видео
Сделайте точечные правки без полного пересоздания. Передайте prompt c описанием изменения.
curl -X POST "https://api.aitunnel.ru/v1/videos/video_abc123/remix" \
-H "Authorization: Bearer $AITUNNEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Shift the color palette to teal, sand, and rust, with a warm backlight."
}'Формат ответа (сводка)
{
"id": "video_abc123",
"object": "video",
"status": "in_progress",
"model": "sora-2",
"progress": 33,
"seconds": "8",
"size": "1280x720"
}Лучшие практики
- Описывайте тип кадра, сюжет, действие, окружение и освещение
- Используйте вебхуки вместо частого опроса
- Соблюдайте guardrails: 18-, нет защищённых персонажей/музыки, без реальных лиц в input изображениях
API Reference (кратко)
POST /v1/videos — создать видео
Обязательные поля:
prompt(string) — описание видеоmodel(string) — модель (sora-2,sora-2-pro,wan2.6,wan2.5)
Опциональные поля:
seconds(string) — длительность, по умолчанию"4"(допустимые значения зависят от модели)size(string) — разрешение, по умолчанию"720x1280"
Параметр input_reference (Image-to-Video):
При multipart/form-data — файл загружается напрямую:
-F input_reference="@image.jpeg;type=image/jpeg"При application/json — объект с одним из ключей:
{ "input_reference": { "image_url": "https://..." } }
{ "input_reference": { "image_url": "data:image/jpeg;base64,..." } }
{ "input_reference": { "file_id": "file-abc123" } }Формат ответа:
{
"id": "video_abc123",
"object": "video",
"created_at": 1758941485,
"status": "queued",
"model": "sora-2-pro",
"progress": 0,
"seconds": "8",
"size": "1280x720"
}Поле id — идентификатор задачи, используется в последующих запросах.
Возможные значения status: queued → in_progress → completed / failed.
GET /v1/videos/{video_id} — статус и метаданные
GET /v1/videos/{video_id}/content — скачать контент
Параметр variant: video (по умолчанию), thumbnail, spritesheet.
POST /v1/videos/{video_id}/remix — ремикс готового видео
Тело: prompt (string, required).