#!/usr/bin/env python3 """Tile stills into one labelled sheet so a whole ad can be reviewed in one image. python3 contact-sheet.py "still-*.jpg" -o stills.jpg [--cols 3] [--width 640] Quote the pattern: zsh aborts on unmatched globs before Python sees them. Needs Pillow (pip install pillow). """ import argparse import glob import os import re from PIL import Image, ImageDraw ap = argparse.ArgumentParser() ap.add_argument("pattern") ap.add_argument("-o", "--out", default="sheet.jpg") ap.add_argument("--cols", type=int, default=3) ap.add_argument("--width", type=int, default=640) a = ap.parse_args() def order(path): nums = re.findall(r"\d+(?:\.\d+)?", os.path.basename(path)) return float(nums[-1]) if nums else 0.0 files = sorted(glob.glob(a.pattern), key=order) if not files: raise SystemExit(f"no files match {a.pattern}") images = [Image.open(f).convert("RGB") for f in files] tw = a.width th = int(tw * images[0].height / images[0].width) rows = (len(images) + a.cols - 1) // a.cols sheet = Image.new("RGB", (a.cols * (tw + 8), rows * (th + 28)), "#888") draw = ImageDraw.Draw(sheet) for k, (f, im) in enumerate(zip(files, images)): x, y = (k % a.cols) * (tw + 8), (k // a.cols) * (th + 28) sheet.paste(im.resize((tw, int(tw * im.height / im.width)), Image.LANCZOS), (x, y)) draw.text((x + 6, y + th + 6), os.path.basename(f), fill="white") sheet.save(a.out, quality=85) print(f"{len(images)} images -> {a.out}")