Files
Magent/backend/app/routers/images.py
2026-01-22 22:49:57 +13:00

83 lines
2.9 KiB
Python

import os
import re
import mimetypes
from fastapi import APIRouter, HTTPException, Response
from fastapi.responses import FileResponse, RedirectResponse
import httpx
from ..runtime import get_runtime_settings
router = APIRouter(prefix="/images", tags=["images"])
_TMDB_BASE = "https://image.tmdb.org/t/p"
_ALLOWED_SIZES = {"w92", "w154", "w185", "w342", "w500", "w780", "original"}
def _safe_filename(path: str) -> str:
trimmed = path.strip("/")
trimmed = trimmed.replace("/", "_")
safe = re.sub(r"[^A-Za-z0-9_.-]", "_", trimmed)
return safe or "image"
async def cache_tmdb_image(path: str, size: str = "w342") -> bool:
if not path or "://" in path or ".." in path:
return False
if not path.startswith("/"):
path = f"/{path}"
if size not in _ALLOWED_SIZES:
return False
runtime = get_runtime_settings()
cache_mode = (runtime.artwork_cache_mode or "remote").lower()
if cache_mode != "cache":
return False
cache_dir = os.path.join(os.getcwd(), "data", "artwork", "tmdb", size)
os.makedirs(cache_dir, exist_ok=True)
file_path = os.path.join(cache_dir, _safe_filename(path))
if os.path.exists(file_path):
return True
url = f"{_TMDB_BASE}/{size}{path}"
async with httpx.AsyncClient(timeout=20) as client:
response = await client.get(url)
response.raise_for_status()
content = response.content
with open(file_path, "wb") as handle:
handle.write(content)
return True
@router.get("/tmdb")
async def tmdb_image(path: str, size: str = "w342"):
if not path or "://" in path or ".." in path:
raise HTTPException(status_code=400, detail="Invalid image path")
if not path.startswith("/"):
path = f"/{path}"
if size not in _ALLOWED_SIZES:
raise HTTPException(status_code=400, detail="Invalid size")
runtime = get_runtime_settings()
cache_mode = (runtime.artwork_cache_mode or "remote").lower()
url = f"{_TMDB_BASE}/{size}{path}"
if cache_mode != "cache":
return RedirectResponse(url=url)
cache_dir = os.path.join(os.getcwd(), "data", "artwork", "tmdb", size)
os.makedirs(cache_dir, exist_ok=True)
file_path = os.path.join(cache_dir, _safe_filename(path))
headers = {"Cache-Control": "public, max-age=86400"}
if os.path.exists(file_path):
media_type = mimetypes.guess_type(file_path)[0] or "image/jpeg"
return FileResponse(file_path, media_type=media_type, headers=headers)
try:
await cache_tmdb_image(path, size)
if os.path.exists(file_path):
media_type = mimetypes.guess_type(file_path)[0] or "image/jpeg"
return FileResponse(file_path, media_type=media_type, headers=headers)
raise HTTPException(status_code=502, detail="Image cache failed")
except httpx.HTTPError as exc:
raise HTTPException(status_code=502, detail=f"Image fetch failed: {exc}") from exc