Files
SaraTune/Covers.py
T
2026-10-09 15:33:43 +02:00

59 lines
1.9 KiB
Python

"""Small versions of cover art. Embedded covers can be several megabytes,
and everything shown goes through Flet's connection, so the album grid and
play bar get thumbnails, cached in ~/.cache/SaraSuite/SaraTune/Covers/."""
import hashlib
import io
from pathlib import Path
from typing import Optional
from PIL import Image, UnidentifiedImageError
from Core import CacheDir
from Metadata import ReadCover
CoversDir = CacheDir / "Covers"
_NoCover = b"" # remembered in the memory cache so a coverless album isn't read again
def Thumbnail(Data: bytes, Size: int) -> Optional[bytes]:
try:
with Image.open(io.BytesIO(Data)) as Picture:
Picture = Picture.convert("RGB")
Picture.thumbnail((Size, Size), Image.LANCZOS)
Output = io.BytesIO()
Picture.save(Output, "JPEG", quality=88)
return Output.getvalue()
except (UnidentifiedImageError, OSError, ValueError, Image.DecompressionBombError):
return None
_Memory: dict[tuple[str, int], bytes] = {}
def CoverFor(FilePath: str, Size: int) -> Optional[bytes]:
"""FilePath's cover at most Size pixels wide. Blocking (run it in a
thread). Cached on disk by path, size and the file's mtime."""
Key = (FilePath, Size)
if Key in _Memory:
return _Memory[Key] or None
try:
Stamp = Path(FilePath).stat().st_mtime_ns
except OSError:
return None
Name = hashlib.sha1(f"{FilePath}|{Size}|{Stamp}".encode("utf-8", "surrogateescape")).hexdigest()
CachePath = CoversDir / f"{Name}.jpg"
if CachePath.exists():
Data = CachePath.read_bytes()
else:
Original = ReadCover(FilePath)
Data = Thumbnail(Original, Size) if Original else None
if Data:
try:
CoversDir.mkdir(parents=True, exist_ok=True)
CachePath.write_bytes(Data)
except OSError:
pass
_Memory[Key] = Data or _NoCover
return Data