One product photo, five ad shapes: Flux 2 Pro $0.1875, Qwen $0.125
Five shapes (1:1, 4:5, 9:16, 16:9, 21:9) from one photo: $0.1875 on Flux 2 Pro, $0.125 on Qwen Image, $0.50 on Nano Banana 2.1. Seedream 5 Lite lacks 21:9.

Five ad shapes from one product photo (1:1, 4:5, 9:16, 16:9 and 21:9) cost $0.1875 on Flux 2 Pro, $0.125 on Qwen Image and $0.50 on Nano Banana 2.1 on Sume, one call per shape. Seedream 5.0 Lite lists the first four shapes but not 21:9, so it covers four of the five for $0.175. The prices are the billed endpoint figures from origin/main, read 2026-10-08.
This is regeneration, not cropping. Each call asks the model to stage the product again for the new frame, so the composition, background and sometimes small label details change from shape to shape. Check each result against the original before it goes into an ad.
Cost by row
Each row below takes up to 10 reference images. The count is the number of the five shapes the row lists.
| Model | Per image | Shapes listed (of 5) | Cost for 5 |
|---|---|---|---|
| Qwen Image | $0.025 | 5 | $0.125 |
| Flux 2 Pro | $0.0375 | 5 | $0.1875 |
| Seedream 5.0 Lite | $0.04375 | 4 (no 21:9) | $0.175 for 4 |
| Nano Banana 2.1 | $0.10 | 5 | $0.50 |
The loop
Each call passes the same reference and names its own aspect_ratio. Leaving aspect_ratio off is not the same as auto, and a shape you want should be named. The script prints the status and moves on if a call does not return 200; a 202 means the job is still running and its result is fetched from the job.
import os, requests
PHOTO = "https://example.com/tin.jpg"
RATIOS = ["1:1", "4:5", "9:16", "16:9", "21:9"]
headers = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
total = 0.0
for ratio in RATIOS:
body = {
"model": "black-forest-labs/flux.2-pro",
"prompt": "Same tin, same label, restaged for this frame shape",
"input_references": [{"type": "image_url", "image_url": {"url": PHOTO}}],
"aspect_ratio": ratio,
}
r = requests.post("https://api.sume.com/v1/images", json=body,
headers=headers, timeout=60)
if r.status_code != 200:
print(ratio, "status", r.status_code)
continue
out = r.json()
total += out["usage"]["cost"]
print(ratio, out["data"][0]["url"])
print(f"total ${total:.4f}")
Keeping the set consistent
Same wording across all five prompts, with only the framing instruction changing, gives the most similar set. Tall 9:16 and wide 21:9 frames are the shapes where the product is most likely to shrink or move, so say in the prompt where it sits and roughly how large.
A cheaper first pass on Qwen Image costs $0.125 for the set. If the results hold up for your product, you do not need the dearer row.
Run the five calls one after another while you are testing, then switch to a small number in parallel once the prompt is stable. Each call still waits up to 30 seconds before Sume hands back a job envelope, so a timeout of 60 seconds in the client is enough to see either outcome.
Keep the original photo URL public for the duration of the batch. Sume fetches reference URLs from the public internet and refuses localhost, private-network and non-HTTPS addresses before it submits the work.
Sources
Related posts
More in Use cases
- Open-house reel from 12 listing photos: one Timeline render, $0.10
12 listing photos at 3 seconds each make a 36-second vertical reel in one Sume Timeline render: one voice spine, fades, blur fit, ceil(36/60) = 1 minute, $0.10.
- A real person's photo avatar reading a script: YouTube disclosure
A Sume avatar made from a real person's photo and given a script can show them saying words they never said, which YouTube says needs a disclosure label.
- Photographer portfolio reel from stills with Omni Flash
Turn 10 portfolio photos into a 60-second reel with Gemini Omni Flash: six seconds each, $7.50 at 720p on Sume, with 360p drafts first at $2.25.
- Pinterest video titles show 40 characters: burn the hook in
Pinterest shows the first 40 characters of a video ad title in the feed (30 for CJK) and hides the description. Burn the hook in with Sume captions.
Written by Sume