-
-
Save andreialba/0bd9a09eec0dffd6ae56748178cd511f to your computer and use it in GitHub Desktop.
Image Distributor & Thumbnail Generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Image Distributor & Thumbnail Generator | |
| ---------------------------------------- | |
| 1. Creates 10 subfolders inside a target directory | |
| 2. Randomly distributes ~1000 images across them (~100 each) | |
| 3. For each image, generates 10 WordPress thumbnail sizes | |
| Usage: | |
| python generate_thumbnails.py /path/to/your/images | |
| Requirements: | |
| pip install Pillow | |
| """ | |
| import os | |
| import sys | |
| import random | |
| import shutil | |
| from pathlib import Path | |
| from PIL import Image | |
| # ── Configuration ───────────────────────────────────────────────────────────── | |
| NUM_FOLDERS = 10 | |
| # 10 WordPress / web thumbnail sizes — all within 1024px max dimension | |
| THUMBNAIL_SIZES = [ | |
| (100, 100), # WooCommerce tiny | |
| (150, 150), # WP default thumbnail (square) | |
| (250, 250), # Sidebar / widget square | |
| (300, 300), # WP medium square | |
| (320, 240), # Mobile small landscape | |
| (400, 300), # Card / widget landscape | |
| (600, 400), # Blog featured image | |
| (768, 432), # Tablet 16:9 | |
| (800, 600), # Medium-large landscape | |
| (1024, 576), # Full-width 16:9 | |
| ] | |
| # Supported input image extensions | |
| IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".tiff", ".gif"} | |
| # ── Helpers ─────────────────────────────────────────────────────────────────── | |
| def collect_images(source_dir: Path) -> list[Path]: | |
| """Return a shuffled list of image files in source_dir (non-recursive).""" | |
| images = [ | |
| f for f in source_dir.iterdir() | |
| if f.is_file() and f.suffix.lower() in IMAGE_EXTENSIONS | |
| ] | |
| random.shuffle(images) | |
| return images | |
| def create_folders(base_dir: Path) -> list[Path]: | |
| """Create 10 numbered subfolders and return their paths.""" | |
| folders = [] | |
| for i in range(1, NUM_FOLDERS + 1): | |
| folder = base_dir / f"batch_{i:02d}" | |
| folder.mkdir(exist_ok=True) | |
| folders.append(folder) | |
| return folders | |
| def distribute_images(images: list[Path], folders: list[Path]) -> dict[Path, list[Path]]: | |
| """ | |
| Move images into folders as evenly as possible. | |
| Returns a dict mapping each folder to its list of moved images. | |
| """ | |
| mapping: dict[Path, list[Path]] = {f: [] for f in folders} | |
| for idx, img_path in enumerate(images): | |
| target_folder = folders[idx % NUM_FOLDERS] | |
| dest = target_folder / img_path.name | |
| # Handle filename collisions | |
| if dest.exists(): | |
| stem = img_path.stem | |
| suffix = img_path.suffix | |
| dest = target_folder / f"{stem}_{idx}{suffix}" | |
| shutil.move(str(img_path), dest) | |
| mapping[target_folder].append(dest) | |
| return mapping | |
| def make_thumbnail(src: Path, folder: Path, width: int, height: int) -> None: | |
| """ | |
| Create a thumbnail of (width x height) from src image. | |
| Uses LANCZOS resampling. Fits the image within the box without cropping. | |
| Saves as JPEG regardless of source format. | |
| """ | |
| thumb_name = f"{src.stem}-thumb-{width}x{height}.jpg" | |
| thumb_path = folder / thumb_name | |
| with Image.open(src) as img: | |
| # Convert palette / RGBA images so they save cleanly as JPEG | |
| if img.mode in ("P", "RGBA", "LA"): | |
| img = img.convert("RGB") | |
| elif img.mode != "RGB": | |
| img = img.convert("RGB") | |
| img_copy = img.copy() | |
| img_copy.thumbnail((width, height), Image.LANCZOS) | |
| img_copy.save(thumb_path, "JPEG", quality=85, optimize=True) | |
| def generate_thumbnails(mapping: dict[Path, list[Path]]) -> None: | |
| """Generate all thumbnail sizes for every image in every folder.""" | |
| total_thumbs = 0 | |
| for folder, images in mapping.items(): | |
| print(f"\n 📁 {folder.name} ({len(images)} images)") | |
| for img_path in images: | |
| for width, height in THUMBNAIL_SIZES: | |
| try: | |
| make_thumbnail(img_path, folder, width, height) | |
| total_thumbs += 1 | |
| except Exception as e: | |
| print(f" ⚠️ Skipped thumbnail {width}x{height} for {img_path.name}: {e}") | |
| thumb_count = len(images) * len(THUMBNAIL_SIZES) | |
| print(f" ✅ {thumb_count} thumbnails generated") | |
| print(f"\n 🎉 Total thumbnails created: {total_thumbs}") | |
| # ── Main ────────────────────────────────────────────────────────────────────── | |
| def main(): | |
| if len(sys.argv) < 2: | |
| print("Usage: python generate_thumbnails.py /path/to/your/images") | |
| sys.exit(1) | |
| source_dir = Path(sys.argv[1]).resolve() | |
| if not source_dir.is_dir(): | |
| print(f"Error: '{source_dir}' is not a valid directory.") | |
| sys.exit(1) | |
| print(f"\n🔍 Scanning: {source_dir}") | |
| images = collect_images(source_dir) | |
| print(f" Found {len(images)} image(s)") | |
| if not images: | |
| print("No supported images found. Supported formats: JPG, PNG, WEBP, BMP, TIFF, GIF") | |
| sys.exit(0) | |
| print(f"\n📂 Creating {NUM_FOLDERS} folders...") | |
| folders = create_folders(source_dir) | |
| print(f"\n🔀 Distributing images across folders...") | |
| mapping = distribute_images(images, folders) | |
| for folder, imgs in mapping.items(): | |
| print(f" {folder.name}: {len(imgs)} images") | |
| print(f"\n🖼️ Generating thumbnails ({len(THUMBNAIL_SIZES)} sizes per image)...") | |
| print(f" Sizes: {', '.join(f'{w}x{h}' for w, h in THUMBNAIL_SIZES)}") | |
| generate_thumbnails(mapping) | |
| print("\n✅ All done!") | |
| print(f" 📁 {NUM_FOLDERS} folders") | |
| print(f" 🖼️ {len(images)} original images distributed") | |
| print(f" 🔖 Up to {len(images) * len(THUMBNAIL_SIZES)} thumbnails generated") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment