handle more ItemMapping edge cases
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Mon, 3 Apr 2023 14:27:20 +0000 (16:27 +0200)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Mon, 3 Apr 2023 14:27:20 +0000 (16:27 +0200)
music_assistant/common/models/media_items.py
music_assistant/server/controllers/media/albums.py
music_assistant/server/controllers/media/artists.py
music_assistant/server/controllers/media/base.py
music_assistant/server/controllers/media/playlists.py
music_assistant/server/controllers/media/radio.py
music_assistant/server/controllers/media/tracks.py

index 74592850e45f4fbdd25de983a60e8eeafea1c816..9321cbd9a91e11bdab14b86314d6da8faa2dfab2 100755 (executable)
@@ -130,9 +130,11 @@ class MediaItemMetadata(DataClassDictMixin):
     def update(
         self,
         new_values: MediaItemMetadata,
-        allow_overwrite: bool = False,
+        allow_overwrite: bool = True,
     ) -> MediaItemMetadata:
         """Update metadata (in-place) with new values."""
+        if not new_values:
+            return self
         for fld in fields(self):
             new_val = getattr(new_values, fld.name)
             if new_val is None:
@@ -269,11 +271,14 @@ class ItemMapping(DataClassDictMixin):
     sort_name: str | None = None
     uri: str | None = None
     version: str = ""
+    available: bool = True
 
     @classmethod
     def from_item(cls, item: MediaItem):
         """Create ItemMapping object from regular item."""
-        return cls.from_dict(item.to_dict())
+        result = cls.from_dict(item.to_dict())
+        result.available = item.available
+        return result
 
     def __hash__(self):
         """Return custom hash."""
index 386a4f9f56e18b5129661e87e39a7665d657ab4d..32ecad09551bacadb7387ae4147aa46b43dd50db 100644 (file)
@@ -13,13 +13,12 @@ from music_assistant.common.models.errors import MediaNotFoundError, Unsupported
 from music_assistant.common.models.media_items import (
     Album,
     AlbumType,
-    Artist,
     DbAlbum,
     ItemMapping,
     MediaType,
     Track,
 )
-from music_assistant.constants import DB_TABLE_ALBUMS, DB_TABLE_TRACKS, VARIOUS_ARTISTS
+from music_assistant.constants import DB_TABLE_ALBUMS, DB_TABLE_TRACKS
 from music_assistant.server.controllers.media.base import MediaControllerBase
 from music_assistant.server.helpers.compare import compare_album, loose_compare_strings
 
@@ -116,6 +115,10 @@ class AlbumsController(MediaControllerBase[Album]):
         )
         return db_item
 
+    async def update(self, item_id: int, update: Album, overwrite: bool = False) -> Album:
+        """Update existing record in the database."""
+        return await self._update_db_item(item_id=item_id, item=update, overwrite=overwrite)
+
     async def delete(self, item_id: int, recursive: bool = False) -> None:
         """Delete record from the database."""
         # check album tracks
@@ -171,7 +174,10 @@ class AlbumsController(MediaControllerBase[Album]):
                 ]
             )
             for prov_item in prov_items
+            # title must (partially) match
             if loose_compare_strings(album.name, prov_item.name)
+            # artist must match
+            and album.artists[0].sort_name in {x.sort_name for x in prov_item.artists}
         }
         # make sure that the 'base' version is NOT included
         for prov_version in album.provider_mappings:
@@ -210,7 +216,7 @@ class AlbumsController(MediaControllerBase[Album]):
                 return await self._update_db_item(cur_item.item_id, item)
 
             # insert new item
