from typing import Callable, Protocol from lyra_worker.types import Candidate, DownloadResult, MBTarget, Quality _YT_QUALITY = Quality(fmt="OPUS", lossless=False, bitrate_kbps=160) class YtClient(Protocol): def search_album(self, artist: str, album: str) -> list[dict]: """Return album matches: dicts with keys source_ref, title, artist, track_count.""" ... def download(self, source_ref: str, dest: str, on_progress: Callable[[float], None]) -> dict: """Download into dest; return {track_count, path}. Raise on failure.""" ... class YouTubeAdapter: name = "youtube" tier = 2 def __init__(self, client: YtClient): self._client = client def health(self) -> bool: return True 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="youtube", source_ref=r["source_ref"], matched_artist=r.get("artist", ""), matched_album=r.get("title", ""), quality=_YT_QUALITY, track_count=r.get("track_count", 1) or 1, 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: # network/extraction failures -> fall-through in the pipeline 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), )