import hashlib import io import os from pathlib import Path import httpx from fastapi import APIRouter, HTTPException from fastapi.responses import FileResponse from PIL import Image from sqlalchemy import text from .db import engine # Local WebP cache for Discogs release covers. RecordGod + DealGod share a VPS, so there's no point # in a remote image service or a second copy — RecordGod compresses + serves covers itself. # Storage path is CONFIGURABLE (DISC_IMAGE_DIR env): a default now, per-store storage override later # when shops host their own. Covers are tiny (q72, longest side <=600px). On-demand: converted on first # request, then served from disk. See RECORDGOD_NAVIGATOR_PLAN / recordgod-engine. IMAGE_DIR = Path(os.getenv("DISC_IMAGE_DIR", "/app/disc_images")) WEBP_QUALITY = 72 MAX_DIM = 600 router = APIRouter() def _path(release_id: int) -> Path: h = hashlib.sha256(str(release_id).encode()).hexdigest() return IMAGE_DIR / h[:2] / f"{h}.webp" @router.get("/img/r/{release_id}") async def release_image(release_id: int): """Tiny WebP of a release cover — convert from disc_release.thumb on first hit, then serve from disk.""" p = _path(release_id) if not p.exists(): async with engine.connect() as conn: row = (await conn.execute(text("SELECT thumb FROM disc_release WHERE id = :r"), {"r": release_id})).first() src = row[0] if row else None if not src: raise HTTPException(404, "no source image") try: async with httpx.AsyncClient(timeout=15, follow_redirects=True) as c: r = await c.get(src, headers={"User-Agent": "Mozilla/5.0 (RecordGod)"}) r.raise_for_status() img = Image.open(io.BytesIO(r.content)).convert("RGB") img.thumbnail((MAX_DIM, MAX_DIM)) p.parent.mkdir(parents=True, exist_ok=True) img.save(p, "WEBP", quality=WEBP_QUALITY, method=6) except Exception: raise HTTPException(502, "image fetch/convert failed") return FileResponse(p, media_type="image/webp", headers={"Cache-Control": "public, max-age=2592000"})