"""Shared media type constants and helpers. Single source of truth for the file extensions and MIME types handled by the image support (roadmap #108) and reused by the audio/video players (#109). Keeping these sets here avoids the previous duplication (``indexer.py``, ``attachment_indexer.py`` and ``main.py`` each carried their own copy). """ from __future__ import annotations import mimetypes # Image extensions viewable in the browser (HEIC/HEIF deliberately excluded — # no browser decodes them natively; see roadmap #108). IMAGE_EXTENSIONS: frozenset[str] = frozenset({ ".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".bmp", ".ico", }) # Audio extensions (socle for #109, not wired into the index yet). AUDIO_EXTENSIONS: frozenset[str] = frozenset({ ".mp3", ".m4a", ".aac", ".wav", ".ogg", ".oga", ".opus", ".flac", }) # Video extensions (socle for #109, not wired into the index yet). VIDEO_EXTENSIONS: frozenset[str] = frozenset({ ".mp4", ".webm", ".mov", ".m4v", }) MEDIA_EXTENSIONS: frozenset[str] = IMAGE_EXTENSIONS | AUDIO_EXTENSIONS | VIDEO_EXTENSIONS # Explicit MIME types for extensions ``mimetypes`` gets wrong or does not know. _MIME_OVERRIDES: dict[str, str] = { ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".svg": "image/svg+xml", ".ico": "image/x-icon", ".webp": "image/webp", ".m4a": "audio/mp4", ".oga": "audio/ogg", ".opus": "audio/ogg", ".mov": "video/quicktime", ".m4v": "video/mp4", } def is_image(ext: str) -> bool: """Return True when *ext* (with leading dot, any case) is an image.""" return ext.lower() in IMAGE_EXTENSIONS def is_audio(ext: str) -> bool: """Return True when *ext* is an audio extension.""" return ext.lower() in AUDIO_EXTENSIONS def is_video(ext: str) -> bool: """Return True when *ext* is a video extension.""" return ext.lower() in VIDEO_EXTENSIONS def is_media(ext: str) -> bool: """Return True when *ext* is any supported image/audio/video extension.""" return ext.lower() in MEDIA_EXTENSIONS def media_mime_type(path: str) -> str: """Return the best MIME type for *path* (extension based). Falls back to ``application/octet-stream`` when the type is unknown. """ lower = path.lower() for ext, mime in _MIME_OVERRIDES.items(): if lower.endswith(ext): return mime guessed, _ = mimetypes.guess_type(path) return guessed or "application/octet-stream"