HiAPI
ブログ

チュートリアル

HiAPI API で hailuo-2.3-fast/image-to-video を呼び出す方法:curl と Python の実践

高速・低コストな画像から動画へのティアにおける最初の実行可能なリクエスト — モデル ID から MP4 のダウンロードまで

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

hailuo-2.3-fast/image-to-video でできること

hailuo-2.3-fast/image-to-video は、HiAPI の画像から動画を生成する製品ラインの中で速度を最適化したティアです。静止画 1 枚と、必要な動きを記述したテキストプロンプトを渡すと、その画像から前方へ動き出す短い動画クリップを返します。モデル ID に含まれる「fast」がまさに要点です。このティアは、より低いレイテンシとより低い 1 回あたりのレンダリングコストと引き換えに、重量級の動画モデルが持つ精细な忠実度の一部を犠牲にしているため、反復作業やバッチ生産に非常に適しています。

「メインビジュアル」となる完成映像ではなく、まず動く初版のクリップが必要なときにこのモデルを選んでください。絵コンテのプレビュー、ソーシャル向けのループ動画、背景のモーション素材、あるいはパイプラインの中で重量級モデルが最終レンダリングを担う前のドラフト段階などです。出力に長回しのショット、複雑なカメラワーク、または多数のフレーム間での厳密な同一性の維持が必要なら、そうしたタスクはより重量級のティアに任せてください。ただしリクエスト構造は同じに保てば、切り替えは設定の変更だけで済みます。

以下の内容はすべて、実際に実行できる 1 つのリクエストを中心に展開します。目標は、成功レスポンスのスクリーンショットではなく、MP4 ファイルをディスクに書き出すことです。

リクエストの全体像

1 回の動画レンダリングには数秒から数分かかるため、API は非同期です。タスクを送信してタスク識別子を受け取り、そのタスクを完了を報告して出力を返すまでポーリングします。つまり、どの統合にも送信とポーリングという 2 つの段階があり、モデルによって変わるのは送信段階だけです。

送信リクエストには、あなたが制御する 4 つの要素があります。

  • エンドポイントとメソッド — HiAPI の動画生成エンドポイントに POST リクエストを送ります。
  • 認証ヘッダー — あなたのキーを Authorization: Bearer $HIAPI_API_KEY の形式で指定します。キーは環境変数に置き、送信するコードにインラインで埋め込まないでください。
  • モデル ID — リテラル文字列 hailuo-2.3-fast/image-to-video。これがティアを選択します。
  • 入力 — 動きを記述する prompt と、最初のフレームとなる画像。画像は公開アクセス可能な URL または data URI で提供します。どちらが受け付けられるかは、そのエンドポイントにおけるあなたのアカウントの扱いによりますが、URL から始めるのが最も簡単です。JSON リクエストボディは Content-Type: application/json で送信します。

これら 4 つのフィールドをコードベース内の 1 か所にまとめてください。辞書、設定ファイル、環境変数で駆動するビルダーのいずれでも構いません。そうすれば、将来別の HiAPI 動画モデルに切り替えるときに、書き直しではなく 1 行の変更で済みます。レスポンスは小さな 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"]

ここで重要な習慣が 3 つあります。変数が存在しないときに .get ではなく os.environ[...] を使うと大きな音で失敗しますが、これこそ望ましい失敗の仕方です。明示的な timeout は、ハングしたソケットがワーカープロセスを無期限に占有するのを防ぎます。そして raise_for_status() は 4xx レスポンスボディを記録可能な例外に変え、次の行で ID の欠落により静かに発生する KeyError を防ぎます。

この関数は意図的にタスク ID だけを返します。送信と待機は別々の関心事であり、混ぜてしまうとポーリングループのテストが難しくなります。モデル ID が 1 回だけモジュールレベルで現れている点に注目してください。別の 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)

これは固定スリープではなくリトライループです。バックオフは 2 秒から始まり、最大 15 秒まで徐々に増加するため、短いレンダリングはすぐに完了し、長いレンダリングでもエンドポイントを激しく叩くことはありません。締め切りはスリープ前にチェックするため、総実時間は 1 つの間隔を超えることなく制限内に収まります。time.time() ではなく time.monotonic() を使うことで、時計の調整がループを延長しないようにします。

failed と cancelled は「待機を続ける」とは別に扱ってください。これらは終端ですが成功ではありません。発生したらタスクオブジェクト全体を記録してください。これが得られる唯一の診断情報であり、ID だけで失敗を再現するのは非常に苦痛です。未知の状態文字列は非終端として扱いポーリングを続けることで、新しい中間状態がワーカープロセスをクラッシュさせないようにします。

出力のダウンロードと処理

完了したタスクは 1 つ以上の出力 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 は、プロンプトの問題よりも、API が取得できない URL(localhost、プライベートバケット、ホットリンク防止のホスト、期限切れのリンク)によるものがはるかに一般的です。他の何かをデバッグする前に、新しいブラウザセッションでその URL をテストしてください。
  • リクエストボディの Content-Type。 JSON は Content-Type: application/json で送信します。curl で送信するときに -H を忘れると、サーバーがリクエストボディをまったくパースしない可能性があります。
  • ポーリングが詰まりすぎている。 スリープなしの while True はレート制限を受けるだけで、レンダリングが早く完了することはありません。バックオフと締め切りがすべてのコツです。
  • どこにもタイムアウトがない。 送信、ポーリング、ダウンロードの各呼び出しにタイムアウトを設定してください。タイムアウトの欠如は、遅いプロバイダーをあなた自身のシステム内の詰まったキューに変えてしまいます。
  • プロンプトは画面ではなく動きを記述する。 フレーム間で変化するもの(カメラワーク、被写体の動作、環境要素)を記述し、短く保ってください。静止画像を言い換えても何の得にもなりません。
  • 1 つの設定、複数のモデル。 モデル ID、プロンプト、画像入力を同じペイロードビルダーに置いてください。この高速ティアからより重量級のティアへの移行は、1 つの文字列の変更だけで済みます。

セットアップをエンドツーエンドで検証する最速の方法は、curl で送信を実行し、タスク ID をポーリングループに貼り付け、MP4 がディスクに書き出されることを確認することです。ここまで動けば、あとはパッケージング作業だけです。

永続的なAIアーティファクトで開発を始める

APIキーを取得して、タスクベースの1つのAPIで画像・動画・音声モデルを呼び出せます。

APIドキュメントを読む