Files
singular-particular-space/Images/thumbnail.py
JL Kruger e57d2b0a72 Add Images section — hub + 4 collection galleries, wire nav star
- Images/images.html: hub page linking all 4 collections
- Images/wayback.html, nomad-soul.html, myster-wizzard.html, exopraxist.html: collection galleries
- 429 thumbnails (360px) committed across 4 collections
- Gallery renders from baked filename arrays (no on-load fetch)
- Lightbox lazy-fetches FileBrowser for full-res on click, falls back to thumbnail
- .gitignore: allow Images/*/thumbnails/, anchor /GEMINI.md to root only, add .venv/
- index.html: wire Images star node to Images/images.html

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 20:06:19 +02:00

82 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""
thumbnail.py — Resize images to max 880px width, preserve aspect ratio, no crop.
Outputs compressed JPEGs to Images/{collection}/thumbnails/
Run from the SingularParticular repo root:
python Images/thumbnail.py
Or from inside the Images folder:
python thumbnail.py
Requirements: pip install Pillow
"""
from pathlib import Path
from PIL import Image
MAX_WIDTH = 360
QUALITY = 85
COLLECTIONS = ["WayBack", "YourNomadSoul", "MysterWizzard", "Exopraxist"]
EXTENSIONS = {".jpg", ".jpeg", ".png", ".JPG", ".JPEG", ".PNG"}
def make_thumbnail(src: Path, dest: Path):
with Image.open(src) as img:
orig_w, orig_h = img.size
# Convert to RGB for JPEG output (handles RGBA, palette, greyscale+alpha)
if img.mode not in ("RGB", "L"):
img = img.convert("RGB")
if orig_w > MAX_WIDTH:
ratio = MAX_WIDTH / orig_w
new_size = (MAX_WIDTH, int(orig_h * ratio))
img = img.resize(new_size, Image.LANCZOS)
else:
new_size = (orig_w, orig_h)
dest.parent.mkdir(parents=True, exist_ok=True)
img.save(dest, "JPEG", quality=QUALITY, optimize=True)
orig_kb = src.stat().st_size // 1024
dest_kb = dest.stat().st_size // 1024
print(f" {src.name:<40} {orig_w}x{orig_h} {orig_kb}KB → {new_size[0]}x{new_size[1]} {dest_kb}KB")
def main():
# Support running from repo root or from inside Images/
script_dir = Path(__file__).parent
base = script_dir # Images/
total_orig = 0
total_thumb = 0
for collection in COLLECTIONS:
src_dir = base / collection
thumb_dir = src_dir / "thumbnails"
print(f"\n── {collection} ──")
if not src_dir.exists():
print(f" (folder not found: {src_dir})")
continue
images = sorted(f for f in src_dir.iterdir() if f.suffix in EXTENSIONS and f.is_file())
if not images:
print(" (no images found)")
continue
for src in images:
dest = thumb_dir / (src.stem + ".jpg")
try:
make_thumbnail(src, dest)
total_orig += src.stat().st_size
total_thumb += dest.stat().st_size
except Exception as e:
print(f" ERROR {src.name}: {e}")
print(f"\n── Summary ──")
print(f" Original total: {total_orig // (1024*1024)} MB")
print(f" Thumbnail total: {total_thumb // (1024*1024)} MB")
print(f" Saved: {(total_orig - total_thumb) // (1024*1024)} MB")
print(f"\nThumbnails written to Images/{{collection}}/thumbnails/")
print("Next: upload each thumbnails/ folder to FileBrowser, replacing originals.")
if __name__ == "__main__":
main()