70 lines
2 KiB
Python
70 lines
2 KiB
Python
import io
|
|
import re
|
|
import uuid
|
|
from pathlib import Path
|
|
|
|
from fastapi import UploadFile
|
|
from PIL import Image
|
|
|
|
from app.storage import get_storage
|
|
|
|
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp"}
|
|
MAX_BYTES = 8 * 1024 * 1024
|
|
|
|
VARIANTS = {
|
|
"lg": 2400,
|
|
"md": 1200,
|
|
"thumb": 400,
|
|
}
|
|
|
|
|
|
def public_url_for_path(file_path: str) -> str:
|
|
normalized = file_path.replace("\\", "/").lstrip("/")
|
|
return get_storage().public_url(normalized)
|
|
|
|
|
|
def thumb_path_for(file_path: str) -> str | None:
|
|
normalized = file_path.replace("\\", "/")
|
|
if "_lg." in normalized:
|
|
return re.sub(r"_lg\.(\w+)$", r"_thumb.\1", normalized)
|
|
base, ext = normalized.rsplit(".", 1) if "." in normalized else (normalized, "webp")
|
|
return f"{base}_thumb.{ext}"
|
|
|
|
|
|
def thumb_url_for_path(file_path: str) -> str | None:
|
|
thumb = thumb_path_for(file_path)
|
|
if not thumb:
|
|
return None
|
|
return get_storage().public_url(thumb)
|
|
|
|
|
|
def _encode_variant(img: Image.Image, max_edge: int) -> bytes:
|
|
work = img.convert("RGB") if img.mode in ("RGBA", "P") else img
|
|
w, h = work.size
|
|
if max(w, h) > max_edge:
|
|
ratio = max_edge / max(w, h)
|
|
work = work.resize((int(w * ratio), int(h * ratio)), Image.Resampling.LANCZOS)
|
|
buf = io.BytesIO()
|
|
work.save(buf, format="WEBP", quality=85 if max_edge >= 1200 else 80)
|
|
return buf.getvalue()
|
|
|
|
|
|
def save_image(upload: UploadFile) -> str:
|
|
suffix = Path(upload.filename or "").suffix.lower()
|
|
if suffix not in ALLOWED_EXTENSIONS:
|
|
raise ValueError("Only JPG, PNG, and WebP images are allowed")
|
|
|
|
raw = upload.file.read()
|
|
if len(raw) > MAX_BYTES:
|
|
raise ValueError("Image must be under 8 MB")
|
|
|
|
storage = get_storage()
|
|
base = uuid.uuid4().hex
|
|
primary_key = f"{base}_lg.webp"
|
|
|
|
with Image.open(io.BytesIO(raw)) as img:
|
|
for variant, edge in VARIANTS.items():
|
|
key = f"{base}_{variant}.webp"
|
|
storage.save(key, _encode_variant(img, edge), "image/webp")
|
|
|
|
return primary_key
|