From: Jc2k Date: Mon, 29 Jul 2024 10:00:23 +0000 (+0100) Subject: Jellyfin: Use aiojellyfin 0.10.0 for stricter typing and more speed (#1521) X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=466cae8027ac01ff44a4b6c14ddcb549e0dd8195;p=music-assistant-server.git Jellyfin: Use aiojellyfin 0.10.0 for stricter typing and more speed (#1521) --- diff --git a/music_assistant/server/providers/jellyfin/__init__.py b/music_assistant/server/providers/jellyfin/__init__.py index b1c32b0f..f2699e32 100644 --- a/music_assistant/server/providers/jellyfin/__init__.py +++ b/music_assistant/server/providers/jellyfin/__init__.py @@ -175,11 +175,12 @@ class JellyfinProvider(MusicProvider): return False async def _search_track(self, search_query: str, limit: int) -> list[Track]: - resultset = await self._client.tracks( - search_term=search_query, - limit=limit, - enable_user_data=True, - fields=TRACK_FIELDS, + resultset = ( + await self._client.tracks.search_term(search_query) + .limit(limit) + .enable_userdata() + .fields(*TRACK_FIELDS) + .request() ) tracks = [] for item in resultset["Items"]: @@ -192,11 +193,12 @@ class JellyfinProvider(MusicProvider): albumname = searchterms[1] else: albumname = search_query - resultset = await self._client.albums( - search_term=albumname, - limit=limit, - enable_user_data=True, - fields=ALBUM_FIELDS, + resultset = ( + await self._client.albums.search_term(albumname) + .limit(limit) + .enable_userdata() + .fields(*ALBUM_FIELDS) + .request() ) albums = [] for item in resultset["Items"]: @@ -204,11 +206,12 @@ class JellyfinProvider(MusicProvider): return albums async def _search_artist(self, search_query: str, limit: int) -> list[Artist]: - resultset = await self._client.artists( - search_term=search_query, - limit=limit, - enable_user_data=True, - fields=ARTIST_FIELDS, + resultset = ( + await self._client.artists.search_term(search_query) + .limit(limit) + .enable_userdata() + .fields(*ARTIST_FIELDS) + .request() ) artists = [] for item in resultset["Items"]: @@ -216,10 +219,11 @@ class JellyfinProvider(MusicProvider): return artists async def _search_playlist(self, search_query: str, limit: int) -> list[Playlist]: - resultset = await self._client.playlists( - search_term=search_query, - limit=limit, - enable_user_data=True, + resultset = ( + await self._client.playlists.search_term(search_query) + .limit(limit) + .enable_userdata() + .request() ) playlists = [] for item in resultset["Items"]: @@ -270,77 +274,39 @@ class JellyfinProvider(MusicProvider): """Retrieve all library artists from Jellyfin Music.""" jellyfin_libraries = await self._get_music_libraries() for jellyfin_library in jellyfin_libraries: - offset = 0 - limit = 100 - - response = await self._client.artists( - jellyfin_library[ITEM_KEY_ID], - start_index=offset, - limit=limit, - enable_user_data=True, - fields=ARTIST_FIELDS, + stream = ( + self._client.artists.parent(jellyfin_library[ITEM_KEY_ID]) + .enable_userdata() + .fields(*ARTIST_FIELDS) + .stream(100) ) - for artist in response["Items"]: + async for artist in stream: yield parse_artist(self.logger, self.instance_id, self._client, artist) - while offset < response["TotalRecordCount"]: - response = await self._client.artists( - jellyfin_library[ITEM_KEY_ID], - start_index=offset, - limit=limit, - enable_user_data=True, - fields=ARTIST_FIELDS, - ) - for artist in response["Items"]: - yield parse_artist(self.logger, self.instance_id, self._client, artist) - - offset += limit - async def get_library_albums(self) -> AsyncGenerator[Album, None]: """Retrieve all library albums from Jellyfin Music.""" jellyfin_libraries = await self._get_music_libraries() for jellyfin_library in jellyfin_libraries: - offset = 0 - limit = 100 - - response = await self._client.albums( - jellyfin_library[ITEM_KEY_ID], - start_index=offset, - limit=limit, - enable_user_data=True, - fields=ALBUM_FIELDS, + stream = ( + self._client.albums.parent(jellyfin_library[ITEM_KEY_ID]) + .enable_userdata() + .fields(*ALBUM_FIELDS) + .stream(100) ) - for artist in response["Items"]: - yield parse_album(self.logger, self.instance_id, self._client, artist) - - while offset < response["TotalRecordCount"]: - response = await self._client.albums( - jellyfin_library[ITEM_KEY_ID], - start_index=offset, - limit=limit, - enable_user_data=True, - fields=ALBUM_FIELDS, - ) - for artist in response["Items"]: - yield parse_album(self.logger, self.instance_id, self._client, artist) - - offset += limit + async for album in stream: + yield parse_album(self.logger, self.instance_id, self._client, album) async def get_library_tracks(self) -> AsyncGenerator[Track, None]: """Retrieve library tracks from Jellyfin Music.""" jellyfin_libraries = await self._get_music_libraries() for jellyfin_library in jellyfin_libraries: - offset = 0 - limit = 100 - - response = await self._client.tracks( - jellyfin_library[ITEM_KEY_ID], - start_index=offset, - limit=limit, - enable_user_data=True, - fields=TRACK_FIELDS, + stream = ( + self._client.tracks.parent(jellyfin_library[ITEM_KEY_ID]) + .enable_userdata() + .fields(*TRACK_FIELDS) + .stream(100) ) - for track in response["Items"]: + async for track in stream: if not len(track[ITEM_KEY_MEDIA_STREAMS]): self.logger.warning( "Invalid track %s: Does not have any media streams", track[ITEM_KEY_NAME] @@ -348,31 +314,16 @@ class JellyfinProvider(MusicProvider): continue yield parse_track(self.logger, self.instance_id, self._client, track) - while offset < response["TotalRecordCount"]: - response = await self._client.tracks( - jellyfin_library[ITEM_KEY_ID], - start_index=offset, - limit=limit, - enable_user_data=True, - fields=TRACK_FIELDS, - ) - for track in response["Items"]: - if not len(track[ITEM_KEY_MEDIA_STREAMS]): - self.logger.warning( - "Invalid track %s: Does not have any media streams", - track[ITEM_KEY_NAME], - ) - continue - yield parse_track(self.logger, self.instance_id, self._client, track) - - offset += limit - async def get_library_playlists(self) -> AsyncGenerator[Playlist, None]: """Retrieve all library playlists from the provider.""" playlist_libraries = await self._get_playlists() for playlist_library in playlist_libraries: - playlists_obj = await self._client.playlists(playlist_library[ITEM_KEY_ID]) - for playlist in playlists_obj["Items"]: + stream = ( + self._client.playlists.parent(playlist_library[ITEM_KEY_ID]) + .enable_userdata() + .stream(100) + ) + async for playlist in stream: if "MediaType" in playlist: # Only jellyfin has this property if playlist["MediaType"] == "Audio": yield parse_playlist(self.instance_id, self._client, playlist) @@ -389,8 +340,11 @@ class JellyfinProvider(MusicProvider): async def get_album_tracks(self, prov_album_id: str) -> list[Track]: """Get album tracks for given album id.""" - jellyfin_album_tracks = await self._client.tracks( - prov_album_id, enable_user_data=True, fields=TRACK_FIELDS + jellyfin_album_tracks = ( + await self._client.tracks.parent(prov_album_id) + .enable_userdata() + .fields(*TRACK_FIELDS) + .request() ) return [ parse_track(self.logger, self.instance_id, self._client, jellyfin_album_track) @@ -445,8 +399,11 @@ class JellyfinProvider(MusicProvider): return [] # TODO: Does Jellyfin support paging here? 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 + playlist_items = ( + await self._client.tracks.parent(jellyfin_playlist[ITEM_KEY_ID]) + .enable_userdata() + .fields(*TRACK_FIELDS) + .request() ) for index, jellyfin_track in enumerate(playlist_items["Items"], 1): try: @@ -466,8 +423,11 @@ class JellyfinProvider(MusicProvider): """Get a list of albums for the given artist.""" if not prov_artist_id.startswith(FAKE_ARTIST_PREFIX): return [] - albums = await self._client.albums( - prov_artist_id, fields=ALBUM_FIELDS, enable_user_data=True + albums = ( + await self._client.albums.parent(prov_artist_id) + .fields(*ALBUM_FIELDS) + .enable_userdata() + .request() ) return [ parse_album(self.logger, self.instance_id, self._client, album) diff --git a/music_assistant/server/providers/jellyfin/const.py b/music_assistant/server/providers/jellyfin/const.py index 7ea656f7..2bbfc9a9 100644 --- a/music_assistant/server/providers/jellyfin/const.py +++ b/music_assistant/server/providers/jellyfin/const.py @@ -2,7 +2,8 @@ from typing import Final -from aiojellyfin.const import ImageType as JellyImageType +from aiojellyfin import ImageType as JellyImageType +from aiojellyfin import ItemFields from music_assistant.common.models.enums import ImageType, MediaType from music_assistant.common.models.media_items import ItemMapping @@ -67,9 +68,23 @@ SUPPORTED_CONTAINER_FORMATS: Final = "ogg,flac,mp3,aac,mpeg,alac,wav,aiff,wma,m4 PLAYABLE_ITEM_TYPES: Final = [ITEM_TYPE_AUDIO] -ARTIST_FIELDS = ["Overview", "ProviderIds", "SortName"] -ALBUM_FIELDS = ["ProductionYear", "Overview", "ProviderIds", "SortName"] -TRACK_FIELDS = ["ProviderIds", "CanDownload", "SortName", "MediaSources", "MediaStreams"] +ARTIST_FIELDS: Final = [ + ItemFields.Overview, + ItemFields.ProviderIds, + ItemFields.SortName, +] +ALBUM_FIELDS: Final = [ + ItemFields.Overview, + ItemFields.ProviderIds, + ItemFields.SortName, +] +TRACK_FIELDS: Final = [ + ItemFields.ProviderIds, + ItemFields.CanDownload, + ItemFields.SortName, + ItemFields.MediaSources, + ItemFields.MediaStreams, +] USER_APP_NAME: Final = "Music Assistant" USER_AGENT: Final = "Music-Assistant-1.0" diff --git a/music_assistant/server/providers/jellyfin/manifest.json b/music_assistant/server/providers/jellyfin/manifest.json index be810421..e86e08a4 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.9.2"], + "requirements": ["aiojellyfin==0.10.0"], "documentation": "https://music-assistant.io/music-providers/jellyfin/", "multi_instance": true } diff --git a/music_assistant/server/providers/jellyfin/parsers.py b/music_assistant/server/providers/jellyfin/parsers.py index 84676e86..1d022ce6 100644 --- a/music_assistant/server/providers/jellyfin/parsers.py +++ b/music_assistant/server/providers/jellyfin/parsers.py @@ -6,7 +6,7 @@ import logging from logging import Logger from typing import TYPE_CHECKING -from aiojellyfin.const import ImageType as JellyImageType +from aiojellyfin import ImageType as JellyImageType from music_assistant.common.models.enums import ContentType, ExternalID, ImageType, MediaType from music_assistant.common.models.errors import InvalidDataError diff --git a/requirements_all.txt b/requirements_all.txt index 91f6a2f9..4facbcf3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -4,7 +4,7 @@ Brotli>=1.0.9 aiodns>=3.0.0 aiofiles==24.1.0 aiohttp==3.9.5 -aiojellyfin==0.9.2 +aiojellyfin==0.10.0 aiorun==2024.5.1 aioslimproto==3.0.1 aiosqlite==0.20.0 diff --git a/tests/server/providers/jellyfin/test_init.py b/tests/server/providers/jellyfin/test_init.py index 6d8418ea..1222ef58 100644 --- a/tests/server/providers/jellyfin/test_init.py +++ b/tests/server/providers/jellyfin/test_init.py @@ -16,13 +16,13 @@ async def jellyfin_provider(mass: MusicAssistant) -> AsyncGenerator[ProviderConf """Configure an aiojellyfin test fixture, and add a provider to mass that uses it.""" f = FixtureBuilder() async for _, artist in get_fixtures_dir("artists", "jellyfin"): - f.add_artist_bytes(artist) + f.add_json_bytes(artist) async for _, album in get_fixtures_dir("albums", "jellyfin"): - f.add_album_bytes(album) + f.add_json_bytes(album) async for _, track in get_fixtures_dir("tracks", "jellyfin"): - f.add_track_bytes(track) + f.add_json_bytes(track) authenticate_by_name = f.to_authenticate_by_name()