チュートリアル
HiAPI API で omnihuman-1.5 を使う方法:curl と Python チュートリアル
音声から動画へのデジタルヒューマン生成におけるリクエスト実行の完全ガイド、非同期ポーリング、出力のダウンロード
omnihuman-1.5 API の機能とユースケース
omnihuman-1.5 は、1 本の音声と 1 枚の人物画像からデジタルヒューマンのトーキング動画を生成します。画像内の顔が駆動され、口元と頭の動きが提供された音声に追従します。HiAPI では、このモデルは音声から動画エンドポイントとして提供されます。音声、参照画像、少数の生成パラメータを送信すると、API はタスクを返し、そのタスクをポーリングして最終的な動画を取得します。
手動ツールに対する API の優位性
ブラウザツールは単発の編集に向いています。動画生成がより大きなシステムの一部である場合、API こそが適切な選択です:
- バッチパイプライン——スクリプトや音声トラックのキューから大量のデジタルヒューマンクリップを人手を介さずに生成します。
- 製品機能——アプリ内ナレーション、ローカライズされたチュートリアル動画、パーソナライズされたメッセージなど、レンダリングごとに異なる音声と異なる人物画像を使用します。
- バックエンド自動化——コンテンツワークフローで動画を生成し、それを CMS、ストレージバケット、配信サービスに引き渡します。
リクエストの構造
HiAPI 上のすべての omnihuman-1.5 呼び出しは同じ形に従います。1 回の POST でタスクを送信し、後続のリクエストでそのステータスを確認します。レンダリングは瞬時には完了しないため、統合パターンは送信 → ポーリング → ダウンロードであり、送信 → 同じレスポンスで動画を受け取るではありません。このチュートリアルの残りの部分では、まず curl でこのフローを構築し、次に Python で実装します。
前提条件と HiAPI の認証情報
初回の呼び出しの前に、3 つの点を確認してください。
1. アカウントと API キー
HiAPI アカウントと、コンソールで作成した API キーが必要です。キーは機密情報として扱ってください。ソース管理、クライアントコード、ログに含めないでください。開発時は環境変数から、本番時はプラットフォームのシークレットマネージャーから読み込みます。
export HIAPI_API_KEY="your-api-key-here"
2. ネットワークアクセスとリクエストツール
curl を使う場合、比較的新しいシェルであればどれでも構いません。Python 版では requests ライブラリだけが必要です。特定ベンダーの SDK は不要で、この API は JSON リクエストボディを使う通常の HTTPS だからです。
pip install requests
3. モデルに必要な入力
リクエストを書く前に、以下を用意してください:
- 音声——口元のアニメーションを駆動する音声トラック。エンドポイントが受け付ける入力形式に応じて、API が読み取れる URL またはアップロード済みファイルの参照として提供します。
- 人物画像——アニメーションを駆動する顔。鮮明で正面を向き、口元が見える写真が最も予測可能な結果を生みます。
- 生成パラメータ——モデルが公開している解像度、長さ、品質などのオプション。実際に必要なフィールドだけを送信してください。初回のテストでは通常、デフォルト値が妥当です。
各リクエストは Bearer <your-key> 形式の Authorization ヘッダーでキーを渡します。初回の呼び出しで 401 が返された場合は、まずこのヘッダーを確認してください。
curl でリクエストを送信する
送信呼び出しは JSON ボディ付きの POST です。omnihuman-1.5 の正確なパスとフィールド名は、このモデルの API リファレンスページに記載されています。以下の構造は呼び出しの形と、入力するフィールドを示しています。
curl -X POST "https://api.hiapi.ai/v1/omnihuman-1.5/generate" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"audio_url": "https://your-bucket.example.com/speech.mp3",
"image_url": "https://your-bucket.example.com/portrait.jpg"
}'
フィールドごとの説明
Authorization——API キーを保持します。これがないと、リクエストは処理が始まる前に拒否されます。Content-Type: application/json——ボディは JSON です。このヘッダーを省略すると、わかりにくい検証エラーのよくある原因になります。audio_url——音声トラック。公開読み取り可能、または他の方法で API からアクセスできる URL を使用し、レンダリング途中で期限切れになる署名付きリンクではなく、安定した場所を優先してください。image_url——アニメーションを駆動する人物画像。同じアクセシビリティのルールが適用されます。
返される内容
送信が成功しても動画は返されません。代わりに、識別子(通常は id フィールド)と queued や processing などのステータスを含むタスクオブジェクトが返されます。この識別子を保存してください。以降のすべてのステータス確認で使用するハンドルです。
{
"id": "job_abc123",
"status": "queued"
}
送信の成功は、HTTP ステータスコードとタスク識別子の有無を確認することで判定できます。レスポンスに検証メッセージが含まれている場合、それは問題のあるフィールドを指しています。通常は URL の欠落または形式の誤りです。
同じリクエストを Python で実装する
curl 呼び出しは Python にそのまま変換できます。以下のバージョンはサービスにそのまま組み込めるように設計されています。キーは環境変数から取得し、ペイロードは動的に構築できる辞書であり、エラーは握りつぶさずに送出されます。
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_omnihuman_job(audio_url: str, image_url: str, **options) -> str:
payload = {
"audio_url": audio_url,
"image_url": image_url,
}
payload.update(options)
response = requests.post(
f"{BASE_URL}/omnihuman-1.5/generate",
headers=HEADERS,
json=payload,
timeout=60,
)
response.raise_for_status()
data = response.json()
return data["id"]
この書き方を採用する理由
json=payloadにより、requestsがボディをシリアライズしてコンテンツタイプを設定するため、JSON ヘッダーを忘れることがありません。**optionsにより関数の柔軟性が保たれます。生成パラメータを追加するときは、ペイロードリテラルを変更せずにキーワード引数として渡せます。timeout=60により、ハングした接続がワーカープロセスを無期限にブロックするのを防ぎます。送信は高速な呼び出しです。長時間の待機はここではなくポーリングステップに置くべきです。raise_for_status()は非 2xx レスポンスを、捕捉してログに記録できる例外に変換し、診断用にレスポンスボディを取得できるようにします。
返されたタスク識別子を保持し、ポーリングヘルパーに渡してください。次のセクションでそのヘルパーを構築します。
非同期ライフサイクルを扱う:完了までポーリングする
動画生成には時間がかかるため、API は設計上非同期です。送信後、タスクが終了状態に達するまで繰り返しステータスを問い合わせます。
ポーリングループ
import time
def wait_for_job(job_id: str, interval: int = 5, max_wait: int = 600) -> dict:
waited = 0
while waited < max_wait:
response = requests.get(
f"{BASE_URL}/omnihuman-1.5/jobs/{job_id}",
headers=HEADERS,
timeout=30,
)
response.raise_for_status()
job = response.json()
status = job["status"]
if status == "succeeded" or status == "completed":
return job
if status == "failed" or status == "error":
raise RuntimeError(f"Job {job_id} failed: {job}")
time.sleep(interval)
waited += interval
raise TimeoutError(f"Job {job_id} did not finish within {max_wait}s")
停止するタイミングの判断
3 つの結果がループを終了させ、コードはそれぞれを処理する必要があります:
- 成功——タスクが完了ステータスと出力動画への参照を報告します。終了してダウンロードに進みます。
- 失敗——タスクがエラーステータスを報告します。盲目的に再試行しないでください。失敗は通常、読み取れない入力 URL やサポートされていない音声によって引き起こされるため、まずエラーメッセージを確認してください。
- タイムアウト——独自の実時間上限に達した場合。これは失敗とは別の状況として扱ってください。タスクはサーバー側ではまだ完了する可能性があるため、ポーリングを続けるか、時間枠を延ばすか、諦めるかを意図的に決定する必要があります。
ポーリングのマナー
タイトなループではなく、適度な間隔を使用してください。動画レンダリングの場合、確認間隔は数秒で十分であり、リクエスト量を予測可能に保てます。長時間のキューイングが予想される場合は段階的にバックオフし、常に上限を設けて、スタックしたワーカープロセスが永遠にポーリングしないようにしてください。本番環境では、Web リクエストハンドラー内でポーリングするのではなく、バックグラウンドワーカーやタスクキューを優先してください。
出力動画をダウンロードして保存する
ポーリングが完了したタスクを返すと、レスポンスには動画参照(通常は結果オブジェクト内の URL フィールド)が含まれています。それを取得して、リンクに長期的に依存するのではなく、自分のストレージに永続化してください。
def download_video(job: dict, destination: str) -> None:
video_url = job.get("output_url") or job.get("result", {}).get("video_url")
if not video_url:
raise ValueError(f"No video reference in job payload: {job}")
with requests.get(video_url, stream=True, timeout=120) as response:
response.raise_for_status()
with open(destination, "wb") as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)
デバッグ時間を節約する注意点
- ストリーミングダウンロード。動画はサイズが大きいため、チャンク単位で書き込むことでファイル全体をメモリに読み込むのを避けられます。これはメモリが制限されたワーカープロセスで重要です。
- 決定的なファイル名を使用する。タスク識別子とタイムスタンプから導出することで、実行が追跡可能になり、再実行が衝突しません。
- まず一時ファイルにダウンロードし、その後移動する。一時パスに書き込み、完了後にリネームすることで、部分的なダウンロードが完全なファイルと誤認されることが決してありません。
- 返された URL は一時的なものとして扱う。バイトを自分のバケットやファイルシステムにコピーし、タスク識別子を保存オブジェクトと一緒に記録して後で検索できるようにしてください。
- 成功を宣言する前に検証する。データベースでタスクを完了としてマークする前に、ダウンロードしたファイルが空でなく、サイズが妥当であることを確認してください。
よくあるリクエストとポーリングの障害をトラブルシューティングする
統合の問題のほとんどはいくつかのカテゴリに分類できます。順番に確認してください。
認証とリクエストのエラー
401 Unauthorized——キーが欠落しているか、形式が誤っているか、Bearerトークンとして送信されていません。ヘッダーを注意深く確認してください。Bearerの後のスペースも含めて確認してください。403——キーは有効ですが、このエンドポイントへのアクセス権がありません。キーが想定したプロジェクトに属しているか確認してください。422/400検証エラー——メッセージを読んでください。よくある原因は、audio_urlの欠落、image_urlの欠落、読み取れない URL、サポートされていない音声形式です。- レスポンスに予期しない HTML が含まれる——誤ったホストまたはパスにアクセスしています。記憶から再構築するのではなく、API リファレンスでベース URL を確認してください。
タスクが失敗する、または完了しない
- API が入力 URL にアクセスできない——ブラウザで有効なリンクが、プライベートネットワーク、期限切れの署名、ホットリンク防止によってブロックされることがあります。まず安定した公開 URL でテストしてください。
- 音声と画像の不一致——無音、極端に長い、またはサポートされていない形式の音声トラックは、レンダリングを停滞させたり失敗させたりする可能性があります。まず短く標準的な形式のクリップでパイプラインを検証してください。
- ポーリングが頻繁すぎる——タイトなループはレート制限を引き起こし、タスク失敗に見えるエラーを生成する可能性があります。速度を落とし、バックオフを追加してください。
- ポーリング時間が短すぎる——キューに入ったタスクを数秒で諦めると、誤ったタイムアウトが発生します。現実的な最大待機時間を設定し、停止する前に確認したステータスを記録してください。
- 終了状態を処理していない——ループが成功のみを確認する場合、失敗はタイムアウトまで空回りし続けます。失敗ステータスを明示的に分岐処理してください。
最小限の検証習慣
問題が発生した場合は、最小限のテストに縮小してください。短い音声クリップ、小さなポートレート、1 回の送信、数秒後のステータス確認です。これが成功すれば、障害はペイロード、ポーリングロジック、または入力のホスティングにあり、API 呼び出し自体にはありません。
関連記事
すべての記事を見る →-
チュートリアル
HiAPI API で hailuo-2.3-fast/image-to-video を呼び出す方法:curl と Python の実践
高速・低コストな画像から動画へのティアにおける最初の実行可能なリクエスト — モデル ID から MP4 のダウンロードまで
-
チュートリアル
HiAPI APIでhailuo-2.3/text-to-videoを呼び出す方法:curl、Python、実行可能なリクエスト例
-
チュートリアル
HiAPI で Kandinsky 6.0 Pro のテキストto動画を使用する:curl と Python チュートリアル
リクエスト優先の実践デモ:プロンプトから動画クリップへの呼び出しを構築し、リクエストを送信し、完了状態をポーリングし、結果をダウンロードする