Files
home_hub/backend/app/services/media.py
T
gilles 9aaa5fb562 fix(audio+gps): lecture audio multi-navigateur + icône GPS dans tuile note
Audio :
- MediaRecorder détecte le format supporté : webm (Chrome/Firefox) ou mp4 (Safari/iOS)
- Extension sauvegardée correctement (.webm ou .m4a) selon le navigateur
- Backend : ALLOWED_AUDIO_PREFIXES remplace le set strict, strip des codec suffixes

GPS (note card) :
- Icône fa-location-dot (accent vert) avec tooltip lat/lon remplace l'emoji 📍

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 15:55:35 +02:00

97 lines
3.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)