チュートリアル
Python 中的 Runway ML 文本生成视频 API:可运行的示例与迁移至 hiapi
Python からテキスト生成動画モデルを呼び出したいなら、Runway Gen-3 が明白な出発点です。本ガイドでは、最小限の実行可能な Python サンプルを通じて Runway スタイルの非同期パターン(タスク作成 → ポーリング → ダウンロード)を示し、その後まったく同じコードを hiapi の wan2.7-video/text-to-video にマッピングします —— 同じ /v1/tasks 構造を使うドロップイン代替です。
準備するもの
- Python 3.9+ と
requestsライブラリ(pip install requests)。 - API キー。hiapi の場合:hiapi ダッシュボード から取得し、
HIAPI_API_KEYとしてエクスポートします(sk-で始まります)。 - プロンプト。動画モデルは単一のテキストプロンプトとわずかなパラメータ(アスペクト比、秒単位の長さ)を受け取ります。
以下の認証ヘッダーはすべて
Authorization: Bearer sk-<key>です。HTTP 401 とpermission_deniedを受け取った場合、キーが間違っているか、選択したモデルへのアクセス権がありません。
最小限の実行可能サンプル(Runway スタイルの非同期パターン)
テキスト生成動画プロバイダーは動画を同期的に返しません —— 生成には 30〜120 秒かかります。標準的な構造は次のとおりです:
POST /tasksにモデル id + 入力を付けて送信 →taskIdが返る。GET /tasks/{id}→statusがsuccessまたはfailになるまでポーリング。output[0].urlを読み取り MP4 をダウンロード(この URL は短期間のみ有効)。
以下はこのループを実装した自己完結型の Python スクリプトです:
import os
import time
import requests
API_BASE = "https://api.runwayml.com/v1" # または同じ構造を使う任意のプロバイダー
API_KEY = os.environ["RUNWAY_API_KEY"]
def create_task(prompt: str, aspect_ratio: str = "16:9", duration: int = 5) -> str:
resp = requests.post(
f"{API_BASE}/tasks",
headers={"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"},
json={
"model": "gen3a_turbo",
"input": {
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"duration": duration,
},
},
timeout=60,
)
resp.raise_for_status()
return resp.json()["data"]["taskId"]
def wait_task(task_id: str, timeout_s: int = 600, interval: int = 5) -> dict:
deadline = time.time() + timeout_s
while time.time() < deadline:
resp = requests.get(
f"{API_BASE}/tasks/{task_id}",
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=30,
)
resp.raise_for_status()
task = resp.json()["data"]
if task["status"] == "success":
return task
if task["status"] == "fail":
raise RuntimeError(f"task failed: {task.get('error')}")
time.sleep(interval)
raise TimeoutError(f"task {task_id} not done after {timeout_s}s")
def download(url: str, path: str) -> None:
with requests.get(url, stream=True, timeout=120) as r:
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 20):
f.write(chunk)
if __name__ == "__main__":
tid = create_task("a cat walking on a beach at sunset, cinematic")
print(f"task {tid} created, polling…")
task = wait_task(tid)
download(task["output"][0]["url"], "out.mp4")
print("wrote out.mp4")
このスクリプトは意図的にシンプルに保たれています:SDK なし、非同期のグルーコードなし、requests のみ。一度動作すれば、まったく同じ制御フローを維持したまま、2 つの文字列を変更するだけでプロバイダーを切り替えられます。
hiapi への移行(wan2.7-video/text-to-video)
hiapi は https://api.hiapi.ai/v1/tasks で同じ非同期タスク契約を公開しています。呼び出しコードにおける唯一の違いは次のとおりです:
API_BASEがhttps://api.hiapi.ai/v1になる。- モデル id は
wan2.7-video/text-to-video(裸の id で、URL ではない)。 - 認証キーはあなたの hiapi の
sk-…キー。
具体的には、変更されるのは create_task のみです —— wait_task と download はまったく同じです:
import os
import requests
API_BASE = "https://api.hiapi.ai/v1"
API_KEY = os.environ["HIAPI_API_KEY"]
def create_task(prompt: str, aspect_ratio: str = "16:9", duration: int = 5) -> str:
resp = requests.post(
f"{API_BASE}/tasks",
headers={"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"},
json={
"model": "wan2.7-video/text-to-video",
"input": {
"prompt": prompt,
"ratio": aspect_ratio, # wan2.7 では ratio と呼ぶ:16:9、9:16、1:1、4:3、3:4
"duration": duration, # 2〜15 秒
"resolution": "720P", # 720P は $0.10/秒;デフォルトは 1080P で $0.167/秒
},
},
timeout=60,
)
resp.raise_for_status()
return resp.json()["data"]["taskId"]
レスポンス構造はまったく同じです:作成時は {"data": {"taskId": "..."}} を返し、ポーリング時は次を返します:
{
"data": {
"status": "success",
"output": [{"url": "https://…/result.mp4", "expireAt": 1750000000}],
"error": null
}
}
したがって前節の wait_task(...) と download(...) は変更なしで動作します。完全な移行はたった 2 行:API_BASE の変更と model の変更です。周囲のすべては変わりません。
output[].urlは短期間のみ有効です(数時間、レスポンスにexpireAtが含まれます)。これをリダイレクトとして扱ってください —— バイトを直ちにダウンロードし、自分で保存します(S3、R2、あなたの CDN)。生の URL をページに埋め込まないでください。
本番レベルのパターン
上記の最小サンプルはデモには適していますが、本番公開前に追加で 4 つの事項が必要です。
可能な限りポーリングではなくコールバックを使う
5 秒ごとのポーリングはリクエストを浪費し、ワーカーを 1〜2 分間占有します。環境に公開 webhook URL がある場合、それを API に渡してコールバックさせます:
resp = requests.post(
f"{API_BASE}/tasks",
headers={"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"},
json={
"model": "wan2.7-video/text-to-video",
"input": {"prompt": prompt, "ratio": "16:9", "duration": 5, "resolution": "720P"},
"callback": {
"url": "https://yourapp.com/hooks/hiapi",
"when": "final", # ステータスが success または fail のときのみコールバックする
},
},
timeout=60,
)
webhook は完全なタスクオブジェクトを受け取ります(ポーリングレスポンスと同じ構造)。したがってダウンロード手順はまったく同じです。
ポーリング vs コールバックの使い分け:
- ポーリング:スクリプト、notebook、バッチタスク、および HTTP リスナーを公開したくないあらゆる場所。
- コールバック:Web アプリ、キューワーカー、および公開エンドポイントがあるあらゆる場所。常に冪等性(次節)と組み合わせてください —— プロバイダーは再試行する可能性があります。
送信を冪等にする
コードが create_task を再試行する場合(ネットワークの揺らぎ、ループ途中のクラッシュ)、同じプロンプトを重複送信して 2 回課金されやすくなります。安価な修正方法:入力を安定したクライアントキーにハッシュ化し、呼び出し前に自身のテーブルで重複排除します:
import hashlib, json
def submit_key(prompt: str, aspect_ratio: str, duration: int) -> str:
payload = json.dumps({"p": prompt, "a": aspect_ratio, "d": duration},
sort_keys=True)
return hashlib.sha256(payload.encode()).hexdigest()
そのキーを Idempotency-Key リクエストヘッダーとして送信します(最大 255 バイト)。hiapi はアカウントとキーごとに 1 つのタスクのみを作成し、同じキーでの再試行は重複課金ではなく元の taskId を返します。さらにそれを基に (submit_key → task_id) を自身のデータベースに保存しておけば、後でタスクを検索する必要があるときに役立ちます。
エラーを正確に処理する
hiapi からは 2 種類のエラー封筒が返ります:
{ "error": {
"code": "permission_denied",
"message": "This API key cannot use the selected model.",
"request_id": "20260626…",
"type": "hiapi_error"
} }
……認証/権限の失敗時(HTTP 401/403)、およびタスクが最終的に失敗した時:
{ "data": { "status": "fail",
"error": {"code": "...", "message": "..."} } }
これらは区別して扱ってください:作成時の permission_denied はキーが間違っているかそのモデルをカバーしていないことを意味し —— 再試行しても無意味です。ポーリング後のタスク status: "fail" は一時的なモデルエラーの可能性があり、同じプロンプトで 1 回再試行する価値があります。常に request_id をログに記録し、サポート担当が追跡できるようにしてください。
料金とクォータ
動画生成はリクエスト単位ではなく出力秒数単位で課金され、解像度とモデル階層によって異なります。ユニットエコノミクスを計算する前に、hiapi 料金ページ で最新の数値を確認してください —— リポジトリにドル値をハードコードしないでください。
関連記事
- Wan 2.7 テキスト生成動画モデルページ —— モデルカード、サポートされる
aspect_ratio/durationの値、そしてまったく同じ/v1/tasks呼び出しを行う playground。 - hiapi ドキュメント —— 非同期タスク契約の完全なリファレンス(
/v1/tasksの作成、ポーリング、コールバックスキーマ、エラーコード)。 - モデルカタログ —— 同じ呼び出し構造を共有する画像、動画、音声モデル。
よくある質問
Q:動画モデルと画像モデルには異なる SDK が必要ですか?
A:いいえ。どちらも POST /v1/tasks に {model, input} を付けて送信し、どちらも {data:{taskId}} を返します。異なるのはモデルごとの input フィールドのみです —— 動画は duration を受け取りますが、画像は受け取りません。ポーリングループはまったく同じです。
Q:output[].url が数時間後に 404 になるのはなぜですか?
A:その URL は署名付きで期限切れになります(レスポンスの expireAt は Unix タイムスタンプです)。バイトを直ちにダウンロードし、自身のストレージにプッシュして、そこから配信してください。
Q:書き直しなしで Runway Gen-3 から Wan 2.7 に切り替えるにはどうすればよいですか?
A:プロバイダーが同じ非同期タスク構造(作成 → ポーリング → 出力 URL)を使用していれば、2 つの文字列を変更するだけです:API_BASE と model id。それらを環境変数(API_BASE、MODEL_ID)にラップすれば、同じコードで A/B テストができます。
Q:キーは正しいように見えるのに permission_denied を受け取ります。
A:2 つのことを確認してください:(1) キーが特定のモデルにアクセスできるか(一部のモデルは有料階層が必要)、および (2) Authorization ヘッダーがスペース付きの Bearer sk-... に正確になっているか —— Bearer: sk-... でも生のキーでもありません。エラー本文の完全な request_id はサポート担当の追跡に必要です。
Q:ポーリングとコールバックのどちらを使うべきですか?
A:公開 HTTP エンドポイントがある環境(Web アプリ、キューワーカー)ではコールバックを使用してください。スクリプト、cron タスク、notebook —— webhook の設定が馬鹿げて見えるあらゆる場所ではポーリングを使用してください。いずれの場合も taskId を保存し、クラッシュしたワーカーが再送信ではなくポーリングによって復旧できるようにしてください。
関連記事
すべての記事を見る →-
チュートリアル
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 チュートリアル
リクエスト優先の実践デモ:プロンプトから動画クリップへの呼び出しを構築し、リクエストを送信し、完了状態をポーリングし、結果をダウンロードする