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>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user