first commit
This commit is contained in:
commit
34ea15a54d
246 changed files with 18660 additions and 0 deletions
70
backend/app/services/media.py
Normal file
70
backend/app/services/media.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue