From 9aaa5fb5620aa52d65347fda57d45119381e3479 Mon Sep 17 00:00:00 2001 From: Gilles Soulier Date: Mon, 25 May 2026 15:55:35 +0200 Subject: [PATCH] =?UTF-8?q?fix(audio+gps):=20lecture=20audio=20multi-navig?= =?UTF-8?q?ateur=20+=20ic=C3=B4ne=20GPS=20dans=20tuile=20note?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- backend/app/api/notes.py | 7 ++++--- backend/app/services/media.py | 5 +++-- frontend/src/pages/NotesPage.tsx | 14 +++++++++----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/backend/app/api/notes.py b/backend/app/api/notes.py index edf6130..b93360e 100644 --- a/backend/app/api/notes.py +++ b/backend/app/api/notes.py @@ -9,7 +9,7 @@ from app.core.database import get_session from app.core.redis import enqueue from app.models.notes import NoteItem, NoteAttachment from app.schemas.notes import NoteCreate, NoteUpdate, NoteResponse -from app.services.media import save_image, save_audio, delete_media, ALLOWED_IMAGE_TYPES, ALLOWED_AUDIO_TYPES +from app.services.media import save_image, save_audio, delete_media, ALLOWED_IMAGE_TYPES, ALLOWED_AUDIO_PREFIXES router = APIRouter() @@ -130,10 +130,11 @@ async def add_attachment( if not note: raise HTTPException(404, "Note introuvable") - if file.content_type in ALLOWED_IMAGE_TYPES: + ct = (file.content_type or "").lower().split(";")[0].strip() + if ct in ALLOWED_IMAGE_TYPES: media = await save_image(file, context="note") file_type = "image" - elif file.content_type in ALLOWED_AUDIO_TYPES: + elif ct in ALLOWED_AUDIO_PREFIXES: media = await save_audio(file) file_type = "audio" else: diff --git a/backend/app/services/media.py b/backend/app/services/media.py index 4872e7b..c4dfb44 100644 --- a/backend/app/services/media.py +++ b/backend/app/services/media.py @@ -13,7 +13,7 @@ ALLOWED_IMAGE_TYPES = { "image/jpeg", "image/jpg", "image/png", "image/webp", "image/svg+xml", "image/heic", "image/heif", } -ALLOWED_AUDIO_TYPES = {"audio/webm", "audio/mp4"} +ALLOWED_AUDIO_PREFIXES = {"audio/webm", "audio/mp4", "audio/ogg", "audio/x-m4a"} MAX_ORIG_SIZE = (500, 500) @@ -70,7 +70,8 @@ async def save_image(file: UploadFile, context: str = "note") -> dict: async def save_audio(file: UploadFile) -> dict: - if file.content_type not in ALLOWED_AUDIO_TYPES: + 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()) diff --git a/frontend/src/pages/NotesPage.tsx b/frontend/src/pages/NotesPage.tsx index a800163..1fd48db 100644 --- a/frontend/src/pages/NotesPage.tsx +++ b/frontend/src/pages/NotesPage.tsx @@ -41,19 +41,23 @@ function NoteCard({ note, onEdit, onDelete, onAddPhoto, onAddAudio, onDeleteAtt async function startRecord() { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }) - const recorder = new MediaRecorder(stream) + // Choisir le format supporté par le navigateur (Safari → mp4, Chrome/Firefox → webm) + const mimeType = ['audio/webm', 'audio/mp4', 'audio/ogg'].find(t => MediaRecorder.isTypeSupported(t)) ?? '' + const recorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream) chunksRef.current = [] recorder.ondataavailable = e => { if (e.data.size > 0) chunksRef.current.push(e.data) } recorder.onstop = () => { stream.getTracks().forEach(t => t.stop()) - const blob = new Blob(chunksRef.current, { type: 'audio/webm' }) - onAddAudio(new File([blob], 'enregistrement.webm', { type: 'audio/webm' })) + const type = recorder.mimeType || mimeType || 'audio/webm' + const ext = type.includes('mp4') ? 'm4a' : 'webm' + const blob = new Blob(chunksRef.current, { type }) + onAddAudio(new File([blob], `enregistrement.${ext}`, { type })) } recorder.start() recorderRef.current = recorder setRecording(true) } catch { - // micro non disponible + // micro non disponible ou permission refusée } } @@ -121,7 +125,7 @@ function NoteCard({ note, onEdit, onDelete, onAddPhoto, onAddAudio, onDeleteAtt {note.tags.map(t => ( {t} ))} - {note.gps_lat && 📍} + {note.gps_lat != null && } {/* Actions */}