Python Bulk Image Resizer Script

Written by

in

When you’re handling a growing library of photos, product pictures, or marketing assets, manually resizing each image quickly becomes a nightmare. A Python bulk image resizer script can automate the entire process, saving you hours of repetitive work while ensuring consistent dimensions and optimal file sizes. In this guide we’ll walk through why bulk resizing matters, the key libraries you’ll need, step‑by‑step code examples, and best practices for integrating the script into your workflow. By the end, you’ll have a ready‑to‑run Python script that can shrink, enlarge, or re‑format thousands of images with a single command.

Why a Bulk Image Resizer Is Essential for Modern Projects

Whether you’re a web developer, e‑commerce manager, or content creator, the benefits of automating image resizing are clear:

  • Performance boost: Smaller images load faster, improving page speed and SEO rankings.
  • Consistency: Uniform dimensions keep your site or app looking professional.
  • Storage savings: Optimized images reduce disk usage and cloud storage costs.
  • Time efficiency: One script can process hundreds or thousands of files in minutes.

Choosing the Right Python Library

The most popular choice for image manipulation in Python is Pillow, the friendly fork of the original PIL (Python Imaging Library). Pillow offers a simple API for opening, resizing, and saving images in many formats.

Alternative libraries like opencv-python or imgaug provide advanced computer‑vision features, but for a straightforward bulk resizer Pillow is lightweight, well‑documented, and easy to install:

pip install pillow

Setting Up the Project Structure

Organizing your files before you run the script makes it easier to maintain and scale. A typical layout looks like this:

image-resizer/
│
├─ src/
│   └─ bulk_resizer.py
│
├─ input/
│   ├─ photo1.jpg
│   ├─ photo2.png
│   └─ … (original images)
│
├─ output/
│   └─ (resized images will appear here)
│
└─ requirements.txt

Keep the input folder read‑only and let the script write all results to output. This separation prevents accidental overwrites.

Writing the Core Resizer Script

Below is a complete, production‑ready script that handles the most common requirements: batch processing, aspect‑ratio preservation, format conversion, and optional quality control for JPEGs.

import os
import sys
from pathlib import Path
from PIL import Image

# ---------- Configuration ----------
# Desired maximum width and height (pixels)
MAX_WIDTH = 1280
MAX_HEIGHT = 720

# Output format (e.g., "JPEG", "PNG")
OUTPUT_FORMAT = "JPEG"

# JPEG quality (1‑95). Ignored for PNG.
JPEG_QUALITY = 85

# Whether to keep the original filename extension
KEEP_EXTENSION = False
# ----------------------------------

def resize_image(in_path: Path, out_path: Path) -> None:
    """Resize a single image while preserving aspect ratio."""
    try:
        with Image.open(in_path) as img:
            # Skip if the image is already smaller than the target size
            if img.width <= MAX_WIDTH and img.height <= MAX_HEIGHT:
                img.save(out_path, OUTPUT_FORMAT, quality=JPEG_QUALITY)
                return

            # Calculate new dimensions
            img.thumbnail((MAX_WIDTH, MAX_HEIGHT), Image.LANCZOS)

            # Ensure output folder exists
            out_path.parent.mkdir(parents=True, exist_ok=True)

            # Save the resized image
            save_kwargs = {"quality": JPEG_QUALITY} if OUTPUT_FORMAT == "JPEG" else {}
            img.save(out_path, OUTPUT_FORMAT, **save_kwargs)

            print(f"Resized: {in_path.name} → {out_path.name}")

    except Exception as e:
        print(f"Error processing {in_path}: {e}", file=sys.stderr)


def bulk_resize(input_dir: Path, output_dir: Path) -> None:
    """Walk through input_dir and resize every supported image."""
    supported_ext = {".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff"}

    for root, _, files in os.walk(input_dir):
        for file_name in files:
            if Path(file_name).suffix.lower() not in supported_ext:
                continue

            src_path = Path(root) / file_name
            # Preserve sub‑folder structure in the output folder
            relative_path = src_path.relative_to(input_dir)
            if KEEP_EXTENSION:
                dest_name = relative_path.stem + src_path.suffix
            else:
                dest_name = relative_path.stem + "." + OUTPUT_FORMAT.lower()
            dest_path = output_dir / relative_path.parent / dest_name

            resize_image(src_path, dest_path)


