構築 · ゴーストマネキン自動化
カタログのゴーストマネキン抽出を自動化する
要約
前回の実行以降のカタログの差分を取得し、/api/v1/ghost-mannequin/batch に単一のstyleを指定した product_images 配列(各アイテムは画像URLと任意の back_image)をPOSTします。すべてのアイテムが completed または failed になるまでバッチIDを3秒ごとにポーリングし、各製品行をそれぞれの output_url で更新します。同時実行バッチ数はプランごとに上限があります。
- Credits: 2Kで4 credits · 4Kで5 credits
- 所要時間の目安: 1.5–4 min
- 処理方式: 非同期、ポーリング
このページに記載されている数値はすべて以下に基づきます: Photta APIドキュメント · 確認済み
デザイナーの手を介さずに、すべてのPDP写真からマネキンを除去。Phottaのゴーストマネキンendpointは自然にバッチ処理に対応しており、夜間cronを実行すれば、5万SKUのフィードを数週間ではなく数時間でインビジブルマネキンカタログへと変換できます。
RESPONSE=$(curl -s -X POST https://api.photta.app/api/v1/ghost-mannequin \
-H "Authorization: Bearer $PHOTTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"product_image": "https://cdn.yourstore.com/products/jacket-front.jpg",
"back_image": "https://cdn.yourstore.com/products/jacket-back.jpg",
"image_side": "front",
"angle": "side",
"style": "3d",
"background_color": "white",
"resolution": "2K",
"aspect_ratio": "3:4",
"user_instruction": "closed front, cream interior lining",
"brand_label_id": "7cc9f988-068b-44cd-8004-22f486b10e67"
}')
ID=$(echo "$RESPONSE" | jq -r '.data.id')const res = await fetch("https://api.photta.app/api/v1/ghost-mannequin", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
"product_image": "https://cdn.yourstore.com/products/jacket-front.jpg",
"back_image": "https://cdn.yourstore.com/products/jacket-back.jpg",
"image_side": "front",
"angle": "side",
"style": "3d",
"background_color": "white",
"resolution": "2K",
"aspect_ratio": "3:4",
"user_instruction": "closed front, cream interior lining",
"brand_label_id": "7cc9f988-068b-44cd-8004-22f486b10e67"
}),
});
const { data } = await res.json(); // data.id, data.statusimport os, requests
res = requests.post(
"https://api.photta.app/api/v1/ghost-mannequin",
headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"},
json={
"product_image": "https://cdn.yourstore.com/products/jacket-front.jpg",
"back_image": "https://cdn.yourstore.com/products/jacket-back.jpg",
"image_side": "front",
"angle": "side",
"style": "3d",
"background_color": "white",
"resolution": "2K",
"aspect_ratio": "3:4",
"user_instruction": "closed front, cream interior lining",
"brand_label_id": "7cc9f988-068b-44cd-8004-22f486b10e67"
},
)
data = res.json()["data"] # data["id"], data["status"]仕組み
カタログ全体の夜間実行
無人実行される自動化に必要な5つの要素。
ステップ 1
カタログのマッピング
どの製品行にゴーストマネキン画像が必要か、すでに存在するか、除外対象のカテゴリかを把握します。夜間ジョブが差分のみを処理できるよう、last_photographed_at タイムスタンプを保存してください。
ステップ 2
カテゴリごとに3Dまたは平置きを選択
アウター、デニム、テーラードアイテムなど、ボリュームが必要な衣服には `3d` を指定します。平置きでの撮影に適したアクセサリーやニットウェアには `flat_lay` を指定します。バッチビルダーが手動調整なしで適切なスタイルを選択できるよう、製品カテゴリごとにスタイルを保存してください。
ステップ 3
差分のバッチ送信
バッチ全体に対して1つのstyle、background_color、resolutionを指定し、product_images 配列(各アイテムにimage、任意の back_image、image_side を含む)とともにPOST /api/v1/ghost-mannequin/batch を実行します。配列のインデックスから製品IDへの独自のマッピングを保持してください。レスポンスでは同じ順序でアイテムごとに1つの生成IDが返されます。
ステップ 4
親バッチのポーリング
バッチendpointは集計カウンター(total、processing、completed、failed)を返します。completed + failed が total と等しくなるまで3秒ごとにポーリングしてください。Phottaはバッチ内でアイテムを並列処理するため、個々の生成をポーリングする必要はありません。
ステップ 5
データベースへの永続化
status.data.items を順にイテレーションし(バッチは配列の順序を保持します)、各製品行をそれぞれの output_url で更新します。失敗をログに記録してください。失敗はほとんどの場合フィード内の不正な元画像に起因するため、修正して次回の実行時に再送信できます。
Pythonによる3ステップ
差分抽出、バッチ送信、親ポーリング — カタログ自動化の最小実用構成。
from datetime import datetime, timedelta
yesterday = datetime.utcnow() - timedelta(days=1)
rows = db.execute(
"SELECT id, source_image_url FROM products WHERE updated_at > %s",
(yesterday,),
).fetchall()import os, requests
res = requests.post(
"https://api.photta.app/api/v1/ghost-mannequin/batch",
headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"},
json={
"product_images": [
{
"image": "https://cdn.yourstore.com/products/jacket-front.jpg",
"back_image": "https://cdn.yourstore.com/products/jacket-back.jpg",
"image_side": "front"
},
{
"image": "https://cdn.yourstore.com/products/shirt-front.jpg",
"image_side": "front"
},
{
"image": "https://cdn.yourstore.com/products/trousers-front.jpg",
"image_side": "front"
}
],
"style": "3d",
"background_color": "white",
"resolution": "2K",
"aspect_ratio": "3:4",
"user_instruction": "closed front, buttoned up",
"brand_label_id": "7cc9f988-068b-44cd-8004-22f486b10e67"
},
)
data = res.json()["data"] # data["id"], data["status"]import os, time, requests
BASE = "https://api.photta.app/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"}
# data["items"] comes back in the order of the product_images you submitted.
batch_id = data["batch_id"]
for _ in range(240): # 240 × 3s = 12 minutes
status = requests.get(f"{BASE}/ghost-mannequin/batch/{batch_id}", headers=HEADERS).json()["data"]
if status["completed"] + status["failed"] == status["total"]:
break
time.sleep(3)
for row, item in zip(rows, status["items"]):
if item["status"] == "completed":
db.execute(
"UPDATE products SET ghost_image_url = %s WHERE id = %s",
(item["output_url"], row.id),
)開発者からのよくある質問
カタログ規模でのゴーストマネキンの実行
1つのバッチの最大サイズはどれくらいですか?
ドキュメントによると、バッチendpointは1回のリクエストで最大数百件のアイテムを受け付けます。5万SKUの夜間実行では、差分を100〜200件のバッチに分割して順次実行し、プランのrate limitに余裕を持って収まるようにしてください。
1つのバッチで3Dと平置きを混在させることはできますか?
はい — styleはアイテムごとのフィールドであるため、1つのバッチに両方を含めることができます。実際には、バッチ内のすべての行が同一のデータベースカラムに格納される方が後続の永続化ロジックがシンプルになるため、多くのチームはカテゴリごとに分割しています。
失敗したアイテムはどうなりますか?
バッチ内で失敗したアイテムのcreditsは自動的に返金され、最終的なアイテムのpayloadに error_code と error_message が付与されます。失敗をログに記録し、チームが元画像を修正するか、その行を永続的な不正入力としてマークした後の翌夜に再試行してください。
結果を製品IDにどのようにマッピングし直しますか?
送信時に配列のインデックスから製品IDへの独自のマッピングを保持してください。バッチレスポンスは product_images と同じ順序でアイテムごとに1つの生成IDを返し、バッチステータスでもその順序でアイテムがリストされます。
これを毎日のcronとしてスケジュールできますか?
はい — 直近24時間の差分を取得し、チャンクに分割し、バッチを投入して、結果をデータベースに書き込むシンプルなデイリーcronは、カタログ規模のチームにとって基幹となるパターンです。ランナーを冪等にしておけば、ある夜に失敗しても安全に再実行できます。