Skip to content

← Blog · Tools · 2026-09-29

Compression tool

A small tool to shrink a folder of photos before sending, publishing or archiving them

Contents

This tool reduces the size of a set of photos (a whole folder) by recompressing them as JPEG or WebP, with an optional reduction of their dimensions. The compressed photos are written to a separate folder, the originals are never touched.

It is useful for sending photos by email, publishing them on a website or freeing up disk space, with a preview of the result and of the space saved before you run it on the whole series.

It was developed and tested on Mac only. It should also work on Windows but has not been tested there (see On Windows for the differences). The tool itself is in French: the names of its buttons and options are given below in French, with a translation the first time they appear.

Downloads

  • squeeze.pyv1.0.0 · SHA-256 012990e9816bd2e5… The script: a single Python file.

No warranty

This tool, made with Claude, is provided without any warranty. You use it at your own risk. The script never modifies the original photos and refuses to export into their folder, but always keep a backup of your photos: JPEG or WebP compression permanently discards information.

Installation

You need Python 3.9 or newer. The simplest way is the installer from python.org.

Do this once, in the Terminal, from the folder where squeeze.py is (here Downloads):

cd ~/Downloads
python3 -m venv squeeze-env
squeeze-env/bin/python3 -m pip install PySide6 Pillow

This creates the virtual environment and installs the libraries.

Then, to launch the tool, just use the virtual environment's Python on your photo folder (or without an argument if you would rather pick the folder later):

squeeze-env/bin/python3 squeeze.py ~/Photos/trip

The tool reads JPEG, PNG, TIFF and WebP photos but not RAW files.

On Windows

The tool has only been developed and tested on Mac, but it relies on libraries that also exist on Windows (Qt and Pillow), so it should work there without changes.

Install Python if you do not have it yet, from python.org. The commands are the same as on Mac, in the command prompt, with py instead of python3 and Scripts\python instead of bin/python3. Do this once (in the folder where you put the tool):

py -m venv squeeze-env
squeeze-env\Scripts\python -m pip install PySide6 Pillow

Then, to launch the tool:

squeeze-env\Scripts\python squeeze.py "C:\Photos\trip"

The Ctrl key replaces ⌘ (see the shortcuts table).

How to use it

1. Open a folder

Open the photo folder with "Ouvrir un dossier…" ("Open a folder…"), or by giving it at launch. The list on the left shows each photo with its size, and the total for the folder appears at the bottom right.

2. Set the compression

  • The format ("Format"): JPEG, readable everywhere, or WebP, usually 25 to 35% lighter at the same quality but a little less universal.
  • The quality ("Qualité"), from 10 to 100. Around 80, the difference is very hard to see on screen for a size that is often divided by three or four; below 60, flat areas and gradients (the sky, for example) start to show banding.
  • The maximum long side ("Côté long max.": 4096, 3000, 2048, 1600, 1200 or 800 px): larger photos are shrunk, smaller ones are never enlarged. Reducing the dimensions is the most effective lever: going from 6000 to 2048 px divides the number of pixels by almost 9.
  • Keep the metadata ("Conserver les métadonnées"): copies the EXIF data (camera, date, settings, GPS) and the colour profile. Untick it to publish photos without location or equipment information, and to save a few KB.
  • Keep the original if it is already lighter ("Garder l'original s'il est déjà plus léger"): an already heavily compressed photo can grow when recompressed. It is then simply copied (unless you asked for a size reduction).

3. Check the preview

When you click a photo in the list or change a setting, the tool compresses that photo in memory and shows it as it will be exported, with its size in pixels, its weight before and after and the saving (for example 2048 × 1366 px — 2.2 MB → 71.2 KB (−97 %)). Compare the result with the original on a detailed photo (foliage, feathers): that is where flaws show up first.

4. Compress

Choose the output folder (by default <folder>_compresse, next to the original folder), then click "Compresser" ("Compress"). The photos keep their name, with the extension of the chosen format. A progress bar is displayed and the job can be cancelled. At the end, the tool shows the total size before and after (for example 12.9 MB → 428 KB (−97 %)).

An unreadable photo does not stop the job: it is reported at the end and the others are compressed as usual.

Saving

The settings and the output folder are saved automatically in a squeeze_reglages.json file placed next to the photos. When you reopen the folder, your settings are back.

Shortcuts

Action Mac Windows
Open a folder ⌘O Ctrl+O
Compress ⌘E Ctrl+E

Under the hood

Compressing one photo

with Image.open(path) as src:
    exif = src.getexif()
    icc = src.info.get("icc_profile") if opts["metadata"] else None
    img = ImageOps.exif_transpose(src)          # applies the rotation then removes it from the EXIF
    img.load()
...
side = opts["max_side"]
if side and max(img.size) > side:               # never enlarge
    img.thumbnail((side, side), Image.LANCZOS)

The rotation requested by the camera (photos taken in portrait) is applied to the pixels before anything is saved, then reset to "normal" in the EXIF: without this, the photo would appear lying on its side after being shrunk. thumbnail keeps the proportions and only ever shrinks.

kwargs = {"quality": int(opts["quality"])}
if opts["format"] == "jpg":
    kwargs.update(optimize=True, progressive=True)
else:
    kwargs.update(method=6)

In JPEG, optimize computes more efficient coding tables and progressive saves the image in several passes: both reduce the size a little at no cost in quality. In WebP, method=6 takes longer to find the best compression.

Parallel processing

with ThreadPoolExecutor(max_workers=max(1, (os.cpu_count() or 2) - 1)) as pool:
    futures = [(f, pool.submit(export_one, f, self.out_dir, self.opts)) for f in self.files]
    for f, fut in futures:
        try:
            b, a = fut.result()
            before, after = before + b, after + a
        except Exception as e:              # an unreadable photo does not stop the batch
            errors.append(f"{Path(f).name} : {e}")

The photos are compressed in parallel, leaving one core free so the window stays responsive. Pillow releases Python's lock while decoding and encoding, which lets the threads really work at the same time.

Keep reading