---
title: "Catalog Photo System — product packshots + clean grid export"
tier: hero
format: image
theme: commerce | packshot | catalog
persona: e-commerce manager, brand marketer, marketplace ops
duration: "~10–25 min depending on count"
budget_usd: "~$0.29 for 12 items (Pixelcut @ $0.024/image) + finishing; scales linearly"
caps: ["pixelcut-product-photo", "ffmpeg-grid", "ffmpeg-export"]
skills: ["pixelcut-product-photo", "finishing-quality", "commercial"]
showcases: ["/chapters/catalog-photo-system-example.html"]
status: "live"
reliability: 3.7 # Pixelcut is stable; largest risk is inconsistent source photos
---

# Catalog Photo System — agent playbook

> Paste this whole file (with the BRIEF filled in) into Claude cowork, chat, or Code. Livepeer Agent turns raw product photos into consistent catalog packshots and ships a grid you can paste into a listing doc or send to a stakeholder.

## Read this before the first call: Pixelcut takes NO prompt

`pixelcut-product-photo` has no `prompt` field. Its entire control surface is
**structured objects** — `background`, `margin`, `shadow`, `watermark`,
`image_size`, `output_format`. Writing "pure white background, soft shadow" as
prose does nothing: the string is discarded upstream and you are billed for a
render that ignored every word of it.

So art direction in this playbook is expressed as parameters:

| You want | Pass |
|---|---|
| White studio backdrop | `background: { mode: "Color", color: { r: 255, g: 255, b: 255 } }` |
| Transparent cutout (PNG) | `background: { mode: "Transparent" }` + `output_format: "png"` |
| A lifestyle/scene backdrop | `background: { mode: "Image", image_url: "<backdrop>", image_fit: "Cover" }` |
| Breathing room around the product | `margin: { all: "64px" }` |
| Grounded soft shadow | `shadow: { type: "Generative", generative: { opacity: 0.2 } }` |
| Brand mark on the packshot | `watermark: { image_url: "<logo>", position: "bottom_right" }` |
| Fixed catalog dimensions | `image_size: { width: 1600, height: 1600 }` |

Transparent cutouts come from `background: { mode: "Transparent" }` — a separate
`bg-remove` pass is redundant and doubles the cost. Keep `bg-remove` in mind only
as a **fallback** if Pixelcut is unavailable; it re-mattes the finished packshot
but has none of the margin/shadow/size controls.

## What you'll get

- **N cleaned packshots** — consistent backdrop, margin and shadow; product identity preserved
- **Optional transparent PNG cutouts** — from the same call, not a second one
- **A 1-second MP4 contact sheet** — `ffmpeg-grid` stitches 2–16 images into a review grid (video output)
- **Optional social export** — `ffmpeg-export` turns the MP4 grid into a platform preset (e.g. TikTok)

## Tell the agent about the catalog

```yaml
product_image_urls:    # comma-separated https URLs (or @last_image for one)
background:            # "white" (default) | "transparent" | "keep" | an https backdrop URL
margin_px: 64          # padding around the product; 0 for edge-to-edge
shadow:                # "contact" (default) | "none"
watermark_url:         # optional logo URL; omit for none
output_format: png     # "png" (supports transparency) or "jpeg" (smaller)
grid_layout:           # "3x4" (default), "2x3", "4x4"
max_spend_usd: 2.00    # hard ceiling
output_slug:           # kebab-case filename prefix
```

## Conventions (how the agent must run)

1. **Spend gate**: estimate cost = `0.024 × N` for Pixelcut; STOP before running if it could exceed `max_spend_usd`. No second matting pass is needed, so N images cost N × $0.024.
2. **Identity gate**: process **one** sample image first, STOP for approval, then batch the rest with the identical parameter block.
3. **Final review gate**: STOP before exporting the grid.
4. **No auto-fallback**: `pixelcut-product-photo` deliberately has no fallback chain — every sibling edit cap is prompt-led and would silently drop the whole parameter block. On failure, report it and ask; don't reroute.

## The stages

### Stage 0 — Confirm + count (no render)

Parse `product_image_urls` into a list and report **N**. Translate the BRIEF into
the exact Pixelcut parameter block and echo it back, so the user reviews the real
inputs rather than a paraphrase.

