"""Local cover art cache for the player UI. Caches extracted cover art as JPEG files in a local directory so the player can display them without re-extracting from audio. """ from __future__ import annotations import hashlib import logging import os from xdg_base import xdg_cache_path logger = logging.getLogger(__name__) class CoverCache: """Manages a local cache of cover art images for the player UI. The cache stores JPEG thumbnails keyed by (artist, album) hash. Covers are stored in $XDG_CACHE_HOME/neo-pod-desktop/covers/ (default ~/.cache/neo-pod-desktop/covers/). """ def __init__(self, cache_dir: str | None = None): self.cache_dir = cache_dir or xdg_cache_path("covers") os.makedirs(self.cache_dir, exist_ok=True) def _key(self, artist: str, album: str) -> str: raw = f"{artist.lower()}|{album.lower()}" return hashlib.md5(raw.encode()).hexdigest() def get(self, artist: str, album: str) -> str | None: """Return path to cached cover, or None.""" path = os.path.join(self.cache_dir, f"{self._key(artist, album)}.jpg") if os.path.exists(path): return path return None def put(self, artist: str, album: str, art_bytes: bytes) -> str | None: """Cache cover art bytes and return the file path.""" from PIL import Image from io import BytesIO key = self._key(artist, album) path = os.path.join(self.cache_dir, f"{key}.jpg") try: img = Image.open(BytesIO(art_bytes)) img = img.convert("RGB") img.thumbnail((300, 300), Image.Resampling.LANCZOS) img.save(path, "JPEG", quality=85) return path except Exception as exc: logger.warning("Failed to cache cover art: %s", exc) return None def clear(self) -> None: """Remove all cached covers.""" for fname in os.listdir(self.cache_dir): if fname.endswith(".jpg"): try: os.remove(os.path.join(self.cache_dir, fname)) except OSError: pass cover_cache = CoverCache()