// Tool 006 · CLI · Image Pipeline

Apply one prompt to a folder of images.

A small Python script that walks a folder, sends each image with your prompt to Gemini or OpenAI, and saves the result hyper-organized: per-run directory, manifest log, side-by-side HTML comparison. Run with --provider both to QA the two engines on the same input.

CLI Python 3.10+ Gemini 2.5 Flash Image OpenAI gpt-image-1 Local
View source on GitHub Download .py
Install

Clone the repo and install the Python deps. The script lives in scripts/.

# 1. Clone the toolshed
git clone https://github.com/banditmediagroup/toolshed.git
cd toolshed/scripts

# 2. Install deps (use a venv if you don't already)
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

# 3. Set your keys
cp .env.example .env
# Edit .env, paste in GEMINI_API_KEY and/or OPENAI_API_KEY

The script auto-loads .env if python-dotenv is installed (it's in requirements.txt). You can also export the env vars in your shell instead.

Use

The basics:

# Default: Gemini 2.5 Flash Image
python image-batch-prompt.py -i ./photos -p "watercolor painting style"

# OpenAI gpt-image-1
python image-batch-prompt.py -i ./photos -p "watercolor painting style" --provider openai

# Both, side-by-side, for QA
python image-batch-prompt.py -i ./photos -p "watercolor painting style" --provider both

# Long prompts: load from a file
python image-batch-prompt.py -i ./photos -p @prompts/watercolor.txt

# Test the wiring without burning API credits
python image-batch-prompt.py -i ./photos -p "..." --dry-run

# Override defaults
python image-batch-prompt.py -i ./photos -p "..." \
  --name spring_campaign \
  --limit 5 \
  --size 1536x1024 \
  --openai-model gpt-image-1
Output Structure

Every run creates a timestamped (or named) directory. Per-provider subdirs hold the results, manifest.json captures the full run state, and an auto-generated index.html shows side-by-side comparison.

output/
└── 20260428_223015/                # run name (timestamp by default)
    ├── manifest.json                # full run config + per-image status
    ├── index.html                   # side-by-side HTML, open in browser
    ├── gemini/
    │   ├── photo_001.png
    │   ├── photo_002.png
    │   └── ...
    └── openai/                      # only if --provider openai or both
        ├── photo_001.png
        └── ...

The manifest is updated after each image, so a crash mid-run preserves everything completed so far. Re-run with the same --name to resume (existing outputs skipped by default; pass --no-skip to reprocess).

Provider Comparison
Gemini 2.5 Flash ImageOpenAI gpt-image-1
SDKgoogle-genaiopenai
API endpointgenerate_content (multimodal)images.edit
Output sizesAuto, near 1024x10241024x1024, 1536x1024, 1024x1536, auto
StrengthStrong character/identity preservation, fast, cheapBetter at typography, complex compositions, photorealism
WeaknessOccasional refusals on photo-of-people inputSlower, pricier, content policies stricter
Pricing (approx)~$0.039 / image~$0.040 std, ~$0.080 high quality
Flags
-i, --inputInput folder (required). Picks up .jpg, .jpeg, .png, .webp.
-p, --promptPrompt text (required). Prefix with @ to load from a file.
--providergemini (default), openai, or both for side-by-side.
-o, --outputOutput base directory. Default ./output.
--nameRun name. Default: timestamp YYYYMMDD_HHMMSS.
--limit NProcess at most N images. Useful for testing on a subset before a big batch.
--sizeOpenAI output size. Default 1024x1024.
--gemini-model / --openai-modelOverride the default model for either provider.
--dry-runList inputs without making API calls. Sanity check.
--no-skipReprocess images even when output exists. Default is to skip.
--no-html-indexSkip the generated index.html.
// 01
Why CLI, not browser?
Image generation requires API keys, and shipping API keys to a static page exposes them to anyone who hits the URL. CLI keeps your keys on your machine. Plus it scales: a 200-image batch via the browser would freeze the tab; locally it's a coffee.
// 02
QA flow we use
Run --provider both on a small --limit 5 set first, open the generated index.html to compare side-by-side, pick the winner, then run the full batch through that single provider. Catches "this prompt works great in Gemini but mangles type in OpenAI" before you spend the credits.
// 03
Resume safely
If the run crashes, manifest.json is up-to-date through the last completed image. Re-running with the same --name picks up exactly where it left off because existing output files are skipped by default. No double-charging.