{"id":"create-image-gpt-image-fal","name":"create-image-gpt-image-fal","summary":"OpenAIのGPT画像で、fal.ai を通じて単一のフォトリアルまたはデザイン画像を生成できます。","body":"# create-image-gpt-image-fal\n\n## Purpose\n\nGenerate one image via fal.ai's OpenAI gpt-image endpoints. Two model families are supported through a single `--model` flag:\n\n- **`gpt-image-1`** (default) — `fal-ai/gpt-image-1`. The FAL fallback for Higgsfield's `gpt_image_2`. Fixed output sizes only. Used by:\n  - `video-orchestrator/lock-character` Phase 0 (anchor portrait) and Phase 1 (angle keyframes via `/edit`)\n  - `video-orchestrator/create-clips` Phase 1 for photoreal scenes\n  - the orchestrator's `generate_with_fallback.py` router on Higgsfield failure\n- **`gpt-image-2`** — `openai/gpt-image-2`. The newer model; accepts **custom output sizes** (any multiple of 16, up to 3840px) and renders dense text/layouts well. Used for designed sheets such as ad storyboards (`create-storyboard-sheets-fal`).\n\nThe default stays `gpt-image-1` so existing callers and the lock-character anchor-parity contract are unaffected. Opt into the newer model with `--model gpt-image-2`.\n\n## Pricing (approximate, as of 2026-05)\n\n- **gpt-image-1** — $0.04 (low), $0.08 (medium), $0.20 (high) per image. Source: [fal.ai/models/fal-ai/gpt-image-1](https://fal.ai/models/fal-ai/gpt-image-1).\n- **gpt-image-2** — token-priced; rough per-image estimate $0.02 (low), $0.07 (medium), $0.19 (high). Source: [fal.ai/models/openai/gpt-image-2](https://fal.ai/models/openai/gpt-image-2).\n\nThe script defaults to `medium`; pass `--quality high` for finals.\n\n## Inputs\n\nRequired:\n- `--prompt` — text prompt. A verbatim character descriptor block goes here for character work.\n- `--output` — local PNG destination.\n\nOptional:\n- `--model` — `gpt-image-1` (default) or `gpt-image-2`.\n- `--aspect-ratio` — `9:16` (default), `16:9`, `1:1`, `2:3`, `3:2`. gpt-image-2 also accepts `3:4`, `4:3`, `4:5`. Used when `--image-size` is not given.\n- `--image-size` — explicit `WIDTHxHEIGHT` (e.g. `1728x2304`). **gpt-image-2 only** — values are rounded to multiples of 16 and capped at 3840px. On `gpt-image-1` a custom size is ignored with a warning and the aspect-ratio mapping is used instead.\n- `--quality` — `low | medium | high` (default `medium`).\n- `--ref-image` / `--ref-url` — a **PUBLIC image URL** for the `/edit` variant. **Repeatable** — pass it twice to send multiple refs (e.g. identity + style). The proxy does **not** upload local files, so a **local path is rejected** — host the image first (MCP `get_upload_url` → `get_download_url`, or any public URL) and pass that URL. When present, routes to the model's `/edit` variant so the model can match the references. Order matters: pass identity (character) first, then style refs.\n- `--with-logs` — stream fal queue logs.\n\nCredentials (proxy-routed — NOT a raw FAL key):\n- The bundled `scripts/media_proxy.py` routes every call through the GooseWorks **fal-proxy**, which **bills the Ads agent**. It reads `~/.gooseworks/credentials.json` (`api_base`, `api_key`, `agent_id`) — written by `gooseworks login`. Do **not** set `FAL_API_KEY`: an agent (`cal_`) token is not a FAL key and 401s against fal directly.\n- Set `GW_PROJECT_ID=<ad project id>` in the env so the generation's spend attributes to that ad project (per-project cost shows in the app).\n\n## Preflight\n\n```bash\ntest -f ~/.gooseworks/credentials.json || { echo \"Missing credentials — run: gooseworks login\"; exit 1; }\npython3 -c \"import requests\" || pip3 install requests\n```\n\n## Workflow\n\n```bash\n# Text-to-image, default model (gpt-image-1)\npython3 skills/ads/capabilities/create-image-gpt-image-fal/scripts/generate.py \\\n  --prompt \"...\" \\\n  --output /path/to/anchor.png \\\n  --aspect-ratio 9:16 \\\n  --quality medium\n\n# Edit-from-reference (anchor -> angle). --ref-image must be a PUBLIC URL,\n# NOT a local path (the proxy does not upload local files):\npython3 .../generate.py \\\n  --prompt \"...\" \\\n  --output /path/to/angle-3q-left.png \\\n  --ref-image \"https://.../anchor.png\" \\\n  --aspect-ratio 9:16\n\n# gpt-image-2 with a custom output size (e.g. a designed storyboard sheet)\npython3 .../generate.py \\\n  --prompt \"...\" \\\n  --output /path/to/storyboard.png \\\n  --model gpt-image-2 \\\n  --image-size 1728x2304 \\\n  --quality high\n```\n\nThe script:\n1. Loads the agent credentials from `~/.gooseworks/credentials.json` via the bundled `media_proxy.py` (proxy-routed; bills the Ads agent).\n2. Resolves the model family (`--model`) and output size (`--image-size` if given and supported, else the aspect-ratio mapping).\n3. If one or more `--ref-image` / `--ref-url` flags are set, passes them as `image_urls=[url1, url2, ...]` (they must already be PUBLIC URLs) and routes to the model's `/edit` variant. Otherwise routes to the `/text-to-image` variant.\n4. Submits through the GooseWorks **fal-proxy** and polls the queue to completion — host-swapping the `queue.fal.run` status/response URLs to the proxy base (see `media_proxy.py`); never polls `queue.fal.run` directly.\n5. Downloads the first result image to `--output`.\n6. Writes `<output>.meta.json` with `gateway: \"fal-proxy\"`, model id, `model_family`, request, and cost.\n\n## Output\n\n- `<output_path>` — PNG (≥ 1 KB).\n- `<output_path>.meta.json` — request + result metadata + cost, including `model_family` (`gpt-image-1` or `gpt-image-2`).\n\n## Quality Checks\n\n- Output file exists and is > 1 KB.\n- For character anchors: visually inspect against the descriptor block (hair, shirt color, age).\n- `meta.json` includes `gateway: \"fal-proxy\"`, the resolved `model` id, `model_family`, `image_size`, and `quality`.\n- For gpt-image-2 custom sizes: confirm the output dimensions match the requested `WIDTHxHEIGHT`.\n- **No readable text in the prompt that should appear in the image.** AI image models mangle short brand text, URLs, code tokens, captions, and wordmarks even with explicit prompting. Examples observed: `\"ffmpeg\"` → `\"ffmmg\"`; `\"klarify\"` → `\"clarify\"`; `\"therapists\"` → `\"therapits\"`. Use PIL or `ffmpeg drawtext` for any overlay containing readable text. Reserve image gen for purely visual content (characters, scenes, backgrounds). Repeats LEARNINGS L4.\n\n## Failure Modes\n\n| Symptom | Likely cause | Fix |\n|---|---|---|\n| `401 Unauthorized` from fal | Calling fal directly with an agent token, or polling `queue.fal.run` instead of the proxy | This atom is **proxy-routed** — it uses the `~/.gooseworks/credentials.json` agent token via `media_proxy.py`, never a raw `FAL_API_KEY`. Run `gooseworks login` if the credentials file is missing. |\n| `ERROR: ref images must be PUBLIC URLs` | Passed a **local path** to `--ref-image` / `--ref-url` | The proxy does not upload local files. Host it (MCP `get_upload_url` → `get_download_url`) and pass the resulting public URL. |\n| `429 Too Many Requests` | RPS limit | Drop concurrency to 2-3. |\n| Custom size ignored | `--image-size` passed with `--model gpt-image-1` | gpt-image-1 only supports fixed sizes; use `--model gpt-image-2` for custom sizes. |\n| Aspect-ratio drift (gpt-image-1) | gpt-image-1 only supports 1024x1024, 1024x1536, 1536x1024 | The script maps aspect ratios to these internally. |\n| Size rejected (gpt-image-2) | Dimension not a multiple of 16, or > 3840px | The script rounds to /16 and caps at 3840; pass a smaller size. |\n| Anchor reference ignored | `/text-to-image` variant doesn't accept refs | Pass `--ref-image` to force the `/edit` variant. |\n| Skin / face looks \"AI-stock\" | gpt-image's failure mode | Add anti-AI cues to the prompt: \"natural skin texture with pores, slight asymmetry, no perfect teeth\". |\n\n## Cross-provider parity note\n\nWhen this atom generates a character anchor (lock-character Phase 0), the anchor approved here MUST be pinned for all downstream angle gens, and the **same `--model`** must be used for those angle gens. Mixing model families (or mixing FAL-gpt-image with Higgsfield-gpt_image_2) introduces aesthetic drift. The orchestrator's `generate_with_fallback.py` inherits `gateway`/`model_family` from the anchor's `.meta.json` for subsequent calls.\n\n## References\n\n- [fal.ai/models/fal-ai/gpt-image-1](https://fal.ai/models/fal-ai/gpt-image-1)\n- [fal.ai/models/openai/gpt-image-2](https://fal.ai/models/openai/gpt-image-2)\n- Sibling Higgsfield path: `mcp__higgsfield__generate_image` with `model=\"gpt_image_2\"`\n- Shared helper: `scripts/media_proxy.py` (proxy-routed FAL/ElevenLabs; bills the Ads agent — the helper `generate.py` actually imports). `scripts/fal_helpers.py` is a LEGACY raw-FAL helper kept for reference only; `generate.py` does **not** use it (it would need a real `FAL_KEY`).\n- Storyboard-sheet consumer: `create-storyboard-sheets-fal` (video flow, in the separate ads-video repo)","author":"@gooseworks-ai","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/gooseworks-ai/goose-skills/tree/main/skills/ads/capabilities/create-image-gpt-image-fal","license":"MIT","category":null,"lang":"en","tokens":2396,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[{"path":"scripts/fal_helpers.py","size":5496,"sha256":"e15d1e8aeaafa128544e2a2665b2c09de61bf6dec2dc2f7d2f7f68f86fdc46a2"},{"path":"scripts/generate.py","size":7060,"sha256":"ecd7f91e4b86b62f30f8efdefbdff3b9af267c5b6b495d963954076cd1ee473b"},{"path":"scripts/media_proxy.py","size":15221,"sha256":"a93ee72308f14c6c1ce8ee2101bdf2551cae44597fc90626072d5201b1de840d"},{"path":"skill.meta.json","size":267,"sha256":"84d333349bdc584fbdd321bb8e5a43b7145c6088f3e5198b318fad3783566064"},{"path":"tests/expected-output.md","size":900,"sha256":"ec30210ecae3733581ee450d8e6ad155d36447b472ecc58656dd56d07121b49e"},{"path":"tests/human-test.md","size":1203,"sha256":"6c788dd772e15e9108b47527d7f4107b6ea87ac5747251287cfecbccb9c4e0a7"},{"path":"tests/sample-input.md","size":525,"sha256":"b6752e8cf98351e3cebb695f426e4ffc4bb050decd23cce55a22ea5e18941849"},{"path":"tests/smoke-test.md","size":1444,"sha256":"1a83e78eb059c0082eebe5143845afe23670b609ed5caa1624e64b75e29399b7"},{"path":"tests/verifier.md","size":890,"sha256":"2bfd61636b4de5bfdae009ae7cac4d916cdee42278c5a7ea47db6437f7e2fa34"}],"requires":{"mcp":["higgsfield"],"tools":[]},"safety":{"flags":[{"code":"code.credentials","kind":"dangerous-code","where":"scripts/media_proxy.py:10","excerpt":"credentials.json","message":"reads credential files","severity":"warn"},{"code":"net.endpoints","kind":"exfiltration","excerpt":"fal.ai","message":"bundled scripts reach 1 external host(s)","severity":"warn"}],"scannedAt":"2026-08-22","hasScripts":true,"networkEndpoints":["fal.ai"]}}