From: Jc2k Date: Wed, 19 Jun 2024 22:49:32 +0000 (+0100) Subject: Jellyfin: Stricter typing (#1390) X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=3cc1ac7a3c4fa272f3d57086c1765b9beb4b3e23;p=music-assistant-server.git Jellyfin: Stricter typing (#1390) --- diff --git a/music_assistant/server/providers/jellyfin/__init__.py b/music_assistant/server/providers/jellyfin/__init__.py index 7f682a49..b0320e98 100644 --- a/music_assistant/server/providers/jellyfin/__init__.py +++ b/music_assistant/server/providers/jellyfin/__init__.py @@ -9,9 +9,13 @@ import uuid from asyncio import TaskGroup from collections.abc import AsyncGenerator +from aiojellyfin import Album as JellyAlbum +from aiojellyfin import Artist as JellyArtist from aiojellyfin import MediaItem as JellyMediaItem from aiojellyfin import MediaLibrary as JellyMediaLibrary +from aiojellyfin import Playlist as JellyPlaylist from aiojellyfin import SessionConfiguration, authenticate_by_name +from aiojellyfin import Track as JellyTrack from music_assistant.common.models.config_entries import ( ConfigEntry, @@ -68,17 +72,12 @@ from .const import ( ITEM_KEY_MUSICBRAINZ_TRACK, ITEM_KEY_NAME, ITEM_KEY_OVERVIEW, - ITEM_KEY_PARENT_ID, ITEM_KEY_PARENT_INDEX_NUM, ITEM_KEY_PRODUCTION_YEAR, ITEM_KEY_PROVIDER_IDS, ITEM_KEY_RUNTIME_TICKS, ITEM_KEY_SORT_NAME, ITEM_KEY_USER_DATA, - ITEM_TYPE_ALBUM, - ITEM_TYPE_ARTIST, - ITEM_TYPE_AUDIO, - ITEM_TYPE_MUSICARTISTS, MAX_IMAGE_WIDTH, SUPPORTED_CONTAINER_FORMATS, TRACK_FIELDS, @@ -204,9 +203,8 @@ class JellyfinProvider(MusicProvider): ) async def _search_track(self, search_query: str, limit: int) -> list[Track]: - resultset = await self._client.search_media_items( - term=search_query, - media=ITEM_TYPE_AUDIO, + resultset = await self._client.tracks( + search_term=search_query, limit=limit, enable_user_data=True, fields=TRACK_FIELDS, @@ -222,9 +220,8 @@ class JellyfinProvider(MusicProvider): albumname = searchterms[1] else: albumname = search_query - resultset = await self._client.search_media_items( - term=albumname, - media=ITEM_TYPE_ALBUM, + resultset = await self._client.albums( + search_term=albumname, limit=limit, enable_user_data=True, fields=ALBUM_FIELDS, @@ -235,9 +232,8 @@ class JellyfinProvider(MusicProvider): return albums async def _search_artist(self, search_query: str, limit: int) -> list[Artist]: - resultset = await self._client.search_media_items( - term=search_query, - media=ITEM_TYPE_ARTIST, + resultset = await self._client.artists( + search_term=search_query, limit=limit, enable_user_data=True, fields=ARTIST_FIELDS, @@ -248,17 +244,17 @@ class JellyfinProvider(MusicProvider): return artists async def _search_playlist(self, search_query: str, limit: int) -> list[Playlist]: - resultset = await self._client.search_media_items( - term=search_query, - media="Playlist", + resultset = await self._client.playlists( + search_term=search_query, limit=limit, + enable_user_data=True, ) playlists = [] for item in resultset["Items"]: playlists.append(self._parse_playlist(item)) return playlists - def _parse_album(self, jellyfin_album: JellyMediaItem) -> Album: + def _parse_album(self, jellyfin_album: JellyAlbum) -> Album: """Parse a Jellyfin Album response to an Album model object.""" album_id = jellyfin_album[ITEM_KEY_ID] album = Album( @@ -326,7 +322,7 @@ class JellyfinProvider(MusicProvider): album.favorite = user_data.get(USER_DATA_KEY_IS_FAVORITE, False) return album - def _parse_artist(self, jellyfin_artist: JellyMediaItem) -> Artist: + def _parse_artist(self, jellyfin_artist: JellyArtist) -> Artist: """Parse a Jellyfin Artist response to Artist model object.""" artist_id = jellyfin_artist[ITEM_KEY_ID] artist = Artist( @@ -369,7 +365,7 @@ class JellyfinProvider(MusicProvider): artist.favorite = user_data.get(USER_DATA_KEY_IS_FAVORITE, False) return artist - def _parse_track(self, jellyfin_track: JellyMediaItem) -> Track: + def _parse_track(self, jellyfin_track: JellyTrack) -> Track: """Parse a Jellyfin Track response to a Track model object.""" available = False content = None @@ -452,7 +448,7 @@ class JellyfinProvider(MusicProvider): track.favorite = user_data.get(USER_DATA_KEY_IS_FAVORITE, False) return track - def _parse_playlist(self, jellyfin_playlist: JellyMediaItem) -> Playlist: + def _parse_playlist(self, jellyfin_playlist: JellyPlaylist) -> Playlist: """Parse a Jellyfin Playlist response to a Playlist object.""" playlistid = jellyfin_playlist[ITEM_KEY_ID] playlist = Playlist( @@ -619,8 +615,8 @@ class JellyfinProvider(MusicProvider): """Retrieve all library playlists from the provider.""" playlist_libraries = await self._get_playlists() for playlist_library in playlist_libraries: - playlists_obj = await self._get_children(playlist_library[ITEM_KEY_ID], "Playlist") - for playlist in playlists_obj: + playlists_obj = await self._client.playlists(playlist_library[ITEM_KEY_ID]) + for playlist in playlists_obj["Items"]: if "MediaType" in playlist: # Only jellyfin has this property if playlist["MediaType"] == "Audio": yield self._parse_playlist(playlist) @@ -629,17 +625,19 @@ class JellyfinProvider(MusicProvider): async def get_album(self, prov_album_id: str) -> Album: """Get full album details by id.""" - if jellyfin_album := await self._client.get_item(prov_album_id): + if jellyfin_album := await self._client.get_album(prov_album_id): return self._parse_album(jellyfin_album) msg = f"Item {prov_album_id} not found" raise MediaNotFoundError(msg) async def get_album_tracks(self, prov_album_id: str) -> list[Track]: """Get album tracks for given album id.""" - jellyfin_album_tracks = await self._get_children(prov_album_id, ITEM_TYPE_AUDIO) + jellyfin_album_tracks = await self._client.tracks( + prov_album_id, enable_user_data=True, fields=TRACK_FIELDS + ) return [ self._parse_track(jellyfin_album_track) - for jellyfin_album_track in jellyfin_album_tracks + for jellyfin_album_track in jellyfin_album_tracks["Items"] ] async def get_artist(self, prov_artist_id: str) -> Artist: @@ -660,21 +658,21 @@ class JellyfinProvider(MusicProvider): artist.mbid = UNKNOWN_ARTIST_ID_MBID return artist - if jellyfin_artist := await self._client.get_item(prov_artist_id): + if jellyfin_artist := await self._client.get_artist(prov_artist_id): return self._parse_artist(jellyfin_artist) msg = f"Item {prov_artist_id} not found" raise MediaNotFoundError(msg) async def get_track(self, prov_track_id: str) -> Track: """Get full track details by id.""" - if jellyfin_track := await self._client.get_item(prov_track_id): + if jellyfin_track := await self._client.get_track(prov_track_id): return self._parse_track(jellyfin_track) msg = f"Item {prov_track_id} not found" raise MediaNotFoundError(msg) async def get_playlist(self, prov_playlist_id: str) -> Playlist: """Get full playlist details by id.""" - if jellyfin_playlist := await self._client.get_item(prov_playlist_id): + if jellyfin_playlist := await self._client.get_playlist(prov_playlist_id): return self._parse_playlist(jellyfin_playlist) msg = f"Item {prov_playlist_id} not found" raise MediaNotFoundError(msg) @@ -688,11 +686,13 @@ class JellyfinProvider(MusicProvider): # paging not supported, we always return the whole list at once return [] # TODO: Does Jellyfin support paging here? - jellyfin_playlist = await self._client.get_item(prov_playlist_id) - playlist_items = await self._get_children(jellyfin_playlist[ITEM_KEY_ID], ITEM_TYPE_AUDIO) + jellyfin_playlist = await self._client.get_playlist(prov_playlist_id) + playlist_items = await self._client.tracks( + jellyfin_playlist[ITEM_KEY_ID], enable_user_data=True, fields=TRACK_FIELDS + ) if not playlist_items: return result - for index, jellyfin_track in enumerate(playlist_items, 1): + for index, jellyfin_track in enumerate(playlist_items["Items"], 1): try: if track := self._parse_track(jellyfin_track): if not track.position: @@ -715,7 +715,7 @@ class JellyfinProvider(MusicProvider): async def get_stream_details(self, item_id: str) -> StreamDetails: """Return the content details for the given track when it will be streamed.""" - jellyfin_track = await self._client.get_item(item_id) + jellyfin_track = await self._client.get_track(item_id) mimetype = self._media_mime_type(jellyfin_track) media_stream = jellyfin_track[ITEM_KEY_MEDIA_STREAMS][0] url = self._client.audio_url(jellyfin_track[ITEM_KEY_ID], SUPPORTED_CONTAINER_FORMATS) @@ -751,22 +751,6 @@ class JellyfinProvider(MusicProvider): """Return the stream URL for a media item.""" return self._client.audio_url(media_item) - async def _get_children(self, parent_id: str, item_type: str) -> list[JellyMediaItem]: - """Return all children for the parent_id whose item type is item_type.""" - params: dict[str, str | int] = { - "Recursive": "true", - ITEM_KEY_PARENT_ID: parent_id, - } - if item_type in ITEM_TYPE_ARTIST: - params["IncludeItemTypes"] = f"{ITEM_TYPE_MUSICARTISTS},{ITEM_TYPE_ARTIST}" - else: - params["IncludeItemTypes"] = item_type - if item_type in ITEM_TYPE_AUDIO: - params["Fields"] = ",".join(TRACK_FIELDS) - - result = await self._client.user_items("", params) - return result["Items"] - async def _get_music_libraries(self) -> list[JellyMediaLibrary]: """Return all supported libraries a user has access to.""" response = await self._client.get_media_folders() @@ -790,7 +774,7 @@ class JellyfinProvider(MusicProvider): result.append(library) return result - def _media_mime_type(self, media_item: JellyMediaItem) -> str | None: + def _media_mime_type(self, media_item: JellyTrack) -> str | None: """Return the mime type of a media item.""" if not media_item.get(ITEM_KEY_MEDIA_SOURCES): return None diff --git a/music_assistant/server/providers/jellyfin/const.py b/music_assistant/server/providers/jellyfin/const.py index 486cbb3c..4220efd0 100644 --- a/music_assistant/server/providers/jellyfin/const.py +++ b/music_assistant/server/providers/jellyfin/const.py @@ -39,16 +39,12 @@ ITEM_KEY_ALBUM_ARTIST: Final = "AlbumArtist" ITEM_KEY_ALBUM_ARTISTS: Final = "AlbumArtists" ITEM_KEY_ALBUM: Final = "Album" ITEM_KEY_ALBUM_ID: Final = "AlbumId" -ITEM_KEY_PARENT_ID: Final = "ParentId" ITEM_KEY_ARTIST_ITEMS: Final = "ArtistItems" ITEM_KEY_CAN_DOWNLOAD: Final = "CanDownload" ITEM_KEY_PARENT_INDEX_NUM: Final = "ParentIndexNumber" ITEM_KEY_RUNTIME_TICKS: Final = "RunTimeTicks" ITEM_KEY_USER_DATA: Final = "UserData" -ITEM_TYPE_ALBUM: Final = "MusicAlbum" -ITEM_TYPE_ARTIST: Final = "MusicArtist" -ITEM_TYPE_MUSICARTISTS: Final = "MusicArtists" ITEM_TYPE_AUDIO: Final = "Audio" ITEM_TYPE_LIBRARY: Final = "CollectionFolder" diff --git a/music_assistant/server/providers/jellyfin/manifest.json b/music_assistant/server/providers/jellyfin/manifest.json index fb865066..3647649a 100644 --- a/music_assistant/server/providers/jellyfin/manifest.json +++ b/music_assistant/server/providers/jellyfin/manifest.json @@ -4,7 +4,7 @@ "name": "Jellyfin Media Server Library", "description": "Support for the Jellyfin streaming provider in Music Assistant.", "codeowners": ["@lokiberra", "@Jc2k"], - "requirements": ["aiojellyfin==0.3.0"], + "requirements": ["aiojellyfin==0.5.0"], "documentation": "https://music-assistant.io/music-providers/jellyfin/", "multi_instance": true } diff --git a/requirements_all.txt b/requirements_all.txt index 781454ef..6dda36e8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -4,7 +4,7 @@ Brotli>=1.0.9 aiodns>=3.0.0 aiofiles==23.2.1 aiohttp==3.9.5 -aiojellyfin==0.3.0 +aiojellyfin==0.5.0 aiorun==2024.5.1 aioslimproto==3.0.1 aiosqlite==0.20.0