if __name__ == "__main__":
    # Default folders – change as needed or pass via CLI arguments
    INPUT_FOLDER = Path(__file__).parent.parent / "input"
    OUTPUT_FOLDER = Path(__file__).parent.parent / "output"

    bulk_resize(INPUT_FOLDER, OUTPUT_FOLDER)
    print("Bulk resizing completed.")

Key points to note:

  • The thumbnail method automatically maintains the original aspect ratio.
  • Using Image.LANCZOS gives high‑quality downsampling.
  • The script recreates the original sub‑directory tree inside output, which is handy for large, organized collections.
  • All errors are caught and logged to stderr so the batch can continue even if a single file is corrupted.

Running the Script from the Command Line

After saving the file as bulk_resizer.py, open a terminal, navigate to the project root, and execute:

python src/bulk_resizer.py

If you prefer to specify custom input or output folders, extend the script with argparse – a quick example:

import argparse
# …
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Bulk image resizer")
    parser.add_argument("-i", "--input", type=Path, default=Path("input"))
    parser.add_argument("-o", "--output", type=Path, default=Path("output"))
    args = parser.parse_args()
    bulk_resize(args.input, args.output)

Advanced Tips for Production‑Ready Resizing

1. Parallel Processing for Massive Batches

Python’s concurrent.futures module can dramatically speed up processing on multi‑core machines. Wrap the resize_image call in a thread or process pool:

from concurrent.futures import ThreadPoolExecutor

def bulk_resize_parallel(input_dir, output_dir, workers=8):
    # ... same file discovery logic ...
    with ThreadPoolExecutor(max_workers=workers) as executor:
        futures = [executor.submit(resize_image, src, dst) for src, dst in tasks]
        for f in futures:
            f.result()  # re‑raise any exceptions

2. Adding EXIF Orientation Handling

Many photos contain orientation metadata that can cause rotated outputs. Pillow can auto‑rotate when you load an image with ImageOps.exif_transpose:

from PIL import ImageOps

with Image.open(in_path) as img:
    img = ImageOps.exif_transpose(img)
    # continue with thumbnail...

3. Optimizing PNGs with Pillow‑SIMD

If you’re processing many PNGs, consider installing pillow-simd (a SIMD‑accelerated Pillow fork). It reduces CPU time and can apply additional compression:

pip uninstall pillow
pip install pillow-simd

4. Logging Instead of Printing

Replace print statements with the logging module for better control over verbosity and log file generation.

SEO Benefits of Optimized Images

Search engines reward fast‑loading pages, and properly sized images are a core factor. By integrating the bulk resizer into your build pipeline, you ensure every image served to users is:

  • Below the recommended file size (generally < 200 KB for web thumbnails).
  • In the appropriate format (WebP for modern browsers, JPEG for legacy support).
  • Tagged with descriptive alt attributes (handled separately in HTML, but the script guarantees the visual asset is ready).

Combine this with lazy loading and a CDN, and you’ll see measurable improvements in Core Web Vitals, bounce rate, and organic traffic.

Common Pitfalls and How to Avoid Them

  1. Over‑compressing JPEGs: Setting quality too low (< 70) can produce visible artifacts. Test a few samples before committing to a global value.
  2. Ignoring aspect ratio: Always use thumbnail or calculate proportional dimensions; stretching images harms visual quality and SEO.
  3. Processing unsupported formats: Add a whitelist of extensions (as shown) to skip RAW files or PDFs that Pillow can’t handle out of the box.
  4. Accidentally overwriting originals: Keep separate input and output directories, and avoid setting KEEP_EXTENSION=True unless you’re certain.
  5. Running out of memory on huge batches: Process images one at a time (as the script does) and avoid loading the entire collection into memory.

Deploying the Script in Real‑World Workflows

Here are three practical scenarios where the Python bulk image resizer script shines:

  • CI/CD pipeline for a static site generator: Add a step that copies new assets

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *