doxa-maps/library/scripts/posters/make-previews.py
Doxa Maps e19697673d
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Doxa Maps — the map bundler, 7 production plugins and the Experimental shelf
2026-09-25 20:07:59 +00:00

66 lines
3 KiB
Python

#!/usr/bin/env python3
"""make-previews.py — turn the rendered 3000x3000 posters into the TWO WebP sets
the app ships.
python3 make-previews.py
-> plugin-designs/my-upg-100-list/assets/previews/<zone>.webp 560px card thumbnails
-> data/posters/<zone>.webp 1500px the download
WHY TWO SIZES
The wall card shows a small thumbnail; tapping "Poster Map" must hand the user
THE SAME POSTER at a usable size. The button used to re-render a live Mapbox
canvas through useMapPoster.js — a completely different artefact from the preview
above it, carrying none of the template. Serving the pre-rendered image makes
preview and download identical BY CONSTRUCTION, and it works on Android, where a
Node generator cannot run at all.
SIZE BUDGET (measured): 1500px q80 WebP is ~217 KB per poster, 5.9 MB for all 28 —
small enough to ship in the APK, and 5in at 300dpi / 10in at 150dpi, a real print.
The 3000px PNG masters (51 MB) stay OUT of the app; export-public.mjs sends those
to a sibling folder for desktop/CDN use.
Uses PIL rather than sharp: sharp is not installed in this repo, PIL is present.
"""
import os, sys, glob
from PIL import Image
HERE = os.path.dirname(os.path.abspath(__file__))
SRC = os.path.join(HERE, 'out', 'posters')
REPO = os.path.abspath(os.path.join(HERE, '..', '..'))
# WHICH APP the previews belong to. The country-posters app renders from the SAME
# generator into the SAME out/posters, so the only thing that differs is where the
# WebPs land — parameterize rather than fork this script.
# PROFILE=my-upg-100-list (default)
# PROFILE=country-posters python3 make-previews.py
# ONLY filters by filename prefix so a country run does not re-emit the 28 zone
# posters sitting in the same folder (and vice versa).
PROFILE = os.environ.get('PROFILE', 'my-upg-100-list')
ONLY = os.environ.get('ONLY_PREFIX', '')
THUMB = os.path.join(REPO, 'plugin-designs', PROFILE, 'assets', 'previews')
FULL = os.path.join(REPO, 'data', 'posters') # a SOURCE: the build stages it into <tree>/assets/posters on request
os.makedirs(THUMB, exist_ok=True)
os.makedirs(FULL, exist_ok=True)
pngs = sorted(glob.glob(os.path.join(SRC, (ONLY + '*.png') if ONLY else '*.png')))
if not pngs:
sys.exit(f'[make-previews] no PNGs in {SRC} — run `bun run posters` then `bun run posters:shoot` first.')
tkb = fkb = 0
for p in pngs:
zone = os.path.basename(p)[:-4]
im = Image.open(p).convert('RGB')
t = im.copy(); t.thumbnail((560, 560), Image.LANCZOS)
tp = os.path.join(THUMB, zone + '.webp')
t.save(tp, 'WEBP', quality=82, method=6)
tkb += os.path.getsize(tp) / 1024
f = im.copy(); f.thumbnail((1500, 1500), Image.LANCZOS)
fp = os.path.join(FULL, zone + '.webp')
f.save(fp, 'WEBP', quality=80, method=6)
fkb += os.path.getsize(fp) / 1024
print(f'[make-previews] {len(pngs)} posters')
print(f' thumbnails -> plugin-designs/.../assets/previews {tkb/1024:5.1f} MB (inlined in the bundle)')
print(f' full size -> data/posters {fkb/1024:5.1f} MB (served + shipped in the APK)')