54 lines
2.2 KiB
Python
54 lines
2.2 KiB
Python
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"})
|