Руководство
Использование Veo 3.1 Lite для генерации видео из изображения на HiAPI: руководство по curl и Python
Что умеет veo-3.1-lite/image-to-video
veo-3.1-lite/image-to-video — это модель, преобразующая первый кадр в видео. Вы даёте ей статичное изображение, и она анимирует этот кадр, создавая короткое видео. Предоставленное изображение — это не стилевой референс и не расплывчатый источник вдохновения: это буквально начальный кадр выходного видео, поэтому композиция, положение объекта и кадрирование напрямую продолжаются в первые секунды движения.
Этот дизайнерский выбор определяет, как следует относиться к входным данным. Хорошо освещённое, чёткое, правильно кадрированное изображение даст видео, которое начинается с ожидаемого вами места. А размытое или странно кадрированное изображение даст видео, которое начинается размытым и странно кадрированным, а затем ещё сильнее уходит в сторону по мере генерации движения моделью. Большая часть усилий по эффективному использованию этой модели происходит до того, как вы напишете хоть строчку кода.
На выходе модель возвращает видео с нативным аудио. Именно этим она отличается от старых конвейеров генерации видео из изображения — тех, где сначала создавалось беззвучное изображение, а затем отдельно подключалась независимая звуковая дорожка. Здесь аудио генерируется как часть той же задачи, поэтому окружающие звуки остаются согласованными с визуальной сценой — без ручного микширования и без временных рассинхронизаций между звуковыми эффектами и соответствующим им движением.
Генерация асинхронная. Вы отправляете задачу, получаете идентификатор и затем опрашиваете её до завершения. Это осознанный архитектурный выбор: генерация видео занимает гораздо больше времени, чем типичный тайм-аут HTTP-запроса, поэтому API разделяет отправку и завершение. Каждый пример в этом руководстве следует схеме «сначала отправить, потом опрашивать».
Коротко: одно изображение на входе, видео со звуком на выходе, доставка через асинхронную задачу. Запомните эту ментальную модель — и остальная часть API станет вполне понятной.
Перед отправкой первого запроса
Для успешного первого вызова нужно подготовить три вещи.
API-ключ
Каждый запрос к HiAPI.ai аутентифицируется с помощью API-ключа. Создайте его в панели управления аккаунта и загружайте из переменной окружения, а не вставляйте напрямую в исходный файл:
export HIAPI_API_KEY="your_api_key_here"
Если какой-либо ключ когда-либо был закоммичен в репозиторий, немедленно выполните его ротацию. Заменить ключ дёшево, а утечка обходится дорого.
Доступное изображение первого кадра
Модель должна сама загрузить изображение, поэтому оно должно быть доступно по URL — публичная ссылка на объектное хранилище, путь CDN или любой HTTP-эндпоинт, не требующий интерактивной аутентификации. Подписанный URL с длительным сроком действия подойдёт; URL, требующий сессии входа, — нет. Используйте распространённый формат, например PNG или JPEG, и выбирайте разумное разрешение: чёткое изображение умеренного размера даёт модели чистые детали без лишнего расхода трафика.
Если исходное изображение находится за внутренним сервисом, сначала загрузите его в объектное хранилище, а затем передайте сгенерированный URL. Это, как правило, наиболее частая причина неудачи при первой попытке.
Путь эндпоинта
Модель обозначается как veo-3.1-lite/image-to-video. Рассматривайте эту строку как идентификатор модели, передаваемый вместе с задачей, и не хардкодьте её в десятке мест — поместите её в константу, потому что позже вам, вероятно, придётся переключаться между вариантами модели.
Экспортировав ключ, получив доступный для загрузки URL изображения и зная идентификатор модели, вы готовы к отправке.
Рабочий запрос curl
Вызов отправки создаёт задачу генерации. Полный, готовый к запуску пример:
curl -X POST "https://api.hiapi.ai/v1/video/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "veo-3.1-lite/image-to-video",
"image": "https://your-bucket.example.com/first-frame.jpg",
"prompt": "Утренний свет медленно перемещается по сцене, камера медленно приближается",
"duration": 5
}'
Рассмотрим по пунктам:
- URL — это эндпоинт генерации на HiAPI. Весь трафик моделей идёт через этот один интерфейс, поэтому позже для смены модели достаточно изменить поле
model, а не эндпоинт. - Заголовок
Authorizationнесёт ваш bearer-токен. Запись$HIAPI_API_KEYчитает его из переменной окружения, поэтому ключ никогда не попадает в историю командной оболочки. modelобозначаетveo-3.1-lite/image-to-video.image— URL первого кадра. Именно это поле делает вызов генерацией видео из изображения, а не из текста.promptнеобязателен, но настоятельно рекомендуется. Он описывает желаемое движение — движение камеры, изменение освещения, действие объекта — а не статичное содержимое кадра, поскольку модель уже видит это изображение.duration— запрошенная длительность видео в секундах, ограниченная поддерживаемым моделью диапазоном.
Ответ — объект задачи, содержащий id. Сохраните его:
{
"id": "job_abc123",
"status": "queued",
"model": "veo-3.1-lite/image-to-video"
}
Статус queued или processing — ожидаемый результат: вы ещё не получили видео, только обещание. В следующем разделе рассказывается, как превратить обещание в файл.
Тот же вызов на Python
Этот вызов curl напрямую переводится на Python с помощью requests. Версия ниже инкапсулирует отправку в небольшую функцию, удобную для повторного использования в конвейере:
import os
import requests
API_KEY = os.environ["HIAPI_API_KEY"]
BASE_URL = "https://api.hiapi.ai/v1"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
def submit_video_job(image_url: str, prompt: str, duration: int = 5) -> str:
payload = {
"model": "veo-3.1-lite/image-to-video",
"image": image_url,
"prompt": prompt,
"duration": duration,
}
response = requests.post(
f"{BASE_URL}/video/generations",
headers=HEADERS,
json=payload,
timeout=30,
)
response.raise_for_status()
data = response.json()
print(f"Submitted job {data['id']} ({data['status']})")
return data["id"]
if __name__ == "__main__":
job_id = submit_video_job(
image_url="https://your-bucket.example.com/first-frame.jpg",
prompt="Утренний свет медленно перемещается по сцене, камера медленно приближается",
)
Есть несколько деталей, которые стоит сохранить:
raise_for_status()превращает не-2xx ответы в исключения как можно раньше. Без него ошибка аутентификации или валидации выглядит как успешный вызов с неожиданным телом ответа.- Тайм-аут 30 секунд применяется только к отправке. Отправка должна возвращаться быстро; длительная работа происходит на сервере. Если тайм-аут возникает сама отправка, проблема в связности или размере полезной нагрузки запроса, а не в скорости генерации.
- Возврат id задачи, а не всего ответа, сохраняет функцию компонуемой — вызывающая сторона решает, что делать с этой задачей.
На этом функция может без изменений попасть в worker очереди, эндпоинт Flask или ячейку блокнота.
Опрос асинхронной задачи
Генерация видео занимает минуты, а не миллисекунды. Опрашивайте эндпоинт статуса задачи с интервалами, близкими к человеческому ритму, и останавливайтесь сразу, как только задача достигает терминального состояния:
import time
import requests
def wait_for_video(job_id: str, timeout: int = 600, interval: int = 10) -> dict:
deadline = time.time() + timeout
url = f"{BASE_URL}/video/generations/{job_id}"
while time.time() < deadline:
response = requests.get(url, headers=HEADERS, timeout=30)
response.raise_for_status()
data = response.json()
status = data["status"]
print(f"Job {job_id}: {status}")
if status == "completed":
return data
if status == "failed":
raise RuntimeError(f"Generation failed: {data}")
time.sleep(interval)
raise TimeoutError(f"Job {job_id} did not finish within {timeout}s")
Три практики делают это надёжным в продакшене:
- Экспоненциальная задержка. Начинайте с короткого интервала и постепенно увеличивайте его, задав разумный верхний предел. Первый опрос обычно всё ещё возвращает
queued, поэтому интенсивные удары по эндпоинту в первые секунды бесполезны. - Жёсткий тайм-аут. Всегда ограничивайте общее время ожидания. Задача, застрявшая в
processing, должна проявляться как ошибка, по которой можно поднять тревогу, а не как бесконечный цикл. - Персистентность id задачи. Запишите его в базу данных до начала опроса. Если процесс упадёт в середине ожидания, после перезапуска можно продолжить опрос, а не отправлять задачу заново и платить дважды за одну и ту же работу.
Считайте состояниями завершения цикла только completed и failed. Всё остальное — queued, processing — означает, что нужно продолжать ждать.
Ещё одно предупреждение: если вы запускаете этот код на веб-сервере, не опрашивайте внутри обработчика запроса. Отправьте задачу, верните id задачи клиенту и позвольте фоновому worker выполнить ожидание. Иначе одна медленная генерация будет занимать соединение в течение нескольких минут.
Скачивание выходного видео
Когда задача завершена, ответ о статусе содержит URL готового видео. Скачайте его как двоичные данные и запишите на диск с подходящим расширением:
def download_video(job: dict, path: str = "output.mp4") -> str:
video_url = job["output"]["url"]
response = requests.get(video_url, headers=HEADERS, timeout=120, stream=True)
response.raise_for_status()
with open(path, "wb") as f:
for chunk in response.iter_content(chunk_size=1 << 20):
f.write(chunk)
print(f"Saved {path}")
return path
Несколько вещей, которые нужно сделать правильно:
- Запись в двоичном режиме. Открытие в режиме
"w"вместо"wb"молча испортит файл: загрузка может выглядеть успешной, но видео не будет воспроизводиться. - Потоковая обработка больших файлов. Параметр
stream=Trueвместе с копированием по частям, как показано выше, сохраняет стабильное потребление памяти независимо от размера видео. - Вычисляйте контрольную сумму, если вы архивируете результаты в больших масштабах. Это одна строка кода, но она позволяет поймать усечённую загрузку до того, как она дойдёт до пользователей.
Об аудио: возвращаемое видео уже содержит свою нативную звуковую дорожку. Нет отдельного аудиофайла для получения и нет шага микширования. Если вы перепаковываете файл на последующих этапах — например, меняете формат контейнера, — сохраняйте аудиопоток. Распространённая ошибка — запустить конвертацию, которая по умолчанию отбрасывает аудио, а затем недоумевать, куда оно делось.
Наконец, выходной URL обычно имеет ограниченный срок действия. Скачайте и сохраните его в собственное хранилище как можно скорее, а не сохраняйте URL на несколько дней, чтобы потом запросить: к тому моменту ссылка может уже истечь.
Распространённые точки отказа
Ниже перечислены ошибки, которые чаще всего отнимают время при отладке вызовов генерации видео из изображения:
URL изображения недоступен
Это самая частая причина неудач с большим отрывом. Модель загружает ваше изображение на стороне сервера, поэтому URL, работающий в вашем браузере с активной сессией, ничего не доказывает. Протестируйте его обычным запросом без аутентификации перед отправкой задачи. Срок действия подписанного URL должен быть значительно больше времени генерации.
Ошибки аутентификации, выглядящие как ошибки модели
Отсутствующий или неправильно сформированный заголовок Authorization обычно возвращает явный 401, но иногда разработчики оборачивают код инициализации в логику, которая проглатывает коды состояния, а затем сообщают о «сбое модели». Прежде чем отлаживать полезную нагрузку запроса, проверьте исходный код состояния.
Слишком частый опрос или его полное отсутствие
Опрос каждые 100 миллисекунд расходует квоту и может вызвать ограничение скорости. Опрос один раз, обнаружение processing и отказ от дальнейшей проверки означает, что задача успешно завершилась, но никто об этом не узнал. Следуйте приведённой выше схеме с задержкой и тайм-аутом.
Ошибочное восприятие отправки как завершения
Успешная отправка возвращает задачу, а не видео. Код, читающий поле url из ответа на отправку, ничего не найдёт. Только ответ о статусе завершения содержит выходные данные.
Промпт описывает изображение
Промпт вроде «кошка сидит на подоконнике» лишь пересказывает то, что модель уже видит. Промпты, описывающие движение, — «камера медленно отъезжает назад, когда начинается дождь», — дают лучший результат. Описывайте изменение, а не содержимое.
Потеря id задачи при сбое
Если процесс умирает между отправкой и завершением, несохранённый id задачи означает, что работу невозможно восстановить. Сохраняйте его и опрашивайте асинхронно из worker. Небольшое дизайнерское решение позволяет избежать самого долгого процесса отладки.
Похожие статьи
Все статьи →-
Руководство
Как вызвать hailuo-2.3-fast/image-to-video через HiAPI API: практика с curl и Python
Первый рабочий запрос к быстрому и недорогому уровню генерации видео по изображению — от ID модели до загрузки MP4
-
Руководство
Как вызывать hailuo-2.3/text-to-video через API HiAPI: curl, Python и рабочие примеры запросов
-
Руководство
Использование Kandinsky 6.0 Pro для преобразования текста в видео на HiAPI: руководство по curl и Python
Практическая демонстрация с упором на запросы: построение вызова от промпта до видеоклипа, отправка запроса, опрос статуса завершения и скачивание результата