Image Batch Processing skill for Codex
Resize a thousand photos without one sideways. Codex inspects formats, orientation tags and colour profiles before converting, then runs ImageMagick, sharp or Pillow into a separate output folder with auto-orient, sRGB conversion, shrink-only resizing and metadata stripping. HEIC, CMYK and transparency get explicit handling, batches run in parallel, and counts, dimensions and GPS tags are checked afterwards.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- image-batch-processing Installs to ~/.agents/skills/image-batch-processing/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install image-batch-processing The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project image-batch-processing Installs to .agents/skills/image-batch-processing/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/image-batch-processing.zipunzip -o image-batch-processing.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) image-batch-processing PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $image-batch-processing, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Batch-processes folders of images with ImageMagick 7, sharp (Node) or Pillow (Python) - resize, crop, convert to WebP or AVIF, auto-orient from EXIF, strip or keep metadata, convert colour profiles to sRGB, watermark, and generate thumbnails - in parallel, into a separate output folder, with before/after checks. Use when asked to resize or compress a folder of photos, convert HEIC or PNG to JPEG or WebP, make thumbnails, add a watermark to many images, or fix sideways or washed-out images after a batch job.
Use it when
- A folder of images needs resizing, compression or format conversion
- Thumbnails must be generated for many images at once
- Batch outputs came out rotated, washed out or with black backgrounds
- Photos must have GPS and camera metadata removed before publishing
Use something else when
- You are deciding srcset, sizes and formats for a website: use responsive-images
- You need new images generated from a prompt: use openai-image-generation
How to check the result
Input and output counts match, identify shows every output within the size limit and in sRGB, exiftool finds no GPS tags, and five outputs look right next to their originals.
The mistake it prevents
Stripping metadata before converting to sRGB, so Display P3 or Adobe RGB photos come out dull and off-colour.
A batch is done right when originals are untouched, every output is upright, sRGB and the requested size and format, file names map one to one back to inputs, private metadata (GPS) is gone unless the user wants it, and a count plus a visual spot check prove it.
Inspect before converting
magick identify -format '%f %wx%h %m %[colorspace] q=%Q orient=%[EXIF:Orientation]\n' in/* | head
exiftool -T -FileName -ImageSize -Orientation -ICC_Profile:ProfileDescription -GPSPosition in/ | head
find in -type f | sed 's/.*\.//' | sort | uniq -c
Note: format mix (HEIC from iPhones, CMYK JPEGs from print, 16-bit PNG/TIFF), orientation tags, embedded profiles (Adobe RGB, Display P3), animated GIF/WebP, and transparency. Each changes the pipeline.
Tool choice
| Situation | Default |
|---|---|
| One-off shell batch, any format | ImageMagick 7 (magick, magick mogrify) |
| Node project, build step, high volume | sharp (libvips; fast, low memory) |
| Python project or per-image logic | Pillow (+ pillow-heif for HEIC) |
| HEIC on macOS with nothing installed | sips -s format jpeg in.heic --out out.jpg |
| Web delivery (srcset, CDN) decisions | $responsive-images and $image-optimization |
Check versions: magick -version (v6 installs only have convert/mogrify; options
mostly match but HEIC/AVIF delegates may be missing). Installing any of these needs network
and possibly elevated access; ask in a Codex sandbox.
Defaults that are almost always right
- Output to a new folder; never
mogrifyin place on the only copy. - Auto-orient first, then strip the orientation tag (otherwise viewers rotate twice).
- Convert to sRGB before stripping profiles; stripping a P3 or Adobe RGB profile without converting makes colours dull or oversaturated.
- Resize only down (
>in ImageMagick,withoutEnlargementin sharp,thumbnailin Pillow). - Strip EXIF/GPS for anything published; keep copyright fields if the user asks.
- Quality starting points: JPEG 80 to 85, WebP 75 to 82, AVIF 45 to 60 (AVIF scales differently).
ImageMagick 7
mkdir -p out
# Resize to fit 1600x1600 (shrink only), sRGB, strip metadata, WebP
magick mogrify -path out -auto-orient -colorspace sRGB -resize '1600x1600>' \
-strip -quality 80 -format webp in/*.jpg
# Square thumbnails, centre crop
magick mogrify -path thumbs -auto-orient -thumbnail '400x400^' -gravity center \
-extent 400x400 -strip -quality 82 -format jpg in/*.jpg
# Watermark bottom-right at 30 percent opacity, scaled to 15 percent of image width
for f in in/*.jpg; do
w=$(magick identify -format '%w' "$f")
magick "$f" -auto-orient \( logo.png -resize "$((w * 15 / 100))x" -alpha set -channel A \
-evaluate multiply 0.3 +channel \) -gravity southeast -geometry +24+24 -composite \
-quality 85 "out/$(basename "$f")"
done
For profile-accurate conversion use -profile sRGB.icc with a real ICC file instead of
-colorspace sRGB. For large batches, parallelise per file:
find in -name '*.jpg' -print0 | xargs -0 -P 4 -I{} sh -c \
'magick "$1" -auto-orient -resize "1600x1600>" -strip -quality 80 "out/$(basename "${1%.*}").webp"' _ {}
GNU parallel works too. Cap memory with -limit memory 1GiB when processing huge scans.
sharp (Node)
import sharp from 'sharp';
import { readdir, mkdir } from 'node:fs/promises';
import path from 'node:path';
const IN = 'in', OUT = 'out';
await mkdir(OUT, { recursive: true });
const files = (await readdir(IN)).filter((f) => /\.(jpe?g|png|tiff?)$/i.test(f));
const limit = 4; // concurrency
const failures = [];
for (let i = 0; i < files.length; i += limit) {
await Promise.all(files.slice(i, i + limit).map(async (f) => {
const dst = path.join(OUT, f.replace(/\.\w+$/, '.webp'));
try {
await sharp(path.join(IN, f))
.rotate() // no args = auto-orient from EXIF
.resize({ width: 1600, height: 1600, fit: 'inside', withoutEnlargement: true })
.webp({ quality: 80 })
.toFile(dst);
} catch (err) { failures.push([f, err.message]); }
}));
}
console.log(`${files.length - failures.length}/${files.length} converted`, failures);
sharp drops metadata and converts to sRGB by default; call .keepMetadata() or
.withMetadata() only when metadata must survive. Prebuilt sharp binaries do not decode
HEIC (HEVC licensing); convert HEIC with ImageMagick/libheif or sips first. Animated
inputs need sharp(file, { animated: true }).
Pillow (Python)
from pathlib import Path
from PIL import Image, ImageOps
src, dst = Path("in"), Path("out"); dst.mkdir(exist_ok=True)
for p in sorted(src.glob("*")):
if p.suffix.lower() not in {".jpg", ".jpeg", ".png", ".tif", ".tiff"}:
continue
try:
with Image.open(p) as im:
im = ImageOps.exif_transpose(im) # apply orientation
if im.mode in ("CMYK", "P", "LA", "I;16"):
im = im.convert("RGBA" if "A" in im.getbands() else "RGB")
im.thumbnail((1600, 1600), Image.Resampling.LANCZOS) # shrink only, keeps ratio
im.save(dst / (p.stem + ".webp"), "WEBP", quality=80, method=6)
except Exception as exc:
print(f"FAILED {p.name}: {exc}")
Pillow’s convert("RGB") on CMYK ignores the ICC profile; for print-sourced CMYK files use
PIL.ImageCms with the embedded profile or ImageMagick -profile. HEIC: pip install pillow-heif then pillow_heif.register_heif_opener(). AVIF support depends on the Pillow
build; check PIL.features.check("avif") before relying on it.
Gotchas
-resize 1600x1600without>enlarges small images and blurs them.- PNG with transparency converted to JPEG gets a black background; flatten onto white
(
-background white -alpha remove) or keep PNG/WebP. - Re-encoding JPEGs at quality 95+ makes files bigger with no visible gain; re-encoding at any quality loses detail, so do it once from the original.
mogrify -format webpwritesname.webpnext to inputs unless-pathis set.- Filenames with spaces or leading dashes break naive loops; quote, use
--or-print0. - Same stem with different extensions (
a.jpg,a.png) collide when both becomea.webp. - Animated GIFs processed frame by frame need
-coalescebefore resizing in ImageMagick.
Verification
ls in | wc -l; ls out | wc -l # counts match
magick identify -format '%f %wx%h %[colorspace]\n' out/* | awk '{print $2}' | sort | uniq -c | head
exiftool -if '$GPSPosition' -p '$FileName' out/ 2>/dev/null | head # expect nothing
du -sh in out
- Open 5 outputs side by side with their originals: upright, colours match, no halos.
- Largest dimension never exceeds the limit; no output is bigger than its input unless the format change explains it.
- Failures list is empty or each failure is explained (corrupt file, unsupported format).
Report
**Input:** in/ 1,240 files (1,118 JPEG, 97 HEIC, 25 PNG), 9.8 GB
**Output:** out/ 1,240 WebP, max 1600 px, sRGB, metadata stripped, 412 MB
**Command:** magick mogrify -path out -auto-orient -colorspace sRGB -resize '1600x1600>' -strip -quality 80 -format webp
**Checks:** counts match, no GPS tags, 5 visual spot checks OK
**Failed:** 2 (truncated JPEGs: IMG_2231.jpg, IMG_2240.jpg) What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Image Batch Processing"
short_description: "Resize a thousand photos without one sideways"
default_prompt: "$image-batch-processing Resize every photo in this folder to max 1600 px and convert them to WebP" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.