from typing import Callable, Protocol from lyra_worker.types import Candidate, DownloadResult, MBTarget, Quality class SoulseekClient(Protocol): def is_configured(self) -> bool: ... def search_album(self, artist: str, album: str) -> list[dict]: """Album matches: dicts with source_ref, title, artist, track_count, format, bitrate.""" ... def download(self, source_ref: str, dest: str, on_progress: Callable[[float], None]) -> dict: """Download into dest; return {track_count, path}. Raise on failure.""" ... def _quality(fmt: str, bitrate: int | None) -> Quality: if fmt.upper() == "FLAC": return Quality(fmt="FLAC", lossless=True, bit_depth=16, sample_rate=44100) return Quality(fmt="MP3", lossless=False, bitrate_kbps=bitrate or 320) class SoulseekAdapter: name = "soulseek" tier = 1 def __init__(self, client: SoulseekClient): self._client = client def health(self) -> bool: return self._client.is_configured() def search(self, target: MBTarget) -> list[Candidate]: candidates: list[Candidate] = [] for r in self._client.search_album(target.artist, target.album): candidates.append( Candidate( source="soulseek", source_ref=r["source_ref"], matched_artist=r.get("artist", ""), matched_album=r.get("title", ""), quality=_quality(r.get("format", "MP3"), r.get("bitrate")), track_count=r.get("track_count", 0) or 0, source_tier=self.tier, ) ) return candidates def download( self, candidate: Candidate, dest: str, on_progress: Callable[[float], None] ) -> DownloadResult: try: info = self._client.download(candidate.source_ref, dest, on_progress) except Exception as e: return DownloadResult(ok=False, error=str(e)) return DownloadResult( ok=True, path=info.get("path", dest), track_count=info.get("track_count", candidate.track_count), )