I used to resize images like this:
# One by one. Painful.
for f in *.jpg; do convert "$f" -resize 1920x1920\> "out/$f"; done
# Or ImageMagick with flags I'd forget
mogrify -resize 1920x1920\> -quality 85 -strip *.jpg
# Or upload to TinyPNG and wait. And pay.
Then I wrote img-resize — a single-file Python CLI that does all of this in parallel, with sensible defaults, fit modes that make sense, and a dry-run that shows you exactly what will happen before you commit.
# One command. Thousands of images. Seconds.
img-resize ./photos -M 1920 -q 82 -f webp -o ./web-ready -r
Why Another Image Resizer?
| Need | ImageMagick | mogrify | sips | img-resize |
|---|---|---|---|---|
| Parallel processing | Manual -parallel
|
No | No | Auto |
| Fit modes | Geometry syntax | Limited | Basic | 5 named modes |
| Format conversion | Complex | Yes | Limited | Auto + force |
| Metadata control |
-strip or per-tag |
-strip |
Limited | Granular flags |
| Dry-run preview | No | No | No | Yes |
| Config files | No | No | No | JSON |
| Python API | subprocess | subprocess | subprocess | Native |
| Install | System package | System package | macOS only | pipx install |
The Fit Mode Problem
Most tools give you "resize to fit in box" or "resize to exact dimensions (distort)." Real work needs more:
Original: 800x600 -> Target box: 400x400
fit (default) -> 400x300 # Fits inside, preserves aspect
fill -> 400x400 # Covers box, crops center
stretch -> 400x400 # Distorts to exact size
inside -> 400x300 # Same as fit
outside -> 533x400 # Covers box, no crop
--fill with --bg 0,0,0 gives you perfect Instagram 1080x1080 squares with black bars. --fit outside gives you "cover" without crop. You pick the mode by name, not geometry syntax.
Real Examples
Web-optimize a photo library (recursive, WebP, quality 82, strip metadata):
img-resize ~/Pictures -M 1920 -q 82 -f webp -o ~/Pictures_web -r -s _web
E-commerce square thumbnails (center-crop 400x400):
img-resize ./product-photos -W 400 -H 400 --fill -o ./thumbs -s _thumb -r
Instagram-ready 1080x1080 with black bars:
img-resize ./raw -W 1080 -H 1080 --fill --bg 0,0,0 -o ./insta -r
Lossless PNG compression for screenshots (keep metadata):
img-resize ./screenshots -f png --no-strip -o ./compressed -r
Dry-run before you commit (verbose):
img-resize ./assets -M 1200 -n -r -v
Config Files = Reproducible Pipelines
Save your pipeline once:
img-resize ./photos -M 1920 -q 80 -f webp --save-config web-pipeline.json
Reuse anywhere:
img-resize ./more-photos --config web-pipeline.json -r
Config is just JSON:
{
"max_dimension": 1920,
"quality": 80,
"format": "WEBP",
"strip_metadata": true,
"recursive": true
}
Python API for Custom Pipelines
from img_resize import ResizeConfig, process_one
from pathlib import Path
cfg = ResizeConfig(max_dimension=1920, quality=85, format="WEBP", suffix="_web")
result = process_one(Path("photo.jpg"), cfg)
if result.ok:
print(f"Saved {result.orig_size/1024:.0f} KB -> {result.new_size/1024:.0f} KB")
Install & Run
# Recommended: isolated CLI via pipx
pipx install img-resize
# Or in a virtualenv
pip install -e .
Requires Python 3.9+ and Pillow >= 10. That's it. No system packages.
Output You Can Read
=== Summary (2.3s) ===
OK: 847 Dry-run: 0 Failed: 2
Total size: 1.24 GB -> 312.5 MB (74.8% reduction)
x DSC001.CR2: Unsupported/corrupt image
x DSC002.CR2: Unsupported/corrupt image
Failed files are listed with reasons. Successful ones show total savings.
Get It
GitHub: https://github.com/Kiendas25/image-bulk-resizer
PyPI: pipx install img-resize (after publish)
License: MIT -- use commercially, modify, distribute.
The Pitch
If you resize images more than once a month, this saves you hours. It's one file, zero config to start, and scales to whatever you throw at it.
img-resize ./your-folder -M 1920 -r
That's it. Try it on a folder of 100 images and watch the summary.
Built with Pillow. Tips welcome via USDC (Solana): 49NHJ5aUPpVwjMrHzgJt7pcYPCi7cxHUXVoEhgBPrAgE
Top comments (0)