HiAPI
Блог

Руководство

Как вызвать hailuo-2.3-fast/image-to-video через HiAPI API: практика с curl и Python

Первый рабочий запрос к быстрому и недорогому уровню генерации видео по изображению — от ID модели до загрузки MP4

Nov 3, 2026
扁平切面矿物剖面风格的16:9封面图,深青底色配青绿与孔雀石色几何平面。

Что умеет hailuo-2.3-fast/image-to-video

hailuo-2.3-fast/image-to-video — это оптимизированный по скорости уровень в линейке видеогенерации HiAPI по изображению. Вы даёте ему статичный кадр и текстовый промпт с описанием нужного движения, а он возвращает короткий видеоклип, в котором картинка оживает. Слово «fast» в ID модели — главное: этот уровень обменивает часть высокой точности тяжёлых видеомоделей на меньшую задержку и меньшую стоимость рендера, поэтому он отлично подходит для итераций и пакетного производства.

Выбирайте эту модель, когда нужна рабочая первая версия клипа, а не «парадный» кадр: превью раскадровки, зацикленное видео для соцсетей, фоновые движущиеся материалы или черновой этап конвейера, где финальный рендер выполняют более тяжёлые модели. Если на выходе нужны длинные сцены, сложная постановка камеры или строгое сохранение идентичности между кадрами, отдайте такие задачи более тяжёлым уровням — структура запроса при этом остаётся той же, так что переключение сводится к смене конфигурации.

Всё ниже построено вокруг запроса, который вы действительно можете запустить. Цель — получить MP4-файл на диске, а не скриншот успешного ответа.

Обзор запроса

Рендеринг видео занимает от секунд до минут, поэтому API асинхронный: вы отправляете задачу, получаете идентификатор задачи и затем опрашиваете её, пока она не сообщит о завершении и не отдаст результат. Значит, в каждой интеграции две стадии — отправка и опрос — и только стадия отправки зависит от модели.

Запрос на отправку содержит четыре элемента, которыми вы управляете:

  • Эндпоинт и метод — POST-запрос на эндпоинт видеогенерации HiAPI.
  • Заголовок авторизации — ваш ключ в виде Authorization: Bearer $HIAPI_API_KEY. Храните ключ в переменной окружения и никогда не вставляйте его прямо в код.
  • ID модели — буквальная строка hailuo-2.3-fast/image-to-video. Именно она выбирает уровень.
  • Входные данные — prompt с описанием движения и изображение первого кадра. Изображение передаётся как публично доступный URL или data URI — в зависимости от того, какую форму принимает эндпоинт для вашего аккаунта; проще начать с URL. Тело JSON-запроса отправляется с Content-Type: application/json.

Держите эти четыре поля в одном месте кодовой базы — в словаре, конфигурационном файле, сборщике на основе переменных окружения — чтобы позже переход на другую видеомодель HiAPI требовал одной строки изменений, а не переписывания. Ответ — небольшой JSON-конверт, в котором идентификатор задачи является единственным полем, которое нужно сохранить; остальное — метаданные состояния.

Готовый curl-запрос

Вот полный шаг отправки. Замените URL изображения на адрес, доступный вашему аккаунту, и сначала экспортируйте ключ.

export HIAPI_API_KEY="your-key-here"

curl -sS -X POST "https://api.hiapi.ai/v1/video/generations" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "hailuo-2.3-fast/image-to-video",
    "prompt": "slow push in, gentle breeze moving the fabric, film grain",
    "image": "https://example.com/first-frame.jpg"
  }'

Успешный ответ — это JSON с идентификатором задачи, обычно вида {"id": "task_...", "status": "queued"}. Конкретная форма полей конверта может меняться, поэтому разбирайте ответ защищённо: читайте ID задачи по ключу, а не по позиции, и считайте неизвестные поля информационными.

При отладке добавьте -i, чтобы увидеть строку статуса HTTP. 401 почти всегда означает, что заголовок авторизации отсутствует или переменная развернулась в пустую строку — перед тем как винить тело запроса, выполните echo $HIAPI_API_KEY. 400 обычно указывает на тело запроса: недоступный URL изображения, отсутствующий model или несовпадение имени поля с эндпоинтом.

Скопируйте возвращённый ID задачи в переменную оболочки, чтобы не вводить его повторно на следующих шагах:

TASK_ID=$(curl -sS -X POST "https://api.hiapi.ai/v1/video/generations" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"hailuo-2.3-fast/image-to-video","prompt":"slow push in","image":"https://example.com/first-frame.jpg"}' \
  | python -c 'import sys,json; print(json.load(sys.stdin)["id"])')
echo "$TASK_ID"

Тот же запрос на Python

Версия на Python сохраняет тело запроса побайтово эквивалентным curl, поэтому при поиске ошибки результаты curl и Python можно сравнивать напрямую.

import os
import requests

API_BASE = "https://api.hiapi.ai/v1"
MODEL = "hailuo-2.3-fast/image-to-video"

def submit_video(prompt: str, image_url: str) -> str:
    resp = requests.post(
        f"{API_BASE}/video/generations",
        headers={"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"},
        json={
            "model": MODEL,
            "prompt": prompt,
            "image": image_url,
        },
        timeout=60,
    )
    resp.raise_for_status()
    return resp.json()["id"]

Здесь три важные привычки. os.environ[...] (а не .get) громко падает при отсутствии переменной — именно так и должно падать. Явный timeout не даёт зависшему сокету бесконечно занимать рабочий процесс. А raise_for_status() превращает тело ответа 4xx в исключение, которое можно залогировать, вместо тихого KeyError на следующей строке из-за отсутствующего ID.

