from typing import Any, Optional from app.chain import ChainBase from app.domain.context import MusicAlbumInfo, MusicArtistInfo, MusicInfo from app.domain.meta.metamusic import MetaMusic from app.schemas.types import MediaSource, MediaType class _MusicMetadataSourceChain(ChainBase): """固定音乐元数据来源链的共用端口适配。""" source: MediaSource def search_music(self, meta: MetaMusic, limit: int = 20) -> list[MusicInfo]: """按音乐元数据搜索当前来源候选。""" result = self.run_module( "search_music", meta=meta, limit=limit, media_source=self.source, ) return self._music_infos(result, limit=limit) async def async_search_music(self, meta: MetaMusic, limit: int = 20) -> list[MusicInfo]: """异步按音乐元数据搜索当前来源候选。""" result = await self.async_run_module( "search_music", meta=meta, limit=limit, media_source=self.source, ) return self._music_infos(result, limit=limit) def recognize_music( self, meta: Optional[MetaMusic] = None, media_id: Optional[str] = None, cache: bool = True, music_type: Optional[str] = None, ) -> Optional[MusicInfo]: """按当前来源身份或音乐元数据识别标准音乐信息。""" normalized_id = self._normalize_media_id(media_id) result = self.run_module( "recognize_media", meta=meta, mtype=MediaType.MUSIC, media_source=self.source, media_id=normalized_id, cache=cache, music_type=music_type, ) return self._music_info(result, media_id=normalized_id) async def async_recognize_music( self, meta: Optional[MetaMusic] = None, media_id: Optional[str] = None, cache: bool = True, music_type: Optional[str] = None, ) -> Optional[MusicInfo]: """异步按当前来源身份或音乐元数据识别标准音乐信息。""" normalized_id = self._normalize_media_id(media_id) result = await self.async_run_module( "async_recognize_media", meta=meta, mtype=MediaType.MUSIC, media_source=self.source, media_id=normalized_id, cache=cache, music_type=music_type, ) return self._music_info(result, media_id=normalized_id) def get_music_album(self, media_id: str) -> Optional[MusicAlbumInfo]: """按当前来源原生 ID 获取专辑详情。""" normalized_id = self._normalize_media_id(media_id) if not normalized_id: return None result = self.run_module( "music_album", media_source=self.source, media_id=normalized_id, ) return self._music_album(result, media_id=normalized_id) async def async_get_music_album(self, media_id: str) -> Optional[MusicAlbumInfo]: """异步按当前来源原生 ID 获取专辑详情。""" normalized_id = self._normalize_media_id(media_id) if not normalized_id: return None result = await self.async_run_module( "music_album", media_source=self.source, media_id=normalized_id, ) return self._music_album(result, media_id=normalized_id) async def async_get_music_album_related( self, media_id: str, count: int = 24, ) -> list[MusicInfo]: """异步获取当前来源的专辑关联条目。""" normalized_id = self._normalize_media_id(media_id) if not normalized_id: return [] result = await self.async_run_module( "music_album_related", media_source=self.source, media_id=normalized_id, count=count, ) return self._music_infos(result, limit=count) async def async_get_music_artist(self, media_id: str) -> Optional[MusicArtistInfo]: """异步按当前来源原生 ID 获取艺术家详情。""" normalized_id = self._normalize_media_id(media_id) if not normalized_id: return None result = await self.async_run_module( "music_artist", media_source=self.source, media_id=normalized_id, ) return self._music_artist(result, media_id=normalized_id) async def async_get_music_artist_albums( self, media_id: str, page: int = 1, count: int = 30, album_type: Optional[str] = None, ) -> list[MusicInfo]: """异步分页获取当前来源艺术家的专辑目录。""" normalized_id = self._normalize_media_id(media_id) if not normalized_id: return [] result = await self.async_run_module( "music_artist_albums", media_source=self.source, media_id=normalized_id, page=page, count=count, album_type=album_type, ) return self._music_infos(result, limit=count) async def async_get_music_artist_related( self, media_id: str, count: int = 24, ) -> list[MusicArtistInfo]: """异步获取当前来源艺术家的关联艺术家。""" normalized_id = self._normalize_media_id(media_id) if not normalized_id: return [] result = await self.async_run_module( "music_artist_related", media_source=self.source, media_id=normalized_id, count=count, ) return self._music_artists(result, limit=count) @staticmethod def _normalize_media_id(media_id: Optional[str]) -> Optional[str]: """清理来源原生 ID,空值和历史零哨兵按无身份处理。""" normalized = str(media_id).strip() if media_id is not None else "" return normalized if normalized and normalized != "0" else None def _music_infos( self, result: Any, limit: Optional[int] = None, ) -> list[MusicInfo]: """将模块或插件结果统一转换为当前来源的音乐候选列表。""" candidates = result if isinstance(result, list) else [] infos = [ item if isinstance(item, MusicInfo) else MusicInfo.from_dict(item) for item in candidates if isinstance(item, (MusicInfo, dict)) ] infos = [info for info in infos if info.media_source == self.source] return infos[:limit] if limit else infos def _music_info( self, result: Any, media_id: Optional[str] = None, ) -> Optional[MusicInfo]: """校验单条识别结果的来源与显式身份。""" if isinstance(result, MusicInfo): info = result elif isinstance(result, dict): info = MusicInfo.from_dict(result) else: return None if info.media_source and info.media_source != self.source: return None if media_id and (info.media_source != self.source or info.media_id != media_id): return None return info def _music_album( self, result: Any, media_id: Optional[str] = None, ) -> Optional[MusicAlbumInfo]: """将模块或插件结果统一转换为专辑详情。""" if isinstance(result, MusicAlbumInfo): album = result elif isinstance(result, dict): album = MusicAlbumInfo.from_dict(result) else: return None if album.media_source != self.source: return None if media_id and album.media_id != media_id: return None return album def _music_artist( self, result: Any, media_id: Optional[str] = None, ) -> Optional[MusicArtistInfo]: """将模块或插件结果统一转换为艺术家详情。""" if isinstance(result, MusicArtistInfo): artist = result elif isinstance(result, dict): artist = MusicArtistInfo.from_dict(result) else: return None if artist.media_source != self.source: return None if media_id and artist.media_id != media_id: return None return artist def _music_artists( self, result: Any, limit: Optional[int] = None, ) -> list[MusicArtistInfo]: """将模块或插件结果统一转换为艺术家列表。""" candidates = result if isinstance(result, list) else [] artists = [ item if isinstance(item, MusicArtistInfo) else MusicArtistInfo.from_dict(item) for item in candidates if isinstance(item, (MusicArtistInfo, dict)) ] artists = [artist for artist in artists if artist.media_source == self.source] return artists[:limit] if limit else artists class MusicBrainzChain(_MusicMetadataSourceChain): """MusicBrainz 音乐搜索、识别与详情来源链。""" source = MediaSource.MusicBrainz def match_music_album( self, meta: MetaMusic, tracks: list[MetaMusic], limit: int = 5, ) -> Optional[MusicAlbumInfo]: """按目录元数据与曲目证据匹配 MusicBrainz 发行版本。""" result = self.run_module( "match_music_album", meta=meta, tracks=tracks, limit=limit, ) return self._music_album(result) async def async_match_music_album( self, meta: MetaMusic, tracks: list[MetaMusic], limit: int = 5, ) -> Optional[MusicAlbumInfo]: """异步按目录元数据与曲目证据匹配 MusicBrainz 发行版本。""" result = await self.async_run_module( "async_match_music_album", meta=meta, tracks=tracks, limit=limit, ) return self._music_album(result) def cache_items(self) -> list[dict]: """查询音乐识别缓存条目列表。""" result = self.run_module("music_cache_items") return result or [] def delete_cache(self, cache_key: str) -> dict: """按缓存键删除单条音乐识别缓存。""" result = self.run_module("music_cache_delete", cache_key=cache_key) return result or {} def clear_cache(self) -> None: """清空全部音乐识别缓存。""" self.run_module("music_cache_clear")