-            album_artists = await self._get_album_artists(item, cur_item)
+            album_artists = await self._get_artist_mappings(item, cur_item)
             sort_artist = album_artists[0].sort_name if album_artists else ""
             new_item = await self.mass.music.database.insert(
                 self.db_table,
@@ -230,38 +236,30 @@ class AlbumsController(MediaControllerBase[Album]):
             return await self.get_db_item(item_id)
 
     async def _update_db_item(
-        self,
-        item_id: int,
-        item: Album,
+        self, item_id: int, item: Album | ItemMapping, overwrite: bool = False
     ) -> Album:
         """Update Album record in the database."""
-        assert item.provider_mappings, "Item is missing provider mapping(s)"
-        assert item.artists, f"Album {item.name} is missing artist"
         cur_item = await self.get_db_item(item_id)
-        is_file_provider = item.provider.startswith("filesystem")
-        metadata = cur_item.metadata.update(item.metadata, is_file_provider)
-        provider_mappings = {*cur_item.provider_mappings, *item.provider_mappings}
-        if is_file_provider:
-            album_artists = await self._get_album_artists(cur_item)
-        else:
-            album_artists = await self._get_album_artists(cur_item, item)
-        cur_item.barcode.update(item.barcode)
-        if item.album_type != AlbumType.UNKNOWN:
+        metadata = cur_item.metadata.update(getattr(item, "metadata", None), overwrite)
+        provider_mappings = self._get_provider_mappings(cur_item, item, overwrite)
+        album_artists = await self._get_artist_mappings(cur_item, item, overwrite)
+        if getattr(item, "barcode", None):
+            cur_item.barcode.update(item.barcode)
+        if getattr(item, "album_type", AlbumType.UNKNOWN) != AlbumType.UNKNOWN:
             album_type = item.album_type
         else:
             album_type = cur_item.album_type
-
         sort_artist = album_artists[0].sort_name if album_artists else ""
 
         await self.mass.music.database.update(
             self.db_table,
             {"item_id": item_id},
             {
-                "name": item.name if is_file_provider else cur_item.name,
-                "sort_name": item.sort_name if is_file_provider else cur_item.sort_name,
+                "name": item.name if overwrite else cur_item.name,
+                "sort_name": item.sort_name if overwrite else cur_item.sort_name,
                 "sort_artist": sort_artist,
-                "version": item.version if is_file_provider else cur_item.version,
-                "year": item.year or cur_item.year,
+                "version": item.version if overwrite else cur_item.version,
+                "year": item.year if overwrite else cur_item.year or item.year,
                 "barcode": ";".join(cur_item.barcode),
                 "album_type": album_type.value,
                 "artists": serialize_to_json(album_artists) or None,
@@ -425,36 +423,3 @@ class AlbumsController(MediaControllerBase[Album]):
                     db_album.name,
                     provider.name,
                 )
-
-    async def _get_album_artists(
-        self,
-        db_album: Album,
-        updated_album: Album | None = None,
-    ) -> list[ItemMapping]:
-        """Extract (database) album artist(s) as ItemMapping."""
-        album_artists = set()
-        for album in (updated_album, db_album):
-            if not album:
-                continue
-            for artist in album.artists:
-                album_artists.add(await self._get_artist_mapping(artist))
-        # use intermediate set to prevent duplicates
-        # filter various artists if multiple artists
-        if len(album_artists) > 1:
-            album_artists = {x for x in album_artists if (x.name != VARIOUS_ARTISTS)}
-        return list(album_artists)
-
-    async def _get_artist_mapping(self, artist: Artist | ItemMapping) -> ItemMapping:
-        """Extract (database) track artist as ItemMapping."""
-        if artist.provider == "database":
-            if isinstance(artist, ItemMapping):
-                return artist
-            return ItemMapping.from_item(artist)
-
-        if db_artist := await self.mass.music.artists.get_db_item_by_prov_id(
-            artist.item_id, artist.provider
-        ):
-            return ItemMapping.from_item(db_artist)
-
-        db_artist = await self.mass.music.artists._add_db_item(artist)
-        return ItemMapping.from_item(db_artist)
index 3e9ac019bc6a32a48e4d967e4b7e9eecaab80c83..b4eb0be810996361b426006a0f53e41dc6416f76 100644 (file)
@@ -74,6 +74,10 @@ class ArtistsController(MediaControllerBase[Artist]):
         )
         return db_item
 
+    async def update(self, item_id: int, update: Artist, overwrite: bool = False) -> Artist:
+        """Update existing record in the database."""
+        return await self._update_db_item(item_id=item_id, item=update, overwrite=overwrite)
+
     async def album_artists(
         self,
         in_library: bool | None = None,
@@ -270,21 +274,20 @@ class ArtistsController(MediaControllerBase[Artist]):
         )
         return items
 
-    async def _add_db_item(self, item: Artist) -> Artist:
+    async def _add_db_item(self, item: Artist | ItemMapping) -> Artist:
         """Add a new item record to the database."""
-        assert isinstance(item, Artist), "Not a full Artist object"
-        assert item.provider_mappings, "Item is missing provider mapping(s)"
         # enforce various artists name + id
-        if compare_strings(item.name, VARIOUS_ARTISTS):
-            item.musicbrainz_id = VARIOUS_ARTISTS_ID
-        if item.musicbrainz_id == VARIOUS_ARTISTS_ID:
-            item.name = VARIOUS_ARTISTS
+        if not isinstance(item, ItemMapping):
+            if compare_strings(item.name, VARIOUS_ARTISTS):
+                item.musicbrainz_id = VARIOUS_ARTISTS_ID
+            if item.musicbrainz_id == VARIOUS_ARTISTS_ID:
+                item.name = VARIOUS_ARTISTS
 
         async with self._db_add_lock:
             # always try to grab existing item by musicbrainz_id
             cur_item = None
-            if item.musicbrainz_id:
-                match = {"musicbrainz_id": item.musicbrainz_id}
+            if musicbrainz_id := getattr(item, "musicbrainz_id", None):
+                match = {"musicbrainz_id": musicbrainz_id}
                 cur_item = await self.mass.music.database.get_row(self.db_table, match)
             if not cur_item:
                 # fallback to exact name match
@@ -304,6 +307,10 @@ class ArtistsController(MediaControllerBase[Artist]):
             # insert item
             item.timestamp_added = int(utc_timestamp())
             item.timestamp_modified = int(utc_timestamp())
+            # edge case: item is an ItemMapping,
+            # try to construct (a half baken) Artist object from it
+            if isinstance(item, ItemMapping):
+                item = Artist.from_dict(item.to_dict())
             new_item = await self.mass.music.database.insert(self.db_table, item.to_db_row())
             item_id = new_item["item_id"]
             # update/set provider_mappings table
@@ -313,30 +320,28 @@ class ArtistsController(MediaControllerBase[Artist]):
             return await self.get_db_item(item_id)
 
     async def _update_db_item(
-        self,
-        item_id: int,
-        item: Artist,
+        self, item_id: int, item: Artist | ItemMapping, overwrite: bool = False
     ) -> Artist:
         """Update Artist record in the database."""
-        assert item.provider_mappings, "Item is missing provider mapping(s)"
         cur_item = await self.get_db_item(item_id)
-        is_file_provider = item.provider.startswith("filesystem")
-        metadata = cur_item.metadata.update(item.metadata, is_file_provider)
-        provider_mappings = {*cur_item.provider_mappings, *item.provider_mappings}
+        metadata = cur_item.metadata.update(getattr(item, "metadata", None), overwrite)
+        provider_mappings = self._get_provider_mappings(cur_item, item, overwrite)
 
         # enforce various artists name + id
-        if compare_strings(item.name, VARIOUS_ARTISTS):
-            item.musicbrainz_id = VARIOUS_ARTISTS_ID
-        if item.musicbrainz_id == VARIOUS_ARTISTS_ID:
-            item.name = VARIOUS_ARTISTS
+        musicbrainz_id = cur_item.musicbrainz_id
+        if (not musicbrainz_id or overwrite) and getattr(item, "musicbrainz_id", None):
+            if compare_strings(item.name, VARIOUS_ARTISTS):
+                item.musicbrainz_id = VARIOUS_ARTISTS_ID
+            if item.musicbrainz_id == VARIOUS_ARTISTS_ID:
+                item.name = VARIOUS_ARTISTS
 
         await self.mass.music.database.update(
             self.db_table,
             {"item_id": item_id},
             {
-                "name": item.name if is_file_provider else cur_item.name,
-                "sort_name": item.sort_name if is_file_provider else cur_item.sort_name,
-                "musicbrainz_id": item.musicbrainz_id or cur_item.musicbrainz_id,
+                "name": item.name if overwrite else cur_item.name,
+                "sort_name": item.sort_name if overwrite else cur_item.sort_name,
+                "musicbrainz_id": musicbrainz_id,
                 "metadata": serialize_to_json(metadata),
                 "provider_mappings": serialize_to_json(provider_mappings),
                 "timestamp_modified": int(utc_timestamp()),
index 614626da647bdd5fad0938ed269d8bd7f03bbe16..0cefa03e258fe253f786e98696d46285ee2f8047 100644 (file)
@@ -17,12 +17,12 @@ from music_assistant.common.models.media_items import (
     MediaItemType,
     PagedItems,
     ProviderMapping,
-    Track,
     media_from_dict,
 )
-from music_assistant.constants import DB_TABLE_PROVIDER_MAPPINGS, ROOT_LOGGER_NAME
+from music_assistant.constants import DB_TABLE_PROVIDER_MAPPINGS, ROOT_LOGGER_NAME, VARIOUS_ARTISTS
 
 if TYPE_CHECKING:
+    from music_assistant.common.models.media_items import Album, Artist, Track
     from music_assistant.server import MusicAssistant
 
 ItemCls = TypeVar("ItemCls", bound="MediaItemType")
@@ -48,6 +48,10 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
         """Add item to local db and return the database item."""
         raise NotImplementedError
 
+    @abstractmethod
+    async def update(self, item_id: int, update: ItemCls, overwrite: bool = False) -> ItemCls:
+        """Update existing record in the database."""
+
     async def delete(self, item_id: int, recursive: bool = False) -> None:  # noqa: ARG002
         """Delete record from the database."""
         db_item = await self.get_db_item(item_id)
@@ -414,26 +418,9 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
         if not fallback:
             fallback = await self.get_db_item_by_prov_id(item_id, provider_instance_id_or_domain)
         if fallback:
-            fallback_result = self.item_cls(
-                item_id=item_id,
-                provider=provider.instance_id,
-                name=fallback.name,
-                provider_mappings={
-                    ProviderMapping(
-                        item_id=item_id,
-                        provider_domain=provider.domain,
-                        provider_instance=provider.instance_id,
-                        available=False,
-                    )
-                },
-            )
-            if hasattr(fallback, "version") and hasattr(fallback_result, "version"):
-                fallback_result.version = fallback.version
-            if hasattr(fallback, "artists") and hasattr(fallback_result, "artists"):
-                fallback_result.artists = fallback.artists
-            if hasattr(fallback, "album") and hasattr(fallback_result, "album"):
-                fallback_result.album = fallback.album
-            return fallback_result
+            fallback_item = ItemMapping.from_item(fallback)
+            fallback_item.available = False
+            return fallback_item
         raise MediaNotFoundError(
             f"{self.media_type.value}://{item_id} not "
             "found on provider {provider_instance_id_or_domain}"
@@ -531,3 +518,52 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
                     "provider_item_id": provider_mapping.item_id,
                 },
             )
+
+    def _get_provider_mappings(
+        self,
+        org_item: ItemCls,
+        update_item: ItemCls | ItemMapping | None = None,
+        overwrite: bool = False,
+    ) -> set[ProviderMapping]:
+        """Get/merge provider mappings for an item."""
+        if not update_item or isinstance(update_item, ItemMapping):
+            return org_item.provider_mappings
+        if overwrite and update_item.provider_mappings:
+            return update_item.provider_mappings
+        return {*org_item.provider_mappings, *update_item.provider_mappings}
+
+    async def _get_artist_mappings(
+        self,
+        org_item: Album | Track,
+        update_item: Album | Track | ItemMapping | None = None,
+        overwrite: bool = False,
+    ) -> list[ItemMapping]:
+        """Extract (database) album/track artist(s) as ItemMapping."""
+        if not update_item or isinstance(update_item, ItemMapping):
+            return org_item.artists
+        if overwrite and update_item.provider_mappings:
+            return update_item.artists
+        item_artists: set[ItemMapping] = set()
+        for item in (org_item, update_item):
+            for artist in item.artists:
+                item_artists.add(await self._get_artist_mapping(artist))
+        # use intermediate set to prevent duplicates
+        # filter various artists if multiple artists
+        if len(item_artists) > 1:
+            item_artists = {x for x in item_artists if (x.name != VARIOUS_ARTISTS)}
+        return list(item_artists)
+
+    async def _get_artist_mapping(self, artist: Artist | ItemMapping) -> ItemMapping:
+        """Extract (database) track artist as ItemMapping."""
+        if artist.provider == "database":
+            if isinstance(artist, ItemMapping):
+                return artist
+            return ItemMapping.from_item(artist)
+
+        if db_artist := await self.mass.music.artists.get_db_item_by_prov_id(
+            artist.item_id, artist.provider
+        ):
+            return ItemMapping.from_item(db_artist)
+
+        db_artist = await self.mass.music.artists.add(artist, skip_metadata_lookup=True)
+        return ItemMapping.from_item(db_artist)
index 8a7ac505e0bb5e123db5f2fc4b0bb11bb1b805e1..789aec25ec90af7a567323a73dc3a20a2f662eb6 100644 (file)
@@ -60,6 +60,10 @@ class PlaylistController(MediaControllerBase[Playlist]):
         )
         return db_item
 
+    async def update(self, item_id: int, update: Playlist, overwrite: bool = False) -> Playlist:
+        """Update existing record in the database."""
+        return await self._update_db_item(item_id=item_id, item=update, overwrite=overwrite)
+
     async def tracks(
         self,
         item_id: str,
@@ -194,7 +198,6 @@ class PlaylistController(MediaControllerBase[Playlist]):
             if cur_item := await self.mass.music.database.get_row(self.db_table, match):
                 # update existing
                 return await self._update_db_item(cur_item["item_id"], item)
-
             # insert new item
             item.timestamp_added = int(utc_timestamp())
             item.timestamp_modified = int(utc_timestamp())
@@ -207,15 +210,12 @@ class PlaylistController(MediaControllerBase[Playlist]):
             return await self.get_db_item(item_id)
 
     async def _update_db_item(
-        self,
-        item_id: int,
-        item: Playlist,
+        self, item_id: int, item: Playlist, overwrite: bool = True
     ) -> Playlist:
         """Update Playlist record in the database."""
-        assert item.provider_mappings, "Item is missing provider mapping(s)"
         cur_item = await self.get_db_item(item_id)
-        metadata = cur_item.metadata.update(item.metadata)
-        provider_mappings = {*cur_item.provider_mappings, *item.provider_mappings}
+        metadata = cur_item.metadata.update(getattr(item, "metadata", None), overwrite)
+        provider_mappings = self._get_provider_mappings(cur_item, item, overwrite)
         await self.mass.music.database.update(
             self.db_table,
             {"item_id": item_id},
index 90ec671d251286fced1ce038f4eb182a79ae32dc..8a27bdba70dae995a22e4bbac5baefeab89f1228 100644 (file)
@@ -72,6 +72,10 @@ class RadioController(MediaControllerBase[Radio]):
         )
         return db_item
 
+    async def update(self, item_id: int, update: Radio, overwrite: bool = False) -> Radio:
+        """Update existing record in the database."""
+        return await self._update_db_item(item_id=item_id, item=update, overwrite=overwrite)
+
     async def _add_db_item(self, item: Radio) -> Radio:
         """Add a new item record to the database."""
         assert item.provider_mappings, "Item is missing provider mapping(s)"
@@ -80,7 +84,6 @@ class RadioController(MediaControllerBase[Radio]):
             if cur_item := await self.mass.music.database.get_row(self.db_table, match):
                 # update existing
                 return await self._update_db_item(cur_item["item_id"], item)
-
             # insert new item
             item.timestamp_added = int(utc_timestamp())
             item.timestamp_modified = int(utc_timestamp())
@@ -92,16 +95,11 @@ class RadioController(MediaControllerBase[Radio]):
             # return created object
             return await self.get_db_item(item_id)
 
-    async def _update_db_item(
-        self,
-        item_id: int,
-        item: Radio,
-    ) -> Radio:
+    async def _update_db_item(self, item_id: int, item: Radio, overwrite: bool = True) -> Radio:
         """Update Radio record in the database."""
-        assert item.provider_mappings, "Item is missing provider mapping(s)"
         cur_item = await self.get_db_item(item_id)
-        metadata = cur_item.metadata.update(item.metadata)
-        provider_mappings = {*cur_item.provider_mappings, *item.provider_mappings}
+        metadata = cur_item.metadata.update(getattr(item, "metadata", None), overwrite)
+        provider_mappings = self._get_provider_mappings(cur_item, item, overwrite)
         match = {"item_id": item_id}
         await self.mass.music.database.update(
             self.db_table,
index bee2b76fff4078440df1a6625e7bafda27eda952..0889d5126001da7b39e1b1d9a6267ff6f9e41378 100644 (file)
@@ -9,7 +9,6 @@ from music_assistant.common.models.enums import EventType, MediaType, ProviderFe
 from music_assistant.common.models.errors import MediaNotFoundError, UnsupportedFeaturedException
 from music_assistant.common.models.media_items import (
     Album,
-    Artist,
     DbTrack,
     ItemMapping,
     Track,
@@ -96,7 +95,7 @@ class TracksController(MediaControllerBase[Track]):
 
     async def add(self, item: Track, skip_metadata_lookup: bool = False) -> Track:
         """Add track to local db and return the new database item."""
-        assert item.artists
+        assert item.artists, "Artist(s) missing on Track"
         # resolve any ItemMapping artists
         item.artists = [
             await self.mass.music.artists.get_provider_item(
@@ -140,6 +139,10 @@ class TracksController(MediaControllerBase[Track]):
         )
         return db_item
 
+    async def update(self, item_id: int, update: Track, overwrite: bool = False) -> Track:
+        """Update existing record in the database."""
+        return await self._update_db_item(item_id=item_id, item=update, overwrite=overwrite)
+
     async def versions(
         self,
         item_id: str,
@@ -267,7 +270,7 @@ class TracksController(MediaControllerBase[Track]):
         """Add a new item record to the database."""
         assert isinstance(item, Track), "Not a full Track object"
         assert item.artists, "Track is missing artist(s)"
-        assert item.provider_mappings, "Track is missing provider id(s)"
+        assert item.provider_mappings, "Track is missing provider mapping(s)"
         async with self._db_add_lock:
             cur_item = None
 
@@ -294,7 +297,7 @@ class TracksController(MediaControllerBase[Track]):
                 return await self._update_db_item(cur_item.item_id, item)
 
             # no existing match found: insert new item
-            track_artists = await self._get_track_artists(item)
+            track_artists = await self._get_artist_mappings(item)
             track_albums = await self._get_track_albums(item)
             sort_artist = track_artists[0].sort_name if track_artists else ""
             sort_album = track_albums[0].sort_name if track_albums else ""
@@ -318,30 +321,24 @@ class TracksController(MediaControllerBase[Track]):
             return await self.get_db_item(item_id)
 
     async def _update_db_item(
-        self,
-        item_id: int,
-        item: Track,
+        self, item_id: int, item: Track | ItemMapping, overwrite: bool = False
     ) -> Track:
         """Update Track record in the database, merging data."""
         cur_item = await self.get_db_item(item_id)
-        is_file_provider = item.provider.startswith("filesystem")
-        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)
-        if is_file_provider:
-            track_artists = await self._get_track_artists(item)
-        else:
-            track_artists = await self._get_track_artists(cur_item, item)
+        metadata = cur_item.metadata.update(getattr(item, "metadata", None), overwrite)
+        provider_mappings = self._get_provider_mappings(cur_item, item, overwrite)
+        if getattr(item, "isrc", None):
+            cur_item.isrc.update(item.isrc)
+        track_artists = await self._get_artist_mappings(cur_item, item)
         track_albums = await self._get_track_albums(cur_item, item)
-
         await self.mass.music.database.update(
             self.db_table,
             {"item_id": item_id},
             {
-                "name": item.name if is_file_provider else cur_item.name,
-                "sort_name": item.sort_name if is_file_provider else cur_item.sort_name,
-                "version": item.version if is_file_provider else cur_item.version,
-                "duration": item.duration or cur_item.duration,
+                "name": item.name or cur_item.name,
+                "sort_name": item.sort_name or cur_item.sort_name,
+                "version": item.version or cur_item.version,
+                "duration": getattr(item, "duration", None) or cur_item.duration,
                 "artists": serialize_to_json(track_artists),
                 "albums": serialize_to_json(track_albums),
                 "metadata": serialize_to_json(metadata),
@@ -355,30 +352,20 @@ class TracksController(MediaControllerBase[Track]):
         self.logger.debug("updated %s in database: %s", item.name, item_id)
         return await self.get_db_item(item_id)
 
-    async def _get_track_artists(
-        self,
-        base_track: Track,
-        upd_track: Track | None = None,
-    ) -> list[ItemMapping]:
-        """Extract all (unique) artists of track as ItemMapping."""
-        track_artists = upd_track.artists if upd_track and upd_track.artists else base_track.artists
-        # use intermediate set to clear out duplicates
-        return list({await self._get_artist_mapping(x) for x in track_artists})
-
     async def _get_track_albums(
         self,
         base_track: Track,
-        upd_track: Track | None = None,
+        upd_track: Track | ItemMapping | None = None,
     ) -> list[TrackAlbumMapping]:
         """Extract all (unique) albums of track as TrackAlbumMapping."""
         track_albums: list[TrackAlbumMapping] = []
         # existing TrackAlbumMappings are starting point
         if base_track.albums:
             track_albums = base_track.albums
-        elif upd_track and upd_track.albums:
+        elif upd_track and getattr(upd_track, "albums", None):
             track_albums = upd_track.albums
         # append update item album if needed
-        if upd_track and upd_track.album:
+        if upd_track and getattr(upd_track, "album", None):
             mapping = await self._get_album_mapping(upd_track.album)
             mapping = TrackAlbumMapping.from_dict(
                 {
@@ -419,20 +406,5 @@ class TracksController(MediaControllerBase[Track]):
         ):
             return ItemMapping.from_item(db_album)
 
-        db_album = await self.mass.music.albums._add_db_item(album)
+        db_album = await self.mass.music.albums.add(album, skip_metadata_lookup=True)
         return ItemMapping.from_item(db_album)
-
-    async def _get_artist_mapping(self, artist: Artist | ItemMapping) -> ItemMapping:
-        """Extract (database) track artist as ItemMapping."""
-        if artist.provider == "database":
-            if isinstance(artist, ItemMapping):
-                return artist
-            return ItemMapping.from_item(artist)
-
-        if db_artist := await self.mass.music.artists.get_db_item_by_prov_id(
-            artist.item_id, artist.provider
-        ):
-            return ItemMapping.from_item(db_artist)
-
-        db_artist = await self.mass.music.artists._add_db_item(artist)
-        return ItemMapping.from_item(db_artist)