
Image Ecommerce
- 1.9k installs
- 21 repo stars
- Updated August 3, 2026
- starchild-ai-agent/official-skills
image-ecommerce is an agent skill for e-commerce product photography: white-background hero shots, lifestyle scenes, flat lay, detail close-ups, packaging shots, group/collection displays, scale references,.
About
The image-ecommerce skill is designed for e-commerce product photography: white-background hero shots, lifestyle scenes, flat lay, detail close-ups, packaging shots, group/collection displays, scale references,. image-ecommerce Use this skill for all e-commerce product photography requests on Starchild. Quick start — single product photo (most common) The script reads the local file, base64-encodes it, and sends it to fal.ai as a data URI — no manual URL publishing needed. Invoke when the user asks about image ecommerce or related SKILL.md workflows.
- image-ecommerce → user wants PRODUCT PHOTOS for e-commerce, catalogs, or marketing.
- image-edit → user wants to EDIT or TRANSFORM an existing image (not product-specific).
- image-portrait → user wants a portrait with their face/identity preserved.
- image-create → user wants to CREATE something from text (not product photography).
- image-tryon → user wants to try on clothing/accessories on a person.
Image Ecommerce by the numbers
- 1,905 all-time installs (skills.sh)
- +79 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #308 of 1,879 Marketing & SEO skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
image-ecommerce capabilities & compatibility
- Capabilities
- image ecommerce → user wants product photos for · image edit → user wants to edit or transform an · image portrait → user wants a portrait with thei · image create → user wants to create something fr
- Use cases
- seo
What image-ecommerce says it does
E-commerce product photography: white-background hero shots, lifestyle scenes, flat lay, detail close-ups, packaging shots, group/collection displays, scale references, seasonal/ho
E-commerce product photography: white-background hero shots, lifestyle scenes, flat lay, detail close-ups, packaging shots, group/collection displays, scale ref
npx skills add https://github.com/starchild-ai-agent/official-skills --skill image-ecommerceAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.9k |
|---|---|
| repo stars | ★ 21 |
| Last updated | August 3, 2026 |
| Repository | starchild-ai-agent/official-skills ↗ |
How do I e-commerce product photography: white-background hero shots, lifestyle scenes, flat lay, detail close-ups, packaging shots, group/collection displays, scale references,?
E-commerce product photography: white-background hero shots, lifestyle scenes, flat lay, detail close-ups, packaging shots, group/collection displays, scale references,.
Who is it for?
Developers using image ecommerce workflows documented in SKILL.md.
Skip if: Skip when the task falls outside image-ecommerce scope or needs a different stack.
When should I use this skill?
User asks about image ecommerce or related SKILL.md workflows.
What you get
Completed image-ecommerce workflow with documented commands, files, and expected deliverables.
- Product hero images
- Lifestyle and flat-lay shots
- Platform-optimized listing graphics
By the numbers
- Starchild official skill version 1.0.0
- Optimizes output for 7 e-commerce and social platforms
- Supports 10+ product shot types including hero, lifestyle, and 360-degree views
Files
image-ecommerce
Use this skill for all e-commerce product photography requests on Starchild.
Covers: white-background hero shots, lifestyle product scenes, flat lay arrangements, detail/macro close-ups, packaging/unboxing shots, group/collection displays, scale reference images, seasonal themes (spring/summer/autumn/winter), 360-degree views, comparison layouts, infographic-style feature callouts, and platform-optimized images for Amazon, Shopify, Taobao, Instagram, Xiaohongshu, Etsy, eBay.
Core principle: call the provided script. Do not re-implement proxy/billing plumbing.
When to use image-ecommerce vs other image skills:
- image-ecommerce → user wants PRODUCT PHOTOS for e-commerce, catalogs, or marketing
- image-edit → user wants to EDIT or TRANSFORM an existing image (not product-specific)
- image-portrait → user wants a portrait with their face/identity preserved
- image-create → user wants to CREATE something from text (not product photography)
- image-tryon → user wants to try on clothing/accessories on a person
---
1. Quick start — single product photo (most common)
exec(open('skills/image-ecommerce/product_photo.py').read())
result = product_photo(
product_path="uploads/product.jpg",
style="hero",
background="white",
)
# result -> {"success": True, "images": [{"local_path": "output/images/..."}], ...}The script reads the local file, base64-encodes it, and sends it to fal.ai as a data URI — no manual URL publishing needed.
2. Quick start — public URL
exec(open('skills/image-ecommerce/product_photo.py').read())
result = product_photo(
product_url="https://example.com/product.jpg",
style="lifestyle",
background="natural",
)3. Quick start — text-to-image (no product photo)
exec(open('skills/image-ecommerce/product_photo.py').read())
result = product_photo(
prompt="premium wireless bluetooth headphones, matte black finish, over-ear design",
style="hero",
background="white",
)When no product_path or product_url is provided, the script uses the text-to-image endpoint (no /edit suffix). A prompt describing the product is required in this mode.
4. Quick start — platform-optimized
exec(open('skills/image-ecommerce/product_photo.py').read())
result = product_photo(
product_path="uploads/product.jpg",
platform="amazon",
)
# Automatically applies: style=hero, background=white, aspect_ratio=1:15. Quick start — complete product image set
exec(open('skills/image-ecommerce/product_photo.py').read())
result = product_photo_set(
product_path="uploads/product.jpg",
prompt="premium leather wallet",
platform="amazon",
)
# Generates 7 images: hero, lifestyle, detail, scale, alternate angle, packaging, flat layDelivering the result to the user — IMPORTANT
Never hand the user the raw fal.media URL. fal serves files with restrictive CSP headers. The only reliable delivery path is the already-downloaded local file:
1. Use each image's local_path (e.g. output/images/xxx.png) — the script always downloads on success. 2. Tell the user the files are saved to output/images/ and viewable in the workspace file panel. 3. On Web channel, embed inline so the user can preview in chat:
4. On Telegram / WeChat: send via send_to_telegram(file_path="output/images/...", message_type="image") or send_to_wechat(file_path="output/images/...", message_type="image").
---
6. Parameters — product_photo()
| Parameter | Required | Default | Description |
|---|---|---|---|
product_path | no | — | Local workspace file path to the product image |
product_url | no | — | Public HTTPS URL of the product image |
prompt | no | — | Custom prompt describing the product or desired photo |
style | no | "hero" | Photography style preset (see §7) |
background | no | "white" | Background type (see §8) |
model | no | "nanopro" | Model: "nanopro" (fast ~25s) or "gpt" (best quality ~150s) |
count | no | 1 | Number of images to generate (1–8) |
aspect_ratio | no | "1:1" | Output ratio: 1:1, 3:4, 4:3, 9:16, 16:9 |
platform | no | — | Platform preset: amazon, shopify, taobao, instagram, xiaohongshu, etsy, ebay |
Image input rules:
- Provide
product_pathORproduct_urlfor edit mode (transform existing product photo). - If both are given,
product_pathtakes priority. - Omit both for pure text-to-image generation (requires
prompt).
Prompt priority: prompt + style/background (enhanced) > style + background templates.
Platform preset: When platform is set, it overrides default style, background, and aspect_ratio with platform-optimized values — unless you explicitly set them.
---
7. Photography styles
Core product shots
| Style | Key | Best for |
|---|---|---|
| Hero shot | hero | Primary listing image, magazine ads, main product display |
| Lifestyle | lifestyle | Product in use, editorial, social media |
| Flat lay | flat_lay | Instagram, top-down arrangement, catalog |
| Detail close-up | detail | Material quality, texture, craftsmanship |
| Packaging | packaging | Unboxing experience, brand packaging |
| Group/collection | group | Multiple products, variants, bundles |
| Scale reference | scale | Size comparison, product in hand |
Marketing & informational
| Style | Key | Best for |
|---|---|---|
| 360° view | 360_view | Multi-angle showcase, turntable display |
| Comparison | comparison | Side-by-side, before/after, feature highlight |
| Infographic | infographic | Feature callouts, specs, dimensions |
Seasonal campaigns
| Style | Key | Best for |
|---|---|---|
| Spring | seasonal_spring | Cherry blossoms, fresh green, pastel |
| Summer | seasonal_summer | Beach, sunshine, tropical, vacation |
| Autumn | seasonal_autumn | Fall leaves, golden tones, harvest |
| Winter | seasonal_winter | Snow, holiday, festive, cozy |
---
8. Background types
| Background | Key | Best for |
|---|---|---|
| Pure white | white | Amazon, e-commerce standard, marketplace listings |
| Gradient | gradient | Hero shots, premium feel, modern |
| Studio | studio | Professional catalog, controlled lighting |
| Natural | natural | Outdoor products, organic brands |
| Lifestyle | lifestyle | Home/office context, in-use scenarios |
| Colored | colored | Brand-matching, vibrant marketing |
| Textured | textured | Luxury products, marble/wood surface |
| Transparent | transparent | Product cutout, PNG for design use |
---
9. Platform presets
| Platform | Aspect Ratio | Background | Style | Key Requirements |
|---|---|---|---|---|
| Amazon | 1:1 | white | hero | Pure white bg (RGB 255,255,255), product fills 85%+, no props/text/watermarks, min 1000px (1600px+ for zoom) |
| Shopify | 1:1 | white | hero | Square format, consistent catalog style, 2048x2048 recommended |
| Taobao | 1:1 | white | hero | 800x800 minimum, white bg for main image |
| 1:1 | lifestyle | lifestyle | 1080x1080 feed, lifestyle context, visually appealing | |
| Xiaohongshu | 3:4 | lifestyle | flat_lay | 1080x1440 vertical, aesthetic flat lay, text overlay space |
| Etsy | 4:3 | natural | lifestyle | Handmade/artisan feel, natural backgrounds |
| eBay | 1:1 | white | hero | White background, clear product view, 1600px min for zoom |
---
10. Model selection guide
| Model | Key | Speed | Quality | Best for |
|---|---|---|---|---|
| NanoPro | nanopro | ~25s | Good | Default for all requests. Fast iteration. |
| GPT Image 2 | gpt | ~150s | Best | When user explicitly asks for "highest quality" or "best quality". Complex scenes. |
Decision rules: 1. Default: always use nanopro unless the user explicitly requests higher quality. 2. Use `gpt` when: user says "highest quality", "best quality", "premium", or the scene is very complex with many specific details. 3. Use `nanopro` when: user wants fast results, is iterating on styles, or generating multiple images.
# Default (fast)
result = product_photo(product_path="product.jpg", style="hero")
# High quality (user requested)
result = product_photo(product_path="product.jpg", style="hero", model="gpt")---
11. Intent recognition guide
Use this table to map user requests to the correct style + background:
Product listing images
| User says | Style | Background | Notes |
|---|---|---|---|
| "product photo", "listing image", "主图" | hero | white | Default e-commerce |
| "Amazon listing", "亚马逊主图" | hero | white | Use platform="amazon" |
| "Shopify product", "独立站产品图" | hero | white | Use platform="shopify" |
| "淘宝主图", "天猫主图" | hero | white | Use platform="taobao" |
| "white background", "白底图" | hero | white | Standard packshot |
| "product on white", "纯白背景" | hero | white | Amazon-style |
Lifestyle & context
| User says | Style | Background | Notes |
|---|---|---|---|
| "lifestyle photo", "场景图" | lifestyle | lifestyle | Product in context |
| "product in use", "使用场景" | lifestyle | lifestyle | Show product being used |
| "flat lay", "俯拍", "平铺" | flat_lay | textured | Top-down arrangement |
| "Instagram product", "小红书产品" | flat_lay | lifestyle | Social media optimized |
Detail & technical
| User says | Style | Background | Notes |
|---|---|---|---|
| "close-up", "detail shot", "细节图" | detail | studio | Macro/texture |
| "packaging", "包装图", "开箱" | packaging | studio | Box + product |
| "size comparison", "尺寸对比" | scale | studio | With reference object |
| "multiple products", "组合图" | group | white | Collection display |
| "360 view", "多角度" | 360_view | white | Turntable style |
| "comparison", "对比图" | comparison | white | Side by side |
| "infographic", "功能标注" | infographic | white | Feature callouts |
Seasonal & campaign
| User says | Style | Background | Notes |
|---|---|---|---|
| "spring campaign", "春季" | seasonal_spring | auto | Cherry blossoms, pastel |
| "summer sale", "夏季" | seasonal_summer | auto | Beach, tropical |
| "autumn/fall", "秋季" | seasonal_autumn | auto | Golden leaves, warm |
| "winter/holiday", "冬季", "圣诞" | seasonal_winter | auto | Snow, festive |
Complete product set
| User says | Function | Notes |
|---|---|---|
| "complete set", "全套产品图", "listing images" | product_photo_set() | 7 images covering all angles |
| "Amazon listing set", "亚马逊全套" | product_photo_set(platform="amazon") | Platform-optimized set |
---
12. Usage examples by scenario
Amazon listing — white background hero shot
exec(open('skills/image-ecommerce/product_photo.py').read())
result = product_photo(
product_path="uploads/headphones.jpg",
platform="amazon",
)Lifestyle product photo
exec(open('skills/image-ecommerce/product_photo.py').read())
result = product_photo(
product_path="uploads/coffee_mug.jpg",
style="lifestyle",
background="lifestyle",
prompt="premium coffee mug on rustic wooden table beside an open book, morning sunlight",
)Product detail close-up
exec(open('skills/image-ecommerce/product_photo.py').read())
result = product_photo(
product_path="uploads/leather_bag.jpg",
style="detail",
background="studio",
prompt="extreme close-up of leather stitching and grain texture",
)Seasonal campaign — winter holiday
exec(open('skills/image-ecommerce/product_photo.py').read())
result = product_photo(
product_path="uploads/candle.jpg",
style="seasonal_winter",
prompt="luxury scented candle in cozy holiday setting with pine branches and warm glow",
)Text-to-image — generate product from description
exec(open('skills/image-ecommerce/product_photo.py').read())
result = product_photo(
prompt="sleek minimalist smartwatch with black silicone band and OLED display showing time",
style="hero",
background="gradient",
model="gpt",
)Flat lay for Instagram / Xiaohongshu
exec(open('skills/image-ecommerce/product_photo.py').read())
result = product_photo(
product_path="uploads/skincare_set.jpg",
style="flat_lay",
background="textured",
platform="xiaohongshu",
)Multiple images — batch generation
exec(open('skills/image-ecommerce/product_photo.py').read())
result = product_photo(
product_path="uploads/sneakers.jpg",
style="hero",
background="white",
count=4,
)
# Generates 4 variations of the hero shotComplete product image set
exec(open('skills/image-ecommerce/product_photo.py').read())
result = product_photo_set(
product_path="uploads/wallet.jpg",
prompt="premium leather bifold wallet",
platform="amazon",
)
# result -> {"success": True, "sets": [...], "total_images": 7, ...}
# Generates: hero, lifestyle, detail, scale, alternate angle, packaging, flat lay---
13. Prompt engineering best practices
The product photography prompt structure
Every effective product photo prompt should include these elements:
[product description], [photography style], [lighting], [background/surface], [composition], [quality modifiers]Key principles (derived from product-photography, eachlabs-product-visuals, image-create skills)
1. Product preservation is critical — when editing an existing product image:
- Always emphasize "keep the product exactly as it is"
- Preserve shape, color, branding, and details
- Only change the background/context/lighting
2. Lighting specificity — always specify lighting type:
- Studio: "soft diffused studio lighting", "even lighting with no shadows"
- Dramatic: "dramatic rim lighting", "edge light for premium feel"
- Natural: "natural window light", "golden hour warm light"
- Flat: "flat even lighting" (for e-commerce white background)
3. Background precision — vague backgrounds produce poor results:
- ❌ "nice background"
- ✅ "pure white background #FFFFFF, no shadows"
- ✅ "rustic wooden table with morning sunlight"
- ✅ "soft gradient from white to light grey"
4. Composition rules (from product-photography skill):
- Hero shot: product fills 80% of frame, slight 15-30° angle
- Packshot (Amazon): product dead center, fills 85%+
- Flat lay: bird's eye view, organized arrangement
- Group: odd numbers (3 or 5), triangle composition
5. Shadow types matter:
- No shadow: Amazon/e-commerce requirements
- Contact shadow: grounded but clean
- Drop shadow: adds depth, professional
- Reflection: tech, luxury, premium feel
6. Material and texture — for detail shots, specify:
- "visible leather grain and stitching"
- "brushed metal finish with subtle reflections"
- "soft fabric texture, thread detail visible"
7. Platform compliance — when targeting a specific platform:
- Amazon: pure white (RGB 255,255,255), no props/text/watermarks
- Instagram: lifestyle context, visually appealing
- Xiaohongshu: vertical format, aesthetic, text overlay space
Example: building a custom prompt
User request: "I need a hero shot of my leather wallet for Amazon"
result = product_photo(
product_path="uploads/wallet.jpg",
platform="amazon",
prompt="premium leather bifold wallet, rich brown color, slight angle showing card slots",
)The script automatically builds:
Transform this product image into a professional e-commerce photo.
Keep the product exactly as it is — preserve its shape, color, details, and branding.
premium leather bifold wallet, rich brown color, slight angle showing card slots.
Photography style: professional product hero shot, clean composition, studio lighting...
Background: pure white background #FFFFFF, clean, e-commerce standard, no shadows.---
14. E-commerce image set guide
A complete product listing needs 7-9 images. Use product_photo_set() for automatic generation, or create individual shots:
| Position | Image Type | Style | Background | Purpose |
|---|---|---|---|---|
| 1 | Hero / packshot | hero | white | Primary listing image |
| 2 | Lifestyle | lifestyle | lifestyle | Product in use/context |
| 3 | Detail close-up | detail | studio | Material quality, craftsmanship |
| 4 | Scale reference | scale | studio | Size in hand or next to known object |
| 5 | Alternate angle | hero | white | Back or side view |
| 6 | Packaging | packaging | studio | Unboxing experience |
| 7 | Flat lay | flat_lay | textured | Arranged composition |
| 8 | Infographic | infographic | white | Dimensions, specs, features |
| 9 | Seasonal | seasonal_* | auto | Campaign-specific |
---
15. Error handling
The script returns structured results. Always check success:
result = product_photo(product_path="uploads/product.jpg")
if result["success"]:
for img in result["images"]:
print(f"Saved: {img['local_path']}")
else:
print(f"Error: {result.get('error')}")Common errors:
"File not found"— check the product_path"Unsupported image format"— use JPG, PNG, or WebP"Image too large"— max 10 MB"Either a product image or a prompt is required"— provide product_path/product_url or prompt"Unknown style/background"— check available presets in §7/§8
"""Cost tracking helper for skill subprocesses.
Skills that call sc-proxy via plain `requests` need to:
1. Tag every paid call with a SC-CALLER-ID that ties it back to the user
turn that triggered the skill (so the agent's per-turn cost summary
shows the cost in the right cost card).
2. After each call, parse the sc-proxy response headers
(`X-Credits-Used`, `X-Credits-Api-Type`) and write a row to the cost
ledger that the agent reads back when it builds the SSE
`cost_summary` event.
This file is intentionally zero-dependency (stdlib only) so it can be
dropped into any skill folder without coupling to starchild-clawd internals.
Env vars consumed (set by the agent before dispatching the bash subprocess):
- STARCHILD_TOOL_CALLER_ID — opaque tag for the current tool call
- STARCHILD_USER_TURN_ID — uuid of the current user turn
- STARCHILD_COST_LEDGER_DIR — optional override for ledger directory
When env vars are absent (e.g. running the script outside an agent), the
helpers degrade gracefully: caller-id falls back to a synthetic string so
the call still goes through, and ledger writes still happen for audit but
the user-turn reader will skip them.
"""
from __future__ import annotations
import fcntl
import json
import os
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Optional
from urllib.parse import urlparse
_DEFAULT_LEDGER_DIR = "/data/.starchild/cost_ledger"
# Allowlisted request payload keys we forward into the ledger row's
# `details` field. MUST stay in sync with starchild-clawd's
# core/http_client._record_cost_to_ledger allowlist — anything not in
# that allowlist won't be picked up by the agent and won't render in
# the frontend cost card.
_PAYLOAD_ALLOWLIST = (
# Identity
"model", "provider",
# Image geometry
"aspect_ratio", "quality", "resolution", "image_size", "size",
# Video / motion
"duration", "duration_s", "fps", "motion_strength",
# Quantity
"n", "count",
# Generation knobs
"seed", "steps", "guidance_scale", "cfg_scale", "strength",
"scheduler", "sampler",
# Reference / mode hints
"image_to_image", "image_to_video", "use_reference", "reference_count",
)
def caller_headers(extra: Optional[Dict[str, str]] = None,
tool_default: str = "skill") -> Dict[str, str]:
"""Return an HTTP-headers dict with SC-CALLER-ID filled in.
Resolution order:
1. `extra["SC-CALLER-ID"]` (case-insensitive) — caller wins.
2. STARCHILD_TOOL_CALLER_ID env (set by the agent)
3. Synthetic `f"{tool_default}:{int(time.time())}"` — tags the call so
charges are attributable to *some* identifier even when the agent
didn't inject one (standalone CLI runs, tests, cron).
"""
merged: Dict[str, str] = dict(extra or {})
has_caller = any(k.lower() == "sc-caller-id" for k in merged)
if not has_caller:
cid = os.environ.get("STARCHILD_TOOL_CALLER_ID") \
or f"{tool_default}:{int(time.time())}"
merged["SC-CALLER-ID"] = cid
return merged
def record_response(response,
request_url: str,
request_payload: Optional[Dict[str, Any]] = None,
api_type_hint: Optional[str] = None) -> None:
"""Inspect a sc-proxy response and append a ledger row when paid.
Best-effort. Silently no-ops when:
- response carries no X-Credits-Used / X-Credits-Api-Type
- cost is 0 or unparseable
- file write fails
Never raises — must not break a real request flow.
"""
try:
headers = getattr(response, "headers", None) or {}
used = headers.get("X-Credits-Used") or headers.get("x-credits-used")
api_type = (headers.get("X-Credits-Api-Type")
or headers.get("x-credits-api-type")
or api_type_hint)
if not used or not api_type:
return
try:
cost_f = float(used)
except (TypeError, ValueError):
return
if cost_f <= 0:
return
turn_id = os.environ.get("STARCHILD_USER_TURN_ID") or ""
caller_id = os.environ.get("STARCHILD_TOOL_CALLER_ID") or ""
host = ""
try:
host = urlparse(request_url).netloc or ""
except Exception:
pass
details: Dict[str, Any] = {}
if isinstance(request_payload, dict):
for k in _PAYLOAD_ALLOWLIST:
v = request_payload.get(k)
if v not in (None, "", []):
details[k] = v
# fal.ai puts the model in the URL path, not the body.
if "model" not in details and api_type == "falai":
try:
path = urlparse(request_url).path or ""
model_path = path.lstrip("/")
if "/requests/" in model_path:
model_path = model_path.split("/requests/", 1)[0]
if model_path and not model_path.startswith("requests/"):
details["model"] = model_path
details["provider"] = "fal"
except Exception:
pass
_append_ledger(
turn_id=turn_id,
caller_id=caller_id,
api_type=api_type,
cost_usd=cost_f,
url_host=host,
details=details or None,
)
except Exception:
# Never let cost tracking break the actual request.
pass
def _ledger_dir() -> Path:
base = os.environ.get("STARCHILD_COST_LEDGER_DIR") or _DEFAULT_LEDGER_DIR
p = Path(base)
try:
p.mkdir(parents=True, exist_ok=True)
except OSError:
p = Path("/tmp/starchild_cost_ledger")
p.mkdir(parents=True, exist_ok=True)
return p
def _today_path() -> Path:
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
return _ledger_dir() / f"{today}.jsonl"
def _derive_tool(caller_id: str, api_type: str) -> str:
"""Match starchild-clawd's _derive_tool_from_caller fallback."""
if not caller_id:
return api_type or "unknown"
# chat:{sid}/tool:{name} → name
if "/tool:" in caller_id:
return caller_id.rsplit("/tool:", 1)[-1] or api_type
# skill:{name} | job:{id} | video:{ts}
head = caller_id.split(":", 1)[0]
return head or api_type or "unknown"
def _append_ledger(*, turn_id: str, caller_id: str, api_type: str,
cost_usd: float, url_host: str,
details: Optional[Dict[str, Any]]) -> None:
row = {
"ts": round(time.time(), 3),
"turn_id": turn_id,
"caller_id": caller_id,
"tool": _derive_tool(caller_id, api_type),
"api_type": api_type or "unknown",
"cost_usd": round(cost_usd, 8),
"url_host": url_host or "",
}
if details:
row["details"] = details
line = json.dumps(row, ensure_ascii=False, separators=(",", ":")) + "\n"
path = _today_path()
try:
with open(path, "ab") as f:
try:
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
except OSError:
pass
try:
f.write(line.encode("utf-8"))
f.flush()
try:
os.fsync(f.fileno())
except OSError:
pass
finally:
try:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
except OSError:
pass
except OSError:
pass
"""
Image E-commerce skill exports — script-mode skill.
Usage from a bash block:
python3 - <<'EOF'
import sys
sys.path.insert(0, "/data/workspace/skills/image-ecommerce")
from exports import product_photo, product_photo_set, STYLE_PROMPTS, BACKGROUND_PROMPTS, PLATFORM_PRESETS
result = product_photo(
product_path="uploads/product.jpg",
style="hero",
background="white",
)
print(result)
EOF
"""
import os
import sys
# Ensure the skill directory is importable regardless of cwd.
_SKILL_DIR = os.path.dirname(os.path.abspath(__file__))
if _SKILL_DIR not in sys.path:
sys.path.insert(0, _SKILL_DIR)
from product_photo import ( # noqa: E402
product_photo,
product_photo_set,
STYLE_PROMPTS,
BACKGROUND_PROMPTS,
PLATFORM_PRESETS,
VALID_ASPECT_RATIOS,
MODELS,
)
__all__ = [
"product_photo",
"product_photo_set",
"STYLE_PROMPTS",
"BACKGROUND_PROMPTS",
"PLATFORM_PRESETS",
"VALID_ASPECT_RATIOS",
"MODELS",
]
#!/usr/bin/env python3
"""E-commerce product photography script — professional product images for all platforms.
Supports three models:
- nano2 (fal-ai/gemini-3.1-flash-image-preview/edit or /generate) — fastest ~15s, good for drafts
- nanopro (fal-ai/gemini-3-pro-image-preview/edit or /generate) — balanced ~25s, good quality (default)
- gpt (openai/gpt-image-2/edit or /generate) — best quality, slow ~150s
Modes:
- With product image (edit): transforms existing product photo with new background/style
- Without product image (text-to-image): generates product photo from description only
Flow: resolve image → build prompt → submit to fal queue → poll → download.
Cost tracking: uses _cost_track.py to record per-call costs via sc-proxy
headers so the agent's per-turn cost_summary picks up this skill's cost.
Local testing: set FAL_KEY env var to call fal.ai directly (no sc-proxy).
"""
import requests
import json
import time
import os
import sys
import base64
import mimetypes
from datetime import datetime
from pathlib import Path
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Make _cost_track importable when this script is invoked from any CWD.
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from _cost_track import caller_headers, record_response # noqa: E402
# Local testing: when FAL_KEY env var is set, call fal.ai directly
# (no sc-proxy). In production, sc-proxy injects the real key.
_FAL_KEY = os.environ.get("FAL_KEY")
_LOCAL_MODE = bool(_FAL_KEY)
PROXY_URL = 'http://sc-proxy.internal:8080'
PROXIES = {} if _LOCAL_MODE else {'http': PROXY_URL, 'https': PROXY_URL}
# ── Model configuration ──────────────────────────────────────────────
MODELS = {
"nano2": {
"edit": "fal-ai/gemini-3.1-flash-image-preview/edit",
"generate": "fal-ai/gemini-3.1-flash-image-preview",
"timeout": 90, # nano2 faster
"poll_interval": 2,
},
"nanopro": {
"edit": "fal-ai/gemini-3-pro-image-preview/edit",
"generate": "fal-ai/gemini-3-pro-image-preview",
"timeout": 120, # 2 min
"poll_interval": 3,
},
"gpt": {
"edit": "openai/gpt-image-2/edit",
"generate": "openai/gpt-image-2",
"timeout": 600, # 10 min
"poll_interval": 5,
},
}
DEFAULT_MODEL = "nanopro"
# Supported image extensions for local file validation
SUPPORTED_IMAGE_EXTS = {'.jpg', '.jpeg', '.png', '.webp', '.bmp'}
MAX_IMAGE_BYTES = 10 * 1024 * 1024 # 10 MB
# ── Style prompt templates ───────────────────────────────────────────
# Each style produces a distinct product photography aesthetic.
# Derived from: product-photography skill (shot types, lighting, composition),
# eachlabs-product-visuals (workflow patterns), image-create product category.
STYLE_PROMPTS = {
"hero": (
"professional product hero shot, clean composition, studio lighting, "
"commercial quality, e-commerce ready, product fills 80% of frame, "
"slight 15-30 degree angle for dimension, one hero light plus fill, "
"sharp focus on product, magazine advertisement quality"
),
"lifestyle": (
"product lifestyle photography, in-use context, natural setting, "
"warm lighting, aspirational feel, shallow depth of field, "
"editorial style, product naturally integrated into scene, "
"authentic atmosphere, storytelling composition"
),
"flat_lay": (
"flat lay product photography, top-down bird's eye view, "
"organized arrangement on clean surface, Instagram-worthy composition, "
"soft overhead lighting, minimal props, coordinated color palette, "
"negative space for text overlay, catalog quality"
),
"detail": (
"product detail close-up, macro photography, texture and material visible, "
"sharp focus, high resolution, extreme close-up showing craftsmanship, "
"soft directional lighting highlighting texture, luxury product photography, "
"shallow depth of field, editorial quality"
),
"packaging": (
"product packaging photography, box and product together, unboxing feel, "
"clean presentation, slight angle showing depth, "
"studio lighting with subtle shadows, premium unboxing experience, "
"brand packaging visible, commercial catalog quality"
),
"group": (
"product group shot, multiple items arranged together, cohesive styling, "
"catalog quality, triangle composition for balance, "
"soft overhead lighting, coordinated brand aesthetic, "
"odd number arrangement, clean background, collection display"
),
"scale": (
"product with scale reference, size comparison with everyday object, "
"clear proportions visible, product held in hand or next to known object, "
"clean blurred background, natural lighting, "
"lifestyle tech photography, informative composition"
),
"seasonal_spring": (
"product in spring setting, cherry blossoms, fresh green leaves, "
"soft pastel colors, seasonal marketing, gentle natural light, "
"floral elements, renewal theme, bright airy atmosphere, "
"spring campaign photography"
),
"seasonal_summer": (
"product in summer setting, beach vibes, bright sunshine, "
"tropical elements, summer campaign, vivid saturated colors, "
"outdoor lifestyle, vacation aesthetic, warm golden light, "
"energetic summer mood"
),
"seasonal_autumn": (
"product in autumn setting, fall leaves in warm golden tones, "
"cozy atmosphere, harvest theme, warm amber lighting, "
"rustic natural elements, comfortable seasonal mood, "
"autumn campaign photography, rich earth tones"
),
"seasonal_winter": (
"product in winter setting, snow, holiday decorations, "
"warm indoor lighting, festive mood, cozy winter atmosphere, "
"soft warm glow, seasonal marketing, winter wonderland aesthetic, "
"holiday campaign photography"
),
"360_view": (
"product 360 degree view, multiple angles shown in one image, "
"turntable style presentation, pure white background, "
"consistent studio lighting across all angles, "
"front side back views, product rotation display, "
"e-commerce multi-angle showcase"
),
"comparison": (
"product comparison layout, side by side arrangement, "
"before and after or feature highlight, clean dividing line, "
"consistent lighting and scale, informative composition, "
"clear visual differentiation, comparison marketing image"
),
"infographic": (
"product infographic style, features labeled with callout arrows, "
"clean design, informative layout, key specifications highlighted, "
"dimensions and measurements shown, what's included display, "
"professional technical illustration, marketing infographic"
),
}
# ── Background prompt templates ───────────────────────────────────────
# Derived from product-photography skill background guide.
BACKGROUND_PROMPTS = {
"white": "pure white background #FFFFFF, clean, e-commerce standard, no shadows",
"gradient": "soft gradient background, subtle color transition from white to light grey, modern feel",
"studio": "professional studio setup, controlled lighting, neutral tones, subtle contact shadow",
"natural": "natural environment, outdoor setting, organic feel, soft natural light",
"lifestyle": "lifestyle context, home or office setting, in-use scenario, warm atmosphere",
"colored": "solid colored background, vibrant, brand-matching, clean and bold",
"textured": "textured background surface, marble or wood or fabric, premium feel, subtle texture",
"transparent": "transparent background, product cutout, PNG ready, clean edges",
}
# ── Platform-specific presets ─────────────────────────────────────────
# E-commerce platform image requirements derived from product-photography skill.
PLATFORM_PRESETS = {
"amazon": {
"aspect_ratio": "1:1",
"background": "white",
"style": "hero",
"notes": "Pure white bg (RGB 255,255,255), product fills 85%+, no props/text/watermarks, min 1000px (1600px+ recommended)",
},
"shopify": {
"aspect_ratio": "1:1",
"background": "white",
"style": "hero",
"notes": "Square format preferred, consistent style across catalog, 2048x2048 recommended",
},
"taobao": {
"aspect_ratio": "1:1",
"background": "white",
"style": "hero",
"notes": "800x800 minimum, white background for main image, lifestyle for secondary",
},
"instagram": {
"aspect_ratio": "1:1",
"background": "lifestyle",
"style": "lifestyle",
"notes": "1080x1080 for feed, lifestyle context preferred, visually appealing",
},
"xiaohongshu": {
"aspect_ratio": "3:4",
"background": "lifestyle",
"style": "flat_lay",
"notes": "1080x1440 vertical format, aesthetic flat lay or lifestyle, text overlay space",
},
"etsy": {
"aspect_ratio": "4:3",
"background": "natural",
"style": "lifestyle",
"notes": "Handmade/artisan feel, natural backgrounds, show craftsmanship",
},
"ebay": {
"aspect_ratio": "1:1",
"background": "white",
"style": "hero",
"notes": "White background, clear product view, 1600px minimum for zoom",
},
}
# ── Constants ─────────────────────────────────────────────────────────
MAX_COUNT = 4 # fal.ai API supports up to 4 images per call
VALID_ASPECT_RATIOS = {
"1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9",
}
DEFAULT_ASPECT_RATIO = "1:1"
VALID_OUTPUT_FORMATS = {"jpeg", "png", "webp"}
DEFAULT_OUTPUT_FORMAT = "png"
OUTPUT_DIR = "output/images"
def _get_auth_key():
"""Return the appropriate fal API key."""
return _FAL_KEY if _LOCAL_MODE else 'fake-falai-key-12345'
def _get_model_config(model_key):
"""Return model config dict for the given key."""
return MODELS.get(model_key, MODELS[DEFAULT_MODEL])
def _resolve_image(image_path=None, image_url=None):
"""Resolve an image input to a URL for the fal API.
Accepts either a local file path or a public URL.
Local files are base64-encoded as data URIs.
Returns (url_string, error_string).
"""
if not image_path and not image_url:
return None, None # No image = text-to-image mode
if image_path:
p = Path(image_path)
if not p.exists():
return None, f"File not found: {image_path}"
if not p.is_file():
return None, f"Not a file: {image_path}"
ext = p.suffix.lower()
if ext not in SUPPORTED_IMAGE_EXTS:
return None, (
f"Unsupported image format: {ext}. "
f"Supported: {', '.join(sorted(SUPPORTED_IMAGE_EXTS))}"
)
size = p.stat().st_size
if size > MAX_IMAGE_BYTES:
return None, (
f"Image too large: {size / 1024 / 1024:.1f} MB "
f"(max {MAX_IMAGE_BYTES / 1024 / 1024:.0f} MB)"
)
mime_type = mimetypes.guess_type(str(p))[0] or "image/jpeg"
with open(p, 'rb') as f:
b64 = base64.b64encode(f.read()).decode('ascii')
return f"data:{mime_type};base64,{b64}", None
# URL input
if not image_url.startswith(("http://", "https://")):
return None, (
"image_url must be a public HTTP(S) URL. "
"For local files, use the product_path parameter instead."
)
return image_url, None
def _build_product_prompt(prompt=None, style="hero", background="white"):
"""Construct the product photography prompt from style + background + user prompt.
Priority:
1. prompt provided → use as primary, enhance with style/background context
2. no prompt → combine style template + background template
Returns the final prompt string.
"""
style_text = STYLE_PROMPTS.get(style, STYLE_PROMPTS["hero"])
bg_text = BACKGROUND_PROMPTS.get(background, BACKGROUND_PROMPTS["white"])
if prompt:
# User provided a custom prompt — enhance with style and background context
return (
f"{prompt}. "
f"Photography style: {style_text}. "
f"Background: {bg_text}."
)
else:
# No custom prompt — use style + background templates
return f"{style_text}, {bg_text}"
def _build_edit_prompt(prompt=None, style="hero", background="white"):
"""Build prompt for edit mode (with product image input).
Wraps the product prompt with instructions to preserve the product
while applying the desired style and background.
"""
base_prompt = _build_product_prompt(prompt, style, background)
return (
f"Transform this product image into a professional e-commerce photo. "
f"Keep the product exactly as it is — preserve its shape, color, details, "
f"and branding. {base_prompt}"
)
def _aspect_ratio_to_size(aspect_ratio):
"""Convert aspect ratio string to fal image_size dict.
Sizes aligned with image_generate tool capabilities
(core/image_models.py _STD_ASPECTS / _NANO2_ASPECTS).
"""
mapping = {
"1:1": {"width": 1024, "height": 1024},
"2:3": {"width": 680, "height": 1024},
"3:2": {"width": 1024, "height": 680},
"3:4": {"width": 768, "height": 1024},
"4:3": {"width": 1024, "height": 768},
"4:5": {"width": 816, "height": 1024},
"5:4": {"width": 1024, "height": 816},
"9:16": {"width": 576, "height": 1024},
"16:9": {"width": 1024, "height": 576},
"21:9": {"width": 1024, "height": 440},
}
return mapping.get(aspect_ratio, mapping["1:1"])
def _build_request_body(prompt, image_url=None, aspect_ratio="1:1", model_key="nanopro",
count=1, output_format="png"):
"""Build the request body for the fal API."""
body = {
"prompt": prompt,
"num_images": count,
"seed": int(time.time() * 1000) % (2**32),
"output_format": output_format,
}
# Add image URL for edit mode
if image_url:
body["image_urls"] = [image_url]
# nano2/nanopro use aspect_ratio string; gpt uses image_size object
if aspect_ratio and aspect_ratio in VALID_ASPECT_RATIOS:
if model_key != "gpt":
body["aspect_ratio"] = aspect_ratio
else:
body["image_size"] = _aspect_ratio_to_size(aspect_ratio)
body["quality"] = "high"
return body
def _submit_request(prompt, image_url, model_key, headers, aspect_ratio="1:1",
count=1, output_format="png"):
"""Submit a request to the fal queue (edit or generate mode)."""
cfg = _get_model_config(model_key)
# Choose edit vs generate endpoint based on whether we have an image
if image_url:
model_id = cfg["edit"]
else:
model_id = cfg["generate"]
submit_url = f"https://queue.fal.run/{model_id}"
body = _build_request_body(prompt, image_url, aspect_ratio, model_key,
count=count, output_format=output_format)
resp = requests.post(
submit_url, headers=headers, json=body,
proxies=PROXIES, verify=False, timeout=90,
)
record_response(resp, request_url=submit_url, request_payload=body)
if resp.status_code != 200:
return None, f"Submit failed: {resp.status_code} - {resp.text[:300]}"
data = resp.json()
cost = float(resp.headers.get('X-Credits-Used', 0))
data['_cost'] = cost
return data, None
def _poll_until_done(status_url, request_id, model_key):
"""Poll the fal queue until the request completes or fails."""
cfg = _get_model_config(model_key)
headers = {'Authorization': f'Key {_get_auth_key()}'}
deadline = time.time() + cfg["timeout"]
poll_interval = cfg["poll_interval"]
while time.time() < deadline:
try:
poll_resp = requests.get(
status_url, headers=headers,
proxies=PROXIES, verify=False, timeout=60,
)
status_data = poll_resp.json()
status = status_data.get('status')
if status == 'COMPLETED':
return "COMPLETED", None
elif status in ('FAILED', 'CANCELLED'):
return status, f"Generation {status}"
except requests.RequestException:
pass
time.sleep(poll_interval)
return "TIMEOUT", f"Generation timed out after {cfg['timeout'] // 60} minutes"
def _extract_image_urls(result_json):
"""Extract image URLs from fal response across model variants."""
if not isinstance(result_json, dict):
return []
urls = []
for key in ("images", "output", "outputs", "data"):
arr = result_json.get(key)
if isinstance(arr, list):
for item in arr:
if isinstance(item, dict) and isinstance(item.get("url"), str):
urls.append(item["url"])
elif isinstance(item, dict) and isinstance(item.get("b64_json"), str):
urls.append(f"data:image/png;base64,{item['b64_json']}")
elif isinstance(item, str) and item.startswith("http"):
urls.append(item)
if not urls:
for key in ("image", "output_image"):
node = result_json.get(key)
if isinstance(node, dict) and isinstance(node.get("url"), str):
urls.append(node["url"])
elif isinstance(node, str) and node.startswith("http"):
urls.append(node)
return urls
def _download_image(url, index, label, timestamp):
"""Download a single image from fal CDN to the output directory."""
os.makedirs(OUTPUT_DIR, exist_ok=True)
if url.startswith("data:"):
ext = ".png"
filename = f"{timestamp}_{label}_{index}{ext}"
local_path = os.path.join(OUTPUT_DIR, filename)
b64_data = url.split(",", 1)[1]
img_bytes = base64.b64decode(b64_data)
with open(local_path, 'wb') as f:
f.write(img_bytes)
return local_path, len(img_bytes)
ext = ".png"
if ".jpg" in url or ".jpeg" in url:
ext = ".jpg"
elif ".webp" in url:
ext = ".webp"
filename = f"{timestamp}_{label}_{index}{ext}"
local_path = os.path.join(OUTPUT_DIR, filename)
resp = requests.get(url, timeout=120)
resp.raise_for_status()
with open(local_path, 'wb') as f:
f.write(resp.content)
return local_path, len(resp.content)
def product_photo(
product_path=None,
product_url=None,
prompt=None,
style="hero",
background="white",
model="nanopro",
count=1,
aspect_ratio="1:1",
platform=None,
output_format=None,
):
"""Generate professional e-commerce product photography.
This is the primary function for all product photography operations.
Supports both edit mode (with product image) and generate mode (text-only).
Args:
product_path: Local workspace file path to the product image.
product_url: Public HTTPS URL of the product image.
prompt: Custom prompt describing the desired product photo.
When provided with a style, enhances the style template.
When provided alone, used as the primary instruction.
style: Photography style preset — one of:
hero, lifestyle, flat_lay, detail, packaging, group, scale,
seasonal_spring, seasonal_summer, seasonal_autumn, seasonal_winter,
360_view, comparison, infographic.
background: Background type — one of:
white, gradient, studio, natural, lifestyle, colored, textured, transparent.
model: Model key — "nanopro" (default, fast ~25s) or
"gpt" (best quality ~150s).
count: Number of images to generate (1-4).
Uses fal.ai native num_images for efficient batch generation.
aspect_ratio: Output ratio — "1:1", "3:4", "4:3", "9:16", "16:9".
platform: E-commerce platform preset — "amazon", "shopify", "taobao",
"instagram", "xiaohongshu", "etsy", "ebay".
When set, overrides style, background, and aspect_ratio with
platform-optimized defaults (unless explicitly provided).
output_format: Output image format — "png" (default), "jpeg", or "webp".
Returns:
dict with keys:
success (bool): Whether generation succeeded.
images (list[dict]): List of generated images, each with:
local_path (str): Path to downloaded file.
url (str): Original fal CDN URL.
size_bytes (int): File size.
model (str): Model used.
style (str): Style applied.
background (str): Background applied.
platform (str|None): Platform preset used.
mode (str): "edit" or "generate".
cost (float): Total credits used.
error (str|None): Error message if failed.
"""
# ── Validate model ────────────────────────────────────────────────
model_key = model.lower() if model else DEFAULT_MODEL
if model_key not in MODELS:
return {
"success": False,
"error": f"Unknown model: {model}. Use 'nanopro' or 'gpt'.",
"images": [],
}
# ── Apply platform preset ─────────────────────────────────────────
platform_info = None
if platform:
platform_key = platform.lower()
if platform_key in PLATFORM_PRESETS:
preset = PLATFORM_PRESETS[platform_key]
platform_info = preset
# Platform overrides defaults but not explicit user choices
if style == "hero": # default value = not explicitly set
style = preset["style"]
if background == "white": # default value = not explicitly set
background = preset["background"]
if aspect_ratio == "1:1": # default value = not explicitly set
aspect_ratio = preset["aspect_ratio"]
# ── Validate style and background ─────────────────────────────────
if style not in STYLE_PROMPTS:
return {
"success": False,
"error": f"Unknown style: {style}. Available: {', '.join(sorted(STYLE_PROMPTS.keys()))}",
"images": [],
}
if background not in BACKGROUND_PROMPTS:
return {
"success": False,
"error": f"Unknown background: {background}. Available: {', '.join(sorted(BACKGROUND_PROMPTS.keys()))}",
"images": [],
}
# ── Validate aspect ratio ─────────────────────────────────────────
if aspect_ratio not in VALID_ASPECT_RATIOS:
return {
"success": False,
"error": f"Invalid aspect_ratio: {aspect_ratio}. Use: {', '.join(sorted(VALID_ASPECT_RATIOS))}",
"images": [],
}
# ── Validate count and output format ──────────────────────────────
count = max(1, min(MAX_COUNT, int(count)))
fmt = output_format if output_format in VALID_OUTPUT_FORMATS else DEFAULT_OUTPUT_FORMAT
# ── Resolve product image ─────────────────────────────────────────
image_url, err = _resolve_image(product_path, product_url)
if err:
return {"success": False, "error": err, "images": []}
# Determine mode: edit (with image) or generate (text-only)
has_image = image_url is not None
mode = "edit" if has_image else "generate"
# ── Build prompt ──────────────────────────────────────────────────
if has_image:
final_prompt = _build_edit_prompt(prompt, style, background)
else:
if not prompt:
return {
"success": False,
"error": "Either a product image (product_path/product_url) or a prompt describing the product is required.",
"images": [],
}
final_prompt = _build_product_prompt(prompt, style, background)
# ── Generate images ───────────────────────────────────────────────
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
all_images = []
total_cost = 0.0
errors = []
headers = {
'Authorization': f'Key {_get_auth_key()}',
'Content-Type': 'application/json',
}
headers.update(caller_headers(tool_default="image-ecommerce"))
# Single API call with num_images=count for efficient batch generation
submit_data, submit_err = _submit_request(
final_prompt, image_url, model_key, headers, aspect_ratio,
count=count, output_format=fmt,
)
if submit_err:
return {"success": False, "error": submit_err, "images": []}
request_id = submit_data.get('request_id')
status_url = submit_data.get('status_url')
response_url = submit_data.get('response_url')
total_cost += submit_data.get('_cost', 0)
if not request_id or not status_url:
return {"success": False, "error": "Missing request_id or status_url in response", "images": []}
# Poll until done
status, poll_err = _poll_until_done(status_url, request_id, model_key)
if status != "COMPLETED":
return {"success": False, "error": poll_err or status, "images": []}
# Fetch result
try:
result_headers = {'Authorization': f'Key {_get_auth_key()}'}
result_resp = requests.get(
response_url, headers=result_headers,
proxies=PROXIES, verify=False, timeout=60,
)
result_json = result_resp.json()
except Exception as e:
return {"success": False, "error": f"Failed to fetch result: {e}", "images": []}
# Extract and download images
img_urls = _extract_image_urls(result_json)
if not img_urls:
return {"success": False, "error": "No images in response", "images": []}
for j, img_url in enumerate(img_urls):
try:
label = f"product_{style}"
local_path, size_bytes = _download_image(
img_url, j, label, timestamp,
)
all_images.append({
"local_path": local_path,
"url": img_url if not img_url.startswith("data:") else "(base64)",
"size_bytes": size_bytes,
})
except Exception as e:
errors.append(f"Download failed: {e}")
# ── Build result ──────────────────────────────────────────────────
result = {
"success": len(all_images) > 0,
"images": all_images,
"model": model_key,
"style": style,
"background": background,
"platform": platform,
"mode": mode,
"output_format": fmt,
"count_requested": count,
"count_generated": len(all_images),
"cost": round(total_cost, 6),
}
if errors:
result["error"] = "; ".join(errors)
if platform_info:
result["platform_notes"] = platform_info.get("notes", "")
return result
def product_photo_set(
product_path=None,
product_url=None,
prompt=None,
platform="amazon",
model="nanopro",
):
"""Generate a complete e-commerce product image set (7-9 images).
Creates a full set of product images following e-commerce best practices:
1. Hero / packshot (white background)
2. Lifestyle (product in use)
3. Detail close-up (material/texture)
4. Scale reference (size comparison)
5. Alternate angle (side/back view)
6. Packaging (unboxing)
7. Group/collection (if applicable)
Args:
product_path: Local file path to the product image.
product_url: Public URL of the product image.
prompt: Description of the product (used to enhance all shots).
platform: Target platform for image specs.
model: Model key — "nanopro" or "gpt".
Returns:
dict with keys:
success (bool): Whether at least some images were generated.
sets (list[dict]): Each set item has shot_type, style, result.
platform (str): Target platform.
total_images (int): Total images generated.
total_cost (float): Total credits used.
errors (list[str]): Any errors encountered.
"""
# Define the shot sequence
shots = [
{"shot_type": "hero", "style": "hero", "background": "white",
"desc": "Primary listing image"},
{"shot_type": "lifestyle", "style": "lifestyle", "background": "lifestyle",
"desc": "Product in use/context"},
{"shot_type": "detail", "style": "detail", "background": "studio",
"desc": "Material/texture close-up"},
{"shot_type": "scale", "style": "scale", "background": "studio",
"desc": "Size reference"},
{"shot_type": "alternate_angle", "style": "hero", "background": "white",
"desc": "Side/back view"},
{"shot_type": "packaging", "style": "packaging", "background": "studio",
"desc": "Packaging/unboxing"},
{"shot_type": "flat_lay", "style": "flat_lay", "background": "textured",
"desc": "Flat lay arrangement"},
]
# Get platform preset for aspect ratio
platform_key = (platform or "amazon").lower()
preset = PLATFORM_PRESETS.get(platform_key, PLATFORM_PRESETS["amazon"])
ar = preset["aspect_ratio"]
results = []
total_cost = 0.0
total_images = 0
errors = []
for shot in shots:
# Build shot-specific prompt
shot_prompt = prompt
if prompt:
shot_prompt = f"{prompt}, {shot['desc']}"
# For alternate angle, add specific angle instruction
if shot["shot_type"] == "alternate_angle":
angle_extra = "three-quarter back view, showing the product from a different angle"
shot_prompt = f"{shot_prompt}, {angle_extra}" if shot_prompt else angle_extra
result = product_photo(
product_path=product_path,
product_url=product_url,
prompt=shot_prompt,
style=shot["style"],
background=shot["background"],
model=model,
count=1,
aspect_ratio=ar,
)
shot_result = {
"shot_type": shot["shot_type"],
"description": shot["desc"],
"style": shot["style"],
"background": shot["background"],
"result": result,
}
results.append(shot_result)
if result.get("success"):
total_images += len(result.get("images", []))
total_cost += result.get("cost", 0)
else:
errors.append(f"{shot['shot_type']}: {result.get('error', 'Unknown error')}")
return {
"success": total_images > 0,
"sets": results,
"platform": platform,
"total_images": total_images,
"total_cost": round(total_cost, 6),
"errors": errors if errors else None,
}Related skills
How it compares
Pick image-ecommerce over generic image generators when output must match marketplace listing formats and product-photography conventions.
FAQ
What does image-ecommerce do?
E-commerce product photography: white-background hero shots, lifestyle scenes, flat lay, detail close-ups, packaging shots, group/collection displays, scale references,.
When should I use image-ecommerce?
User asks about image ecommerce or related SKILL.md workflows.
Is image-ecommerce safe to install?
Review the Security Audits panel on this page before installing in production.