import io import uuid from pathlib import Path from fastapi import HTTPException, UploadFile from PIL import Image from app.core.config import settings UPLOAD_DIR: Path = settings.upload_path ALLOWED_IMAGE_TYPES = { "image/jpeg", "image/jpg", "image/png", "image/webp", "image/svg+xml", "image/heic", "image/heif", } ALLOWED_AUDIO_PREFIXES = {"audio/webm", "audio/mp4", "audio/ogg", "audio/x-m4a"} MAX_ORIG_SIZE = (500, 500) THUMBNAIL_SIZES = { "product": (150, 150), "note": (300, 300), "attachment": (400, 300), } async def save_image(file: UploadFile, context: str = "note") -> dict: content_type = (file.content_type or "").lower() if content_type not in ALLOWED_IMAGE_TYPES: raise HTTPException(status_code=400, detail=f"Format non supporté : {content_type}") file_id = str(uuid.uuid4()) content = await file.read() orig_dir = UPLOAD_DIR / "images" / "originals" orig_dir.mkdir(parents=True, exist_ok=True) if content_type == "image/svg+xml": orig_path = orig_dir / f"{file_id}.svg" orig_path.write_bytes(content) return { "file_id": file_id, "file_path": str(orig_path.relative_to(UPLOAD_DIR)), "thumbnail_path": None, "file_type": "image", } orig_path = orig_dir / f"{file_id}.webp" img = Image.open(io.BytesIO(content)).convert("RGB") # Redimensionne l'original à 500×500 max en conservant l'aspect ratio img.thumbnail(MAX_ORIG_SIZE, Image.LANCZOS) img.save(orig_path, "WEBP", quality=85) thumb_dir = UPLOAD_DIR / "images" / "thumbnails" thumb_dir.mkdir(parents=True, exist_ok=True) thumb_path = thumb_dir / f"{file_id}_thumb.webp" thumb_size = THUMBNAIL_SIZES.get(context, (300, 300)) img_thumb = img.copy() img_thumb.thumbnail(thumb_size, Image.LANCZOS) img_thumb.save(thumb_path, "WEBP", quality=80) return { "file_id": file_id, "file_path": str(orig_path.relative_to(UPLOAD_DIR)), "thumbnail_path": str(thumb_path.relative_to(UPLOAD_DIR)), "file_type": "image", } async def save_audio(file: UploadFile) -> dict: ct = (file.content_type or "").lower().split(";")[0].strip() if ct not in ALLOWED_AUDIO_PREFIXES: raise HTTPException(status_code=400, detail=f"Format audio non supporté : {file.content_type}") file_id = str(uuid.uuid4()) audio_dir = UPLOAD_DIR / "audio" audio_dir.mkdir(parents=True, exist_ok=True) ext = ".webm" if "webm" in (file.content_type or "") else ".m4a" audio_path = audio_dir / f"{file_id}{ext}" audio_path.write_bytes(await file.read()) return { "file_id": file_id, "file_path": str(audio_path.relative_to(UPLOAD_DIR)), "thumbnail_path": None, "file_type": "audio", } def delete_media(file_id: str, file_path: str, thumbnail_path: str | None = None) -> None: (UPLOAD_DIR / file_path).unlink(missing_ok=True) if thumbnail_path: (UPLOAD_DIR / thumbnail_path).unlink(missing_ok=True)