aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/media_cache.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/media_cache.py')
-rw-r--r--packages/meshbay-node/src/meshbay_node/media_cache.py77
1 files changed, 68 insertions, 9 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/media_cache.py b/packages/meshbay-node/src/meshbay_node/media_cache.py
index 6daae3f..47032b8 100644
--- a/packages/meshbay-node/src/meshbay_node/media_cache.py
+++ b/packages/meshbay-node/src/meshbay_node/media_cache.py
@@ -1,11 +1,17 @@
"""
-MeshBay Node — TMDB metadata and thumbnail cache for the Videos group app.
+MeshBay Node — TMDB/MusicBrainz metadata and thumbnail cache, shared by the
+Videos and Music group apps.
Node-wide (not per-group, `data_dir/media_cache.db`), same rationale as
-`tmdb_enabled`/`tmdb_api_token` living in `group_settings` under the
-`group_id=""` sentinel (docs/mediacenter.md §5.5): TMDB is one operator's
-budget and credential, and a thumbnail is the same bytes regardless of which
-group happens to share the file.
+`tmdb_enabled`/`tmdb_api_token` (and `musicbrainz_enabled`/
+`musicbrainz_contact`, docs/musicbay.md §6) living in `group_settings` under
+the `group_id=""` sentinel (docs/mediacenter.md §5.5): the credential/budget
+is one operator's, and a thumbnail or cover image is the same bytes
+regardless of which group happens to share the file. The `file_mbid`/
+`mbid_meta` tables below are the Music app's equivalent of `file_tmdb`/
+`tmdb_meta`, sharing the same `thumbs` table for cover art (a MusicBrainz
+release's cover is cached under a synthetic `musicbrainz:{mbid}` file_id,
+the same trick `_fetch_and_cache_poster` uses for a TMDB poster_path).
Disposable and rebuildable, like the rest of the file index (§1, §2) — never
a second identity for a file. Every row here is keyed off a value the node
@@ -48,15 +54,28 @@ CREATE TABLE IF NOT EXISTS season_meta (
fetched_at REAL NOT NULL,
PRIMARY KEY (tmdb_id, season)
);
+CREATE TABLE IF NOT EXISTS file_mbid (
+ file_id TEXT PRIMARY KEY,
+ mbid TEXT NOT NULL
+);
+CREATE TABLE IF NOT EXISTS mbid_meta (
+ mbid TEXT PRIMARY KEY,
+ json TEXT NOT NULL,
+ fetched_at REAL NOT NULL
+);
"""
# TMDB overviews/ratings do drift; a file's own resolved tmdb_id does not
# need re-checking on this schedule, only the metadata blob (§5.4, V3).
TMDB_META_TTL_SECS = 30 * 86400
+# Same default as TMDB (docs/musicbay.md §6) — MusicBrainz release data is
+# not expected to drift faster; revisit if that proves wrong in practice.
+MUSICBRAINZ_META_TTL_SECS = 30 * 86400
+
class MediaCache:
- """Async SQLite cache for TMDB lookups and generated thumbnails."""
+ """Async SQLite cache for TMDB/MusicBrainz lookups and generated thumbnails/cover art."""
def __init__(self, db_path: Path):
self._db_path = db_path
@@ -144,6 +163,44 @@ class MediaCache:
)
await self._db.commit()
+ # ── file -> musicbrainz release id mapping (Music app) ───────────────────
+
+ async def get_file_mbid(self, file_id: str) -> str | None:
+ async with self._db.execute(
+ "SELECT mbid FROM file_mbid WHERE file_id = ?", (file_id,),
+ ) as cur:
+ row = await cur.fetchone()
+ return row[0] if row else None
+
+ async def set_file_mbid(self, file_id: str, mbid: str) -> None:
+ await self._db.execute(
+ "INSERT OR REPLACE INTO file_mbid (file_id, mbid) VALUES (?, ?)",
+ (file_id, mbid),
+ )
+ await self._db.commit()
+
+ # ── musicbrainz release id -> metadata json (Music app) ───────────────────
+
+ async def get_mbid_meta(self, mbid: str) -> dict | None:
+ """Returns None on a miss or on an entry older than MUSICBRAINZ_META_TTL_SECS."""
+ async with self._db.execute(
+ "SELECT json, fetched_at FROM mbid_meta WHERE mbid = ?", (mbid,),
+ ) as cur:
+ row = await cur.fetchone()
+ if not row:
+ return None
+ raw_json, fetched_at = row
+ if time.time() - fetched_at > MUSICBRAINZ_META_TTL_SECS:
+ return None
+ return json.loads(raw_json)
+
+ async def set_mbid_meta(self, mbid: str, meta: dict) -> None:
+ await self._db.execute(
+ "INSERT OR REPLACE INTO mbid_meta (mbid, json, fetched_at) VALUES (?, ?, ?)",
+ (mbid, json.dumps(meta), time.time()),
+ )
+ await self._db.commit()
+
# ── thumbnails ────────────────────────────────────────────────────────────
async def get_thumb(self, thumb_hash: str) -> bytes | None:
@@ -179,10 +236,12 @@ class MediaCache:
async def prune_file(self, file_id: str) -> None:
"""
Called when a file leaves the index (deletion, unshared root). Removes
- its thumbnail and its file->tmdb mapping. `tmdb_meta` rows are left
- alone — they're keyed by tmdb_id, not file_id, and other files (other
- episodes of the same show) may still reference the same entry.
+ its thumbnail and its file->tmdb/file->mbid mappings. `tmdb_meta`/
+ `mbid_meta` rows are left alone — they're keyed by tmdb_id/mbid, not
+ file_id, and other files (other episodes of the same show, other
+ tracks of the same release) may still reference the same entry.
"""
await self._db.execute("DELETE FROM thumbs WHERE file_id = ?", (file_id,))
await self._db.execute("DELETE FROM file_tmdb WHERE file_id = ?", (file_id,))
+ await self._db.execute("DELETE FROM file_mbid WHERE file_id = ?", (file_id,))
await self._db.commit()