From 48d866e1d9797189752e90ace876bdff0f167a2e Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Sat, 25 Mar 2023 14:20:24 +0100 Subject: [PATCH] Fix metadata reading issues fileprovider (#572) * precache album tracks * fix overwiting over albums of track * fix reading albumtype tag --- .../server/controllers/media/albums.py | 25 +++--- .../server/controllers/media/tracks.py | 4 +- .../server/controllers/player_queues.py | 2 +- music_assistant/server/helpers/tags.py | 6 +- .../server/providers/filesystem_local/base.py | 76 +++++++++++-------- 5 files changed, 67 insertions(+), 46 deletions(-) diff --git a/music_assistant/server/controllers/media/albums.py b/music_assistant/server/controllers/media/albums.py index b957ca52..dd2cdc26 100644 --- a/music_assistant/server/controllers/media/albums.py +++ b/music_assistant/server/controllers/media/albums.py @@ -82,14 +82,21 @@ class AlbumsController(MediaControllerBase[Album]): provider_instance: str | None = None, ) -> list[Track]: """Return album tracks for the given provider album id.""" - if "database" not in (provider_domain, provider_instance): - # return provider album tracks - return await self._get_provider_album_tracks( - item_id, provider_domain or provider_instance - ) + if "database" in (provider_domain, provider_instance): + if db_result := await self._get_db_album_tracks(item_id): + return db_result + # no results in db (yet), grab provider details + if db_album := await self.get_db_item(item_id): + for prov_mapping in db_album.provider_mappings: + # returns the first provider that is available + if not prov_mapping.available: + continue + return await self._get_provider_album_tracks( + prov_mapping.item_id, provider_instance=prov_mapping.provider_instance + ) - # db_album requested: get results from first (non-file) provider - return await self._get_db_album_tracks(item_id) + # return provider album tracks + return await self._get_provider_album_tracks(item_id, provider_domain or provider_instance) async def versions( self, @@ -134,12 +141,12 @@ class AlbumsController(MediaControllerBase[Album]): await self._match(db_item) # return final db_item after all match/metadata actions db_item = await self.get_db_item(db_item.item_id) - # dump album tracks in db + # preload album tracks in db for prov_mapping in db_item.provider_mappings: for track in await self._get_provider_album_tracks( prov_mapping.item_id, prov_mapping.provider_instance ): - await self.mass.music.tracks.add_db_item(track) + await self.mass.music.tracks.get(track.item_id, track.provider, details=track) self.mass.signal_event( EventType.MEDIA_ITEM_UPDATED if existing else EventType.MEDIA_ITEM_ADDED, db_item.uri, diff --git a/music_assistant/server/controllers/media/tracks.py b/music_assistant/server/controllers/media/tracks.py index b797b730..2cc027a1 100644 --- a/music_assistant/server/controllers/media/tracks.py +++ b/music_assistant/server/controllers/media/tracks.py @@ -276,13 +276,11 @@ class TracksController(MediaControllerBase[Track]): metadata = cur_item.metadata.update(item.metadata, is_file_provider) provider_mappings = {*cur_item.provider_mappings, *item.provider_mappings} cur_item.isrc.update(item.isrc) - # ID3 tags from file providers are leading for core metadata if is_file_provider: track_artists = await self._get_track_artists(item) - track_albums = await self._get_track_albums(item) else: track_artists = await self._get_track_artists(cur_item, item) - track_albums = await self._get_track_albums(cur_item, item) + track_albums = await self._get_track_albums(cur_item, item) await self.mass.music.database.update( self.db_table, diff --git a/music_assistant/server/controllers/player_queues.py b/music_assistant/server/controllers/player_queues.py index 09abcdf4..8bfabd0a 100755 --- a/music_assistant/server/controllers/player_queues.py +++ b/music_assistant/server/controllers/player_queues.py @@ -203,7 +203,7 @@ class PlayerQueuesController: await self.play_index(queue_id, 0) # handle next: add item(s) in the index next to the playing/loaded/buffered index elif option == QueueOption.NEXT: - await self.load( + self.load( queue_id, queue_items=queue_items, insert_at_index=cur_index + 1, diff --git a/music_assistant/server/helpers/tags.py b/music_assistant/server/helpers/tags.py index 8585ebf2..ec8fb54c 100644 --- a/music_assistant/server/helpers/tags.py +++ b/music_assistant/server/helpers/tags.py @@ -185,7 +185,11 @@ class AudioTags: return AlbumType.AUDIOBOOK if "podcast" in self.tags.get("genre", "").lower() and len(self.chapters) > 1: return AlbumType.PODCAST - tag = self.tags.get("musicbrainzalbumtype", self.tags.get("musicbrainzalbumtype")) + tag = ( + self.tags.get("musicbrainzalbumtype") + or self.tags.get("albumtype") + or self.tags.get("releasetype") + ) if tag is None: return AlbumType.UNKNOWN # the album type tag is messy within id3 and may even contain multiple types diff --git a/music_assistant/server/providers/filesystem_local/base.py b/music_assistant/server/providers/filesystem_local/base.py index 1a0d7236..4a07009a 100644 --- a/music_assistant/server/providers/filesystem_local/base.py +++ b/music_assistant/server/providers/filesystem_local/base.py @@ -594,8 +594,12 @@ class FileSystemProviderBase(MusicProvider): # album if tags.album: - # work out if we have an album folder - album_dir = get_parentdir(file_item.path, tags.album) + # work out if we have an album and/or disc folder + # disc_dir is the folder level where the tracks are located + # this may be a separate disc folder (Disc 1, Disc 2 etc) underneath the album folder + # or this is an album folder with the disc attached + disc_dir = get_parentdir(file_item.path, f"disc {tags.disc or ''}") + album_dir = get_parentdir(disc_dir or file_item.path, tags.album) # album artist(s) if tags.album_artists: @@ -634,6 +638,7 @@ class FileSystemProviderBase(MusicProvider): track.album = await self._parse_album( tags.album, album_dir, + disc_dir, artists=album_artists, ) else: @@ -755,12 +760,13 @@ class FileSystemProviderBase(MusicProvider): if genre := info.get("genre"): artist.metadata.genres = set(split_items(genre)) # find local images - artist.metadata.images = await self._get_local_images(artist_path) or None + if images := await self._get_local_images(artist_path): + artist.metadata.images = images return artist async def _parse_album( - self, name: str | None, album_path: str | None, artists: list[Artist] + self, name: str | None, album_path: str | None, disc_path: str | None, artists: list[Artist] ) -> Album | None: """Lookup metadata in Album folder.""" assert (name or album_path) and artists @@ -785,34 +791,40 @@ class FileSystemProviderBase(MusicProvider): # return basic object if there is no dedicated album folder return album - nfo_file = os.path.join(album_path, "album.nfo") - if await self.exists(nfo_file): - # found NFO file with metadata - # https://kodi.wiki/view/NFO_files/Artists - data = b"" - async for chunk in self.read_file_content(nfo_file): - data += chunk - info = await asyncio.to_thread(xmltodict.parse, data) - info = info["album"] - album.name = info.get("title", info.get("name", name)) - if sort_name := info.get("sortname"): - album.sort_name = sort_name - if musicbrainz_id := info.get("musicbrainzreleasegroupid"): - album.musicbrainz_id = musicbrainz_id - if mb_artist_id := info.get("musicbrainzalbumartistid"): # noqa: SIM102 - if album.artist and not album.artist.musicbrainz_id: - album.artist.musicbrainz_id = mb_artist_id - if description := info.get("review"): - album.metadata.description = description - if year := info.get("year"): - album.year = int(year) - if genre := info.get("genre"): - album.metadata.genres = set(split_items(genre)) - # parse name/version - album.name, album.version = parse_title_and_version(album.name) - - # find local images - album.metadata.images = await self._get_local_images(album_path) or None + for folder_path in (disc_path, album_path): + if not folder_path: + continue + nfo_file = os.path.join(folder_path, "album.nfo") + if await self.exists(nfo_file): + # found NFO file with metadata + # https://kodi.wiki/view/NFO_files/Artists + data = b"" + async for chunk in self.read_file_content(nfo_file): + data += chunk + info = await asyncio.to_thread(xmltodict.parse, data) + info = info["album"] + album.name = info.get("title", info.get("name", name)) + if sort_name := info.get("sortname"): + album.sort_name = sort_name + if musicbrainz_id := info.get("musicbrainzreleasegroupid"): + album.musicbrainz_id = musicbrainz_id + if mb_artist_id := info.get("musicbrainzalbumartistid"): # noqa: SIM102 + if album.artist and not album.artist.musicbrainz_id: + album.artist.musicbrainz_id = mb_artist_id + if description := info.get("review"): + album.metadata.description = description + if year := info.get("year"): + album.year = int(year) + if genre := info.get("genre"): + album.metadata.genres = set(split_items(genre)) + # parse name/version + album.name, album.version = parse_title_and_version(album.name) + # find local images + if images := await self._get_local_images(folder_path): + if album.metadata.images is None: + album.metadata.images = images + else: + album.metadata.images += images return album -- 2.34.1