Функция намеренно возвращает только ID задачи. Отправка и ожидание — разные заботы; смешивая их, вы делаете цикл опроса трудным для тестирования. Обратите внимание, что ID модели встречается один раз на уровне модуля — это единственное место, которое нужно изменить при переходе на другую видеомодель HiAPI.

Опрос задачи до готовности видео

Проверка статуса — это GET-запрос к ресурсу задачи. Задачи в очереди или в работе возвращают незавершённый статус; завершённые задачи возвращают терминальный статус вместе с результатом.

import time

def wait_for_video(task_id: str, timeout: float = 600.0) -> dict:
    deadline = time.monotonic() + timeout
    delay = 2.0
    url = f"{API_BASE}/video/generations/{task_id}"
    while True:
        r = requests.get(
            url,
            headers={"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"},
            timeout=30,
        )
        r.raise_for_status()
        task = r.json()

        if task.get("status") == "succeeded":
            return task
        if task.get("status") in {"failed", "cancelled"}:
            raise RuntimeError(f"task {task_id} ended: {task}")

        if time.monotonic() >= deadline:
            raise TimeoutError(f"task {task_id} still {task.get('status')} after {timeout}s")

        time.sleep(delay)
        delay = min(delay * 1.5, 15.0)

Это цикл повторов, а не фиксированный сон. Задержка начинается с двух секунд и растёт до верхнего предела в пятнадцать секунд, так что короткие рендеры завершаются быстро, а длинные не заваливают эндпоинт запросами. Дедлайн проверяется перед сном, поэтому общее время по настенным часам остаётся в пределах лимита, а не превышает его на один интервал. Используйте time.monotonic(), а не time.time(), чтобы корректировка часов не продлевала цикл.

Относитесь к failed и cancelled иначе, чем к «продолжать ждать»: это терминальные, но неуспешные состояния. При их наступлении залогируйте весь объект задачи — это единственная диагностика, которую вы получите, а воспроизводить сбой по одному ID крайне мучительно. Неизвестные строки статуса считайте незавершёнными и продолжайте опрос, чтобы новые промежуточные состояния не роняли рабочий процесс.

Загрузка и обработка результата

Завершённая задача содержит один или несколько URL результата. Получите байты и запишите их туда, где вы контролируете хранение.

def download_video(task: dict, dest: str) -> str:
    url = task["output"][0]["url"] if isinstance(task.get("output"), list) else task["output"]["url"]
    with requests.get(url, stream=True, timeout=120) as r:
        r.raise_for_status()
        with open(dest, "wb") as f:
            for chunk in r.iter_content(chunk_size=1 << 20):
                f.write(chunk)
    return dest

Загружайте потоком, а не через .content; видео может быть настолько большим, что кэшировать его целиком в памяти расточительно для рабочего процесса, обрабатывающего несколько задач. Конкретная форма URL результата может немного различаться в зависимости от уровня модели, поэтому перед индексацией проверьте, является ли значение списком — приведённая ветка покрывает оба случая.

Важное правило: относитесь к возвращённому URL как к краткоживущему дескриптору, а не как к хранилищу. Как только задача успешна, скопируйте файл в свой бакет или локальный путь и раздавайте его оттуда. Если сохранить URL в базу данных и попытаться получить его через несколько дней, вы получите 403 и пустой дашборд. Для объектного хранилища тот же цикл потоковых байтов можно напрямую передать в метод загрузки вашего клиента — если ваш SDK принимает файлоподобный объект, промежуточный временный файл не нужен.

Практические советы и типичные ловушки

  • Используйте заголовок авторизации, а не параметр запроса. Ключ должен быть в Authorization: Bearer .... Ключ, вставленный в URL, — это ключ в логах доступа.
  • Доступность изображения важнее его правильности. 400 при отправке гораздо чаще вызван URL, который API не может загрузить — localhost, приватный бакет, хост с защитой от хотлинка, истекающая ссылка — чем проблемой с промптом. Перед отладкой чего-либо ещё проверьте этот URL в новой сессии браузера.
  • Тип содержимого тела запроса. Отправляйте JSON с Content-Type: application/json. Если при отправке через curl забыть -H, сервер может вообще не разобрать тело запроса.
  • Слишком частый опрос. while True без сна приведёт к ограничению частоты запросов и не ускорит рендер. Задержка с ростом и дедлайн — вот и вся хитрость.
  • Нигде нет тайм-аутов. Задавайте тайм-ауты для отправки, опроса и загрузки. Отсутствие тайм-аута превращает медленного провайдера в зависшую очередь в вашей собственной системе.
  • Промпт описывает движение, а не картинку. Описывайте то, что меняется между кадрами, — движение камеры, действие субъекта, элементы окружения — и делайте это кратко. Пересказ статичного изображения ничего не даёт.
  • Одна конфигурация, много моделей. Храните ID модели, промпт и входное изображение в одном сборщике запроса. Переход с этого быстрого уровня на более тяжёлый потребует изменения одной строки.

Самый быстрый способ проверить настройку от начала до конца: выполнить отправку через curl, вставить ID задачи в цикл опроса и убедиться, что MP4 оказался на диске — как только этот шаг работает, дальше остаётся только упаковка.

Похожие статьи

Все статьи →

Начните работать с постоянными артефактами ИИ

Получите API-ключ и вызывайте модели изображений, видео и аудио через один API на основе tasks.

Читать документацию API