Fix metadata reading issues fileprovider (#572)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 25 Mar 2023 13:20:24 +0000 (14:20 +0100)
committerGitHub <noreply@github.com>
Sat, 25 Mar 2023 13:20:24 +0000 (14:20 +0100)
* precache album tracks

* fix overwiting over albums of track

* fix reading albumtype tag

music_assistant/server/controllers/media/albums.py
music_assistant/server/controllers/media/tracks.py
music_assistant/server/controllers/player_queues.py
music_assistant/server/helpers/tags.py
music_assistant/server/providers/filesystem_local/base.py

index b957ca525370c24fdaca6442fa6838b67882af48..dd2cdc2685a82ff0e078ef9c159069698ec4ec12 100644 (file)
@@ -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,
index b797b7302a5fd752d2838230e6520cf26178c991..2cc027a1b2e1f794b3f6405d2a56b79b2e46bf9d 100644 (file)
@@ -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,
index 09abcdf42968f768120840b55349b8eb6a86c617..8bfabd0a81935cffbcdf62a4badc89ec6eccf538 100755 (executable)
@@ -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,
index 8585ebf2c55c207542827ad7055eda93530fdbac..ec8fb54c11e46aa47c081266556456df637f5aec 100644 (file)
@@ -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
index 1a0d72367b936c534c746f1b4f7b0639e3a133cb..4a07009ac820fd32172bc1c4dafd1a7a9c4dcf1f 100644 (file)
@@ -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