From 9e66c11b15b7103e963dcecf88fb152ed1e74253 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Tue, 25 Aug 2026 12:52:32 +0200 Subject: fix(node): make Photos/Video/Music enrichment survive node restarts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only thumbnail bytes were ever durable in media_cache.db — every other derived field (photo width/height/EXIF, video ffprobe duration/dims, audio cover art) lived solely on the in-memory GroupIndex entry, so a node restart re-decoded every photo through Pillow, re-ran ffprobe on every video, and re-scanned for every album cover from scratch, even though the answers already sat in the cache. Adds photo_meta and video_meta tables (content-only fields, keyed by file_id) and checks them before doing the expensive work. Audio gets no new table: mutagen reads tags and duration in one inseparable call, so caching duration alone buys nothing — instead cover-art extraction alone is skipped via a new skip_cover flag when a cached cover already exists. Deliberately excluded from all three caches: anything derived from the filename or folder path (video display_title/season/episode via guessit, audio artist/album folder-fallback) — those must keep being recomputed fresh so a rename/move is still correctly re-derived by the existing _reenrich_renamed_*_entries mechanisms, instead of silently handing back a stale parse under the new name/location. Regression tests prove cache reuse by deleting the source file (or cover) between two enrichment runs, and prove rename/move correctness survives the new cache by renaming/moving to a path that never exists on disk. --- .../src/meshbay_node/indexer/enrich_photo.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'packages/meshbay-node/src/meshbay_node/indexer/enrich_photo.py') diff --git a/packages/meshbay-node/src/meshbay_node/indexer/enrich_photo.py b/packages/meshbay-node/src/meshbay_node/indexer/enrich_photo.py index 44f9ebb..93a68cc 100644 --- a/packages/meshbay-node/src/meshbay_node/indexer/enrich_photo.py +++ b/packages/meshbay-node/src/meshbay_node/indexer/enrich_photo.py @@ -143,6 +143,22 @@ class PhotoEnricher: on_done: Callable[[str, dict], Awaitable[None]], ) -> None: async with self._sem: + # entry.id is the file's own content hash — the same bytes + # produce the same thumbnail, so a hit here means this exact + # content was already decoded, resized and EXIF-read at some + # point (this run, an earlier one, even a previous daemon + # process — media_cache.db is the durable half of this). + # Without this check, every restart re-ran Pillow over every + # image in every configured photo_root from scratch — the + # in-memory GroupIndex enrichment fields don't survive a + # restart, but this cache does, and nothing was reading it + # before spawning the expensive work. + cached_hash = await self._media_cache.get_thumb_hash_by_file_id(entry.id) + cached_meta = await self._media_cache.get_photo_meta(entry.id) if cached_hash else None + if cached_hash and cached_meta: + await on_done(entry.id, {**cached_meta, "thumb_hash": cached_hash}) + return + fields: dict = {} try: thumb, width, height, taken_at, camera = await asyncio.wait_for( @@ -153,6 +169,7 @@ class PhotoEnricher: fields["camera"] = camera thumb_hash = blake3.blake3(thumb).hexdigest() await self._media_cache.put_thumb(thumb_hash, entry.id, thumb) + await self._media_cache.put_photo_meta(entry.id, width, height, taken_at, camera) fields["thumb_hash"] = thumb_hash except Exception as e: log.warning("Photo enrichment failed for %s: %s", file_path, e) -- cgit v1.2.3