Files
ObsiGate/docs/features/image-support.md
T
bruno eccbf7474e
CI / lint (push) Successful in 1m57s
CI / security (push) Successful in 1m22s
CI / test (push) Successful in 4m32s
CI / build (push) Failing after 1m16s
CI / e2e (push) Skipped
feat: support complet des images — arborescence, visionneuse, indexation #108
2026-09-23 07:47:04 -04:00

4.8 KiB
Raw Blame History

#108 — Support complet des images (arborescence, visionneuse, indexation)

Version livrée : 2.17.0 · Statut : ✅ · Impact : 🟡 Zone : backend (indexer, main, media_types, media_thumbs) + frontend (viewer.js, utils.js, style.css).

Contexte

Seul l'affichage inline dans un document markdown (![[image.png]]) fonctionnait. L'image isolée était invisible dans l'arborescence (filtrée par SUPPORTED_EXTENSIONS) et son affichage standalone était cassé : le <img> généré par api_file_view() pointait vers /api/file/{vault}/raw, un endpoint qui renvoie du JSON (FileRawResponse) et non des octets d'image.

Ce qui a été livré

A. Arborescence & indexation

  • backend/media_types.py (nouveau) : source unique des extensions IMAGE_EXTENSIONS, AUDIO_EXTENSIONS, VIDEO_EXTENSIONS (+ helpers is_image/is_audio/is_video/is_media/media_mime_type). Socle réutilisé par #109. attachment_indexer.py et api_file_view() ne dupliquent plus la liste.
  • Les extensions image sont intégrées à SUPPORTED_EXTENSIONS (indexer.py) avec une branche binaire : _scan_vault et _index_single_file_sync indexent nom / taille / mtime et ne lisent jamais les octets (content: "", content_preview: ""). Le TF-IDF reste donc propre et aucune UnicodeDecodeError ne pollue les logs.
  • Le reindex watchdog suit automatiquement (même filtre d'extensions).
  • Le filtre ext:png / ext:jpg de la recherche avancée est opérationnel dès lors que les images entrent dans l'index.
  • /api/dashboard expose image_count (par vault) et total_images (global), séparés du file_count général.

B. Affichage standalone (correctif)

  • api_file_view() génère désormais src="/api/image/{vault}?path=…" (chemin URL-encodé) au lieu de /raw.
  • viewer.js utilise le même endpoint (plus de bouton « Plein écran » cassé).
  • Sécurité SVG : /api/image (et le repli de /api/media/.../thumb) ajoute Content-Security-Policy: sandbox pour les .svg, ce qui empêche l'exécution du JavaScript embarqué quand le fichier est ouvert directement dans un onglet (XSS same-origin). Dans une balise <img>, l'en-tête est sans effet. Le middleware de sécurité ne remplace plus une politique stricte posée par une route.

C. Miniatures

  • GET /api/media/{vault}/thumb?path=…&size=… : miniature WebP générée avec pillow>=10.0, mise en cache sous <OBSIGATE_DATA_DIR>/.obsigate-cache/thumbs/{sha1}.webp. La clé de cache embarque mtime + taille, donc toute édition invalide naturellement la vignette.
  • Génération dans un thread (run_in_executor) avec timeout 2 s ; repli sur l'original en cas d'échec. SVG : l'original est servi tel quel (Pillow ne décode pas le SVG) ; GIF/WebP animés : première frame.

D. Visionneuse

renderImageViewer() (frontend/js/viewer.js) remplace l'ancien rendu minimal :

  • image centrée object-fit: contain ; zoom molette 0,1×–8×, pan au glisser (Pointer Events), double-clic = réinitialisation, raccourcis + / - / 0 ;
  • boutons +/−/reset et badge de zoom ;
  • navigation ←/→ entre les images du même dossier (via /api/browse) et pellicule de miniatures (/api/media/.../thumb, loading="lazy") ;
  • barre d'outils : « Ouvrir l'original » (nouvel onglet /api/image), téléchargement, panneau métadonnées repliable (dimensions via naturalWidth/Height, taille, type MIME, chemin, date), lightbox plein écran (fond rgba(0,0,0,.9), Échap pour quitter) ;
  • EXT_ICONS : extensions image → icône Lucide image ;
  • compatible Split View (#75) : rendu dans getContentArea() du panneau actif.

E. Tests

  • tests/test_image_api.py : octets + MIME sur /api/image, en-tête sandbox des SVG, URL /api/image dans le HTML de api_file_view, encodage des chemins accentués, miniatures WebP + repli SVG + refus non-image.
  • tests/test_image_indexing.py : image présente dans list_directory et path_index, indexée avec content == "", pertinence watchdog, compteurs dashboard, filtre ext:png.
  • tests/frontend/image-viewer.test.mjs : helpers purs (clampImageZoom, isImagePath, buildImageUrl) + vérifications statiques (zoom/pan/nav, absence de /raw dans la visionneuse, CSS, icônes).
  • tests/e2e/image-viewer.spec.js : ouverture d'une image depuis l'arborescence, réponse /api/image en image/png, zoom molette, navigation par la pellicule (fixtures test_vault/sample-image.png + sample-vector.svg).

Limitations connues

  • HEIC/HEIF (iPhone) : non décodables par les navigateurs → hors scope ; pillow-heif envisagé en v2.
  • Le SVG passe par l'original (pas de rendu bitmap côté serveur) : les miniatures de dossiers SVG ne sont pas générées.