Batch Image Processing with Python: Compress, Watermark and Convert Hundreds of Photos

If you run an online shop or handle marketing, you have been here before: a few hundred product photos arrive, and every single one needs the same treatment — resized to one dimension, compressed below a certain file size, stamped with your company watermark, and finally converted into a web-friendly format. Opening, editing and re-saving each file by hand is bearable for ten images. By the hundredth, you want to give up.

Yet this kind of "same set of steps, repeated hundreds of times" work is exactly what a computer does best. With Pillow, Python's image-processing library, roughly twenty lines of code will process an entire folder in one go — you can go and make a coffee, and come back to a finished job.

What is Pillow, and why learn it?

Pillow is the most widely used image library for Python (it was originally called PIL). It can open images, resize, crop, rotate, adjust brightness, draw text watermarks, convert formats and compress. In short, it covers everything you use day to day in an image editor — and it is free, open source, and driven entirely by code. Because it is code-driven, it can work in bulk: processing a hundred images is exactly the same procedure as processing one. The only difference is a loop.

Step 1: Install Pillow

Open Command Prompt on Windows and run a single line:

pip install pillow

That is it — your Python installation can now handle images, with no software to buy.

Step 2: Learn the three basic moves

Every batch job is built from three actions: open, edit, and save.

from PIL import Image img = Image.open("product.jpg") # 1. open the image img = img.resize((800, 800)) # 2. change the size img.save("product_small.jpg", quality=80) # 3. save (quality 80 = compression)

As simple as that. resize() sets the dimensions, and quality=80 inside save() controls compression (1 to 100 — the lower the number, the smaller the file).

Step 3: Process an entire folder

Once you can handle one image, adding a loop turns it into batch work. Say you have a folder called photos and you want every image inside resized to 800×800, compressed, and written into a new folder called output:

from PIL import Image import os, glob IMG_EXTS = (".jpg", ".jpeg", ".png", ".webp") os.makedirs("output", exist_ok=True) # create the output folder if missing count = 0 for path in glob.glob("photos/*"): if not path.lower().endswith(IMG_EXTS): continue # skip anything that is not an image try: with Image.open(path) as img: img = img.convert("RGB") # normalise colour mode so PNG saves as JPG img = img.resize((800, 800), Image.LANCZOS) # high-quality resampling name = os.path.splitext(os.path.basename(path))[0] img.save(os.path.join("output", name + ".jpg"), quality=80, optimize=True) count += 1 except Exception as e: print("skipped", path, e) # one bad file will not stop the whole run print("Done! Processed", count, "images")

Line by line

  • glob.glob("photos/*") — grabs every file in the folder at once, so you never have to count them yourself.
  • path.lower().endswith(IMG_EXTS) — keeps images only; spreadsheets, PDFs and hidden files are skipped automatically.
  • convert("RGB") — normalises the colour mode; some PNGs carry a transparency channel and will throw an error if saved as JPG without this.
  • resize(..., Image.LANCZOS) — picks the highest-quality resampling filter, so downscaled images stay crisp.
  • try / except — if one or two files are corrupt, the script carries on instead of dying halfway and wasting the run.
  • optimize=True — asks Pillow for an extra squeeze, usually a few percent smaller, at no cost.

Adding a company watermark

A watermark is simply a small logo pasted onto a larger image. Prepare a logo with a transparent background (PNG), then:

from PIL import Image base = Image.open("photos/a.jpg").convert("RGB") logo = Image.open("logo.png").convert("RGBA") # transparent-background logo # scale the logo to 15% of the base image width w = int(base.width * 0.15) logo = logo.resize((w, int(logo.height * w / logo.width))) # paste it into the bottom-right corner, 20px from the edges pos = (base.width - logo.width - 20, base.height - logo.height - 20) base.paste(logo, pos, logo) # third argument = use the logo's alpha as a mask base.save("a_watermark.jpg", quality=85)

Note the third argument to paste(): passing the logo itself as the mask is what makes the transparent area show the photo underneath. Skip it and you get an ugly white box.

Convert to WebP for a faster website

If you run a website, converting photos to WebP typically makes them about thirty percent smaller than JPG with no visible difference — so pages load faster. The change is minimal: just alter the filename and format on save.

img.save("output/a.webp", format="WEBP", quality=80)

Pillow picks the format from the filename, so there is nothing extra to install and no need to upload files to an online converter one by one.

Five practical tips

  • Back up the originals — always write results to a new folder rather than overwriting the source. You get room to experiment and nothing is lost if something goes wrong.
  • Think in "long edge" — product photos come in different aspect ratios, and forcing them into a square stretches them. To keep proportions, scale the long edge only, or crop to square first.
  • Phone photos can appear upside down — handset photos carry rotation metadata (EXIF). One line, ImageOps.exif_transpose(img), puts them the right way up.
  • Start at quality 80 — JPG quality 80 is usually indistinguishable from 100 by eye but halves the file size. Below 70 you start to see blocky artefacts, so there is no reason to start at 100.
  • It is faster than you think — Pillow processes an 800px image in a few milliseconds; a thousand images take tens of seconds. Far quicker than doing it by hand.

Summary

Python Tutorial: This article started from the three simplest moves — open, resize, save — and worked up to processing an entire folder, stamping a company watermark, and converting to WebP to save space. The core idea is a single sentence: find the task you repeat, and hand it to Python to run through a loop in one pass. Next time you face "a few hundred images that all need the same treatment", stop opening an image editor for every file — write twenty lines, set your parameters, run it once, and reuse the script every month after that.