aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/indexer/enrich.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/indexer/enrich.py')
-rw-r--r--packages/meshbay-node/src/meshbay_node/indexer/enrich.py63
1 files changed, 46 insertions, 17 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/enrich.py b/packages/meshbay-node/src/meshbay_node/indexer/enrich.py
index 4dddc27..ae3f3dc 100644
--- a/packages/meshbay-node/src/meshbay_node/indexer/enrich.py
+++ b/packages/meshbay-node/src/meshbay_node/indexer/enrich.py
@@ -129,15 +129,40 @@ class Enricher:
) -> None:
async with self._sem:
fields: dict = {}
+
+ # entry.id is the file's own content hash — a probe/thumbnail
+ # cache hit here means this exact content was already handled
+ # (this run, an earlier one, even a previous daemon process).
+ # Neither survives a restart on its own (the in-memory
+ # GroupIndex entry is rebuilt from scratch every time), but
+ # media_cache.db does — nothing was checking it before spawning
+ # ffprobe/ffmpeg again on every file, every restart.
+ #
+ # Deliberately *not* cached this way: display_title/season/
+ # episode. Those come from guessit against entry.name, which is
+ # exactly what a rename needs re-derived —
+ # _reenrich_renamed_video_entries exists for precisely that —
+ # and reusing a stale parse under a new name would silently
+ # defeat it. ffprobe's own output has no such concern: the same
+ # bytes probe the same regardless of what the file is called.
+ cached_meta = await self._media_cache.get_video_meta(entry.id)
duration: float | None = None
- try:
- _codec, duration, _has_audio, width, height, _raw = await asyncio.wait_for(
- probe_video(str(file_path)), timeout=PROBE_TIMEOUT_SECS)
- fields["duration"] = int(duration) if duration else None
- fields["width"] = width
- fields["height"] = height
- except Exception as e:
- log.warning("Probe failed for %s: %s", file_path, e)
+ if cached_meta is not None:
+ fields["duration"] = cached_meta["duration"]
+ fields["width"] = cached_meta["width"]
+ fields["height"] = cached_meta["height"]
+ duration = cached_meta["duration"]
+ else:
+ try:
+ _codec, duration, _has_audio, width, height, _raw = await asyncio.wait_for(
+ probe_video(str(file_path)), timeout=PROBE_TIMEOUT_SECS)
+ fields["duration"] = int(duration) if duration else None
+ fields["width"] = width
+ fields["height"] = height
+ except Exception as e:
+ log.warning("Probe failed for %s: %s", file_path, e)
+ await self._media_cache.put_video_meta(
+ entry.id, fields.get("duration"), fields.get("width"), fields.get("height"))
ep = title_parse.parse_episode_filename(entry.name)
if ep.episode is not None:
@@ -153,14 +178,18 @@ class Enricher:
mv = title_parse.parse_movie_filename(entry.name)
fields["display_title"] = mv.display_title or mv.naive_title
- try:
- thumb = await _make_thumbnail(file_path, duration)
- except Exception as e:
- log.warning("Thumbnail generation failed for %s: %s", file_path, e)
- thumb = None
- if thumb:
- thumb_hash = blake3.blake3(thumb).hexdigest()
- await self._media_cache.put_thumb(thumb_hash, entry.id, thumb)
- fields["thumb_hash"] = thumb_hash
+ cached_thumb_hash = await self._media_cache.get_thumb_hash_by_file_id(entry.id)
+ if cached_thumb_hash:
+ fields["thumb_hash"] = cached_thumb_hash
+ else:
+ try:
+ thumb = await _make_thumbnail(file_path, duration)
+ except Exception as e:
+ log.warning("Thumbnail generation failed for %s: %s", file_path, e)
+ thumb = None
+ if thumb:
+ thumb_hash = blake3.blake3(thumb).hexdigest()
+ await self._media_cache.put_thumb(thumb_hash, entry.id, thumb)
+ fields["thumb_hash"] = thumb_hash
await on_done(entry.id, fields)