**STOP**: “Proceed with N items and these parameters? (approve / edit [field])”

### Stage 1 — One “golden packshot” sample

Run `pixelcut-product-photo` on the first product image with the agreed block:

```ts
mcp__livepeer-agent__run_capability({
  capability: "pixelcut-product-photo",
  inputs: {
    image_url: "<first product URL>",
    background: { mode: "Color", color: { r: 255, g: 255, b: 255 } },
    margin: { all: "64px" },
    shadow: { type: "Generative", generative: { opacity: 0.2 } },
    output_format: "png",
    sync_mode: false,
  },
})
```

**STOP**: “Sample looks right? (approve / adjust [parameter] / abort)”

Adjustments are parameter edits — bigger `margin`, omit `shadow` for no shadow,
or choose a different RGB `background.color`. There is no wording to tweak.

### Stage 2 — Batch process all products

Re-run the **byte-identical** parameter block for the remaining images, changing
only `image_url`. Reusing the approved block is what makes the catalog look
consistent; drifting parameters per item is the main cause of a mismatched grid.

**STOP**: “Batch complete. Any item to redo? (approve / redo [index])”

### Stage 3 — Transparent cutouts (same call, no extra cap)

If the BRIEF asked for transparency, it was already set in Stage 1 via
`background: { mode: "Transparent" }` + `output_format: "png"`. Don't run a
second matting pass.

If you need BOTH a white packshot and a transparent cutout of the same product,
that's two Pixelcut calls per item — budget `0.048 × N` and say so at the spend
gate.

### Stage 4 — Deterministic normalization (size + format)

Skip normalization unless the user names a platform constraint. Pixelcut already
sizes the output via `image_size`; unnecessary resizing afterwards often makes
labels softer.

### Stage 5 — Build the catalog grid

Build one (or more) grid sheets using `ffmpeg-grid`. The agent must:

- Parse `grid_layout` like `"3x4"` → columns = 3 (rows are implied by the item count).
- Use the *finished packshots* as the grid inputs (or the transparent cutouts if requested).
- Be explicit that `ffmpeg-grid` outputs an MP4: for image-only grids, it becomes a 1-second still-video.
- If N exceeds the grid capacity, produce multiple pages (page-1, page-2).

**STOP**: “Grid looks good to ship? (approve / change grid_layout)”

### Stage 6 — Deliver

Return:
- The final packshot URLs (and transparent PNGs if requested)
- The grid sheet URL(s)
- A short note on what to redo when a product looks inconsistent (“retake source: flat lighting, avoid glare”)

## FINAL OUTPUT

Print exactly:

```
✅ Packshots (N):              <URL × N>
✅ Transparent PNGs (optional): <URL × N>
✅ Catalog grid sheet(s):      <URL × pages>

Total spent: $X.XX (ceiling $max_spend_usd)
```

## AFTERCARE

- If a single product looks “off”: redo only that item with adjusted parameters (larger `margin`, or omit `shadow`) — don't re-run the whole batch.
- If the grid looks inconsistent: check that every item used the same parameter block. That, not the model, is almost always the cause.
- If you need a vertical social export: export the MP4 grid with `preset:"tiktok-portrait"` (it’s still a still-video, just sized correctly).
- If Pixelcut is unavailable: `bg-remove` is the explicit manual fallback for cutouts only — it cannot reproduce margin, shadow, sizing or watermark, so say what's missing rather than presenting it as equivalent.

## Notes for the agent (exact calls)

- Pixelcut: `mcp__livepeer-agent__run_capability({ capability:"pixelcut-product-photo", inputs:{ image_url, background, margin, shadow, watermark?, image_size?, output_format, sync_mode:false } })` — **no `prompt` argument**
- Grid: `mcp__livepeer-agent__run_capability({ capability:"ffmpeg-grid", inputs:{ items:[...], columns: 3, tile_width: 540, gap_px: 24, background: "black" } })`
- Vertical export (only when requested): `mcp__livepeer-agent__run_capability({ capability:"ffmpeg-export", inputs:{ source_url: "<grid_mp4_url>", preset:"tiktok-portrait", crop:"center-crop" } })`
