Files
Jonathan 4907d0d5a4 feat: library scan — probe seam + scan_library (have + monitored + followed)
Adds the AudioProbe seam (probe.py, _probe.py MutagenProbe with a lazy
mutagen import, FakeAudioProbe) and scan_library, which walks /music,
resolves each album against MusicBrainz (falling back to embedded tags
when the folder name doesn't resolve), and idempotently records matched
albums as have (LibraryItem), monitored (MonitoredRelease, fulfilled),
and followed (WatchedArtist, autoMonitorFuture=false).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 17:00:33 +02:00

33 lines
1.1 KiB
Python

import os
from lyra_worker.probe import ProbeResult
from lyra_worker.quality import quality_class
from lyra_worker.types import Quality
_LOSSLESS_EXT = {".flac", ".wav", ".alac", ".aiff", ".aif"}
class MutagenProbe:
"""Real AudioProbe via mutagen (imported lazily; not unit-tested offline)."""
def probe(self, path: str) -> ProbeResult:
import mutagen
ext = os.path.splitext(path)[1].lower()
audio = mutagen.File(path, easy=True)
artist = album = ""
bit_depth = sample_rate = None
if audio is not None:
artist = (audio.get("artist") or [""])[0]
album = (audio.get("album") or [""])[0]
info = getattr(audio, "info", None)
bit_depth = getattr(info, "bits_per_sample", None)
sample_rate = getattr(info, "sample_rate", None)
q = quality_class(Quality(
fmt=ext.lstrip(".").upper(),
lossless=ext in _LOSSLESS_EXT,
bit_depth=bit_depth,
sample_rate=sample_rate,
))
return ProbeResult(artist=artist, album=album, fmt=ext.lstrip(".").upper(), quality_class=q)