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/png", "image/webp", "image/svg+xml"} ALLOWED_AUDIO_TYPES = {"audio/webm", "audio/mp4"} THUMBNAIL_SIZES = { "product": (150, 150), "note": (300, 300), "attachment": (400, 300), } async def save_image(file: UploadFile, context: str = "note") -> dict: if file.content_type not in ALLOWED_IMAGE_TYPES: raise HTTPException(status_code=400, detail=f"Format non supporté : {file.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) orig_path = orig_dir / f"{file_id}.webp" if file.content_type == "image/svg+xml": 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", } img = Image.open(io.BytesIO(content)).convert("RGB") 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" size = THUMBNAIL_SIZES.get(context, (300, 300)) img_thumb = Image.open(io.BytesIO(content)).convert("RGB") img_thumb.thumbnail(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: if file.content_type not in ALLOWED_AUDIO_TYPES: 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)