- 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>
82 lines
2.7 KiB
Python
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()
|