Various small fixes and improvements (#551)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sun, 19 Mar 2023 21:16:35 +0000 (22:16 +0100)
committerGitHub <noreply@github.com>
Sun, 19 Mar 2023 21:16:35 +0000 (22:16 +0100)
@marcelveldt
[add guard for empty
objects](https://github.com/music-assistant/server/pull/551/commits/a43c51b39a766769c45fbdffebccc3b44fb0eaa1)

[a43c51b](https://github.com/music-assistant/server/pull/551/commits/a43c51b39a766769c45fbdffebccc3b44fb0eaa1)
@marcelveldt
[better handle value type
conversions](https://github.com/music-assistant/server/pull/551/commits/5566338577d70a19c0e27329aa1173526de1bb51)

[5566338](https://github.com/music-assistant/server/pull/551/commits/5566338577d70a19c0e27329aa1173526de1bb51)
@marcelveldt
[set playlist author for
YTM](https://github.com/music-assistant/server/pull/551/commits/5265b01dd00fc1f644984e8dcfbbfddfefd87877)

[5265b01](https://github.com/music-assistant/server/pull/551/commits/5265b01dd00fc1f644984e8dcfbbfddfefd87877)
@marcelveldt
[typo in
loglevel](https://github.com/music-assistant/server/pull/551/commits/6f2843d47b19ff5b86bd3137bbef0daec3e800d4)

[6f2843d](https://github.com/music-assistant/server/pull/551/commits/6f2843d47b19ff5b86bd3137bbef0daec3e800d4)
@marcelveldt
[unload provider on
disable](https://github.com/music-assistant/server/pull/551/commits/f1787a223c79b2a551461ea4d2ef28aff6fa9d61)

[f1787a2](https://github.com/music-assistant/server/pull/551/commits/f1787a223c79b2a551461ea4d2ef28aff6fa9d61)
@marcelveldt
[fix metadata pickup in
background](https://github.com/music-assistant/server/pull/551/commits/4f55d142ff085ec309cbd59aa9c1dd2ab9a58c40)

[4f55d14](https://github.com/music-assistant/server/pull/551/commits/4f55d142ff085ec309cbd59aa9c1dd2ab9a58c40)
@marcelveldt
[fix sync
interval](https://github.com/music-assistant/server/pull/551/commits/30f76afeaedfee381ba4885964889677c681bfdf)

[30f76af](https://github.com/music-assistant/server/pull/551/commits/30f76afeaedfee381ba4885964889677c681bfdf)
@marcelveldt
[do not include self in
versions](https://github.com/music-assistant/server/pull/551/commits/cb05e94d0d6d724e00fbb312a99b84d0ab1c72a5)

[cb05e94](https://github.com/music-assistant/server/pull/551/commits/cb05e94d0d6d724e00fbb312a99b84d0ab1c72a5)
@marcelveldt
[make default action for missing albumartist
configurable](https://github.com/music-assistant/server/pull/551/commits/fc13c997d26366f572c63d5cbef79950370f8a92)

[fc13c99](https://github.com/music-assistant/server/pull/551/commits/fc13c997d26366f572c63d5cbef79950370f8a92)
@marcelveldt
[simplify albumtype
match](https://github.com/music-assistant/server/pull/551/commits/60a161a42bd6537d30c53b8e2556cd2901a6d908)

[60a161a](https://github.com/music-assistant/server/pull/551/commits/60a161a42bd6537d30c53b8e2556cd2901a6d908)
@marcelveldt
[add albumtype EP on some missing
places](https://github.com/music-assistant/server/pull/551/commits/0b5168be9caa4f538e4e5ad61c8a333d66c429f9)

16 files changed:
music_assistant/common/models/config_entries.py
music_assistant/server/controllers/config.py
music_assistant/server/controllers/media/albums.py
music_assistant/server/controllers/media/radio.py
music_assistant/server/controllers/media/tracks.py
music_assistant/server/controllers/metadata.py
music_assistant/server/controllers/music.py
music_assistant/server/models/metadata_provider.py
music_assistant/server/providers/filesystem_local/base.py
music_assistant/server/providers/filesystem_local/helpers.py
music_assistant/server/providers/filesystem_local/manifest.json
music_assistant/server/providers/filesystem_smb/manifest.json
music_assistant/server/providers/lms_cli/models.py
music_assistant/server/providers/spotify/__init__.py
music_assistant/server/providers/theaudiodb/__init__.py
music_assistant/server/providers/ytmusic/__init__.py

index 62aff62f1403c4970ad8a0bc438974354eece4b9..a9a5a7cb65445e38b58f99a54974fa83098d258e 100644 (file)
@@ -108,10 +108,13 @@ class ConfigEntryValue(ConfigEntry):
         if entry.type == ConfigEntryType.LABEL:
             result.value = result.label
         if not isinstance(result.value, expected_type):
-            if result.value is None and allow_none:
-                # In some cases we allow this (e.g. create default config)
-                result.value = result.default_value
+            # value type does not match
+            try:
+                # try to simply convert it
+                result.value = expected_type(result.value)
                 return result
+            except ValueError:
+                pass
             # handle common conversions/mistakes
             if expected_type == float and isinstance(result.value, int):
                 result.value = float(result.value)
@@ -119,6 +122,11 @@ class ConfigEntryValue(ConfigEntry):
             if expected_type == int and isinstance(result.value, float):
                 result.value = int(result.value)
                 return result
+            # fallback to default
+            if result.value is None and allow_none:
+                # In some cases we allow this (e.g. create default config)
+                result.value = result.default_value
+                return result
             if entry.default_value:
                 LOGGER.warning(
                     "%s has unexpected type: %s, fallback to default",
@@ -271,7 +279,7 @@ DEFAULT_PROVIDER_CONFIG_ENTRIES = (
             ConfigValueOption("info", "INFO"),
             ConfigValueOption("warning", "WARNING"),
             ConfigValueOption("error", "ERROR"),
-            ConfigValueOption("debug", "DEBIG"),
+            ConfigValueOption("debug", "DEBUG"),
         ],
         default_value="GLOBAL",
         description="Set the log verbosity for this provider",
index 029fdf6a436c0e62e964604a9d6338199e777d1a..ea0a45972e7b362a627d03e4eac13bc7ac23dad3 100644 (file)
@@ -191,6 +191,8 @@ class ConfigController:
         # try to load the provider first to catch errors before we save it.
         if config.enabled:
             await self.mass.load_provider(config)
+        else:
+            await self.mass.unload_provider(config.instance_id)
         # load succeeded, save new config
         conf_key = f"{CONF_PROVIDERS}/{instance_id}"
         self.set(conf_key, config.to_raw())
index 22fc4ee6545c3ee9150093d9dd73d59eb14400f4..01ff5b26faebc91ae66a6f952c6c874e4170aade 100644 (file)
@@ -116,15 +116,9 @@ class AlbumsController(MediaControllerBase[Album]):
             for prov_item in prov_items
             if loose_compare_strings(album.name, prov_item.name)
         }
-        # make sure that the 'base' version is included
+        # make sure that the 'base' version is NOT included
         for prov_version in album.provider_mappings:
-            if prov_version.item_id in all_versions:
-                continue
-            album_copy = Album.from_dict(album.to_dict())
-            album_copy.item_id = prov_version.item_id
-            album_copy.provider = prov_version.provider_domain
-            album_copy.provider_mappings = {prov_version}
-            all_versions[prov_version.item_id] = album_copy
+            all_versions.pop(prov_version.item_id, None)
 
         # return the aggregated result
         return all_versions.values()
@@ -260,7 +254,7 @@ class AlbumsController(MediaControllerBase[Album]):
         assert not (db_rows and not recursive), "Tracks attached to album"
         for db_row in db_rows:
             with contextlib.suppress(MediaNotFoundError):
-                await self.mass.music.albums.delete_db_item(db_row["item_id"], recursive)
+                await self.mass.music.tracks.delete_db_item(db_row["item_id"], recursive)
 
         # delete the album itself from db
         await super().delete_db_item(item_id)
index 2dafdd2e14a3263fdbefb164b83a0de9a75f20e9..c87138ad1c45da61b5ea5c0228f5e18ba0536b09 100644 (file)
@@ -49,15 +49,9 @@ class RadioController(MediaControllerBase[Radio]):
             for prov_item in prov_items
             if loose_compare_strings(radio.name, prov_item.name)
         }
-        # make sure that the 'base' version is included
+        # make sure that the 'base' version is NOT included
         for prov_version in radio.provider_mappings:
-            if prov_version.item_id in all_versions:
-                continue
-            radio_copy = Radio.from_dict(radio.to_dict())
-            radio_copy.item_id = prov_version.item_id
-            radio_copy.provider = prov_version.provider_domain
-            radio_copy.provider_mappings = {prov_version}
-            all_versions[prov_version.item_id] = radio_copy
+            all_versions.pop(prov_version.item_id, None)
 
         # return the aggregated result
         return all_versions.values()
index 6a56740c9b8c7e7709cd7e1e5cf70e65ca24ab3f..5dcd9c017a88a2cc87c2ef1450170f8e3c4c8c35 100644 (file)
@@ -134,15 +134,9 @@ class TracksController(MediaControllerBase[Track]):
             if loose_compare_strings(track.name, prov_item.name)
             and compare_artists(prov_item.artists, track.artists, any_match=True)
         }
-        # make sure that the 'base' version is included
+        # make sure that the 'base' version is NOT included
         for prov_version in track.provider_mappings:
-            if prov_version.item_id in all_versions:
-                continue
-            # grab full item here including album details etc
-            prov_track = await self.get_provider_item(
-                prov_version.item_id, prov_version.provider_instance
-            )
-            all_versions[prov_version.item_id] = prov_track
+            all_versions.pop(prov_version.item_id, None)
 
         # return the aggregated result
         return all_versions.values()
index 570a48a886d436a976d0f72080f080a0e7d87139..8e24323942f2815d6071604120e64d70a1f167ae 100755 (executable)
@@ -2,6 +2,7 @@
 from __future__ import annotations
 
 import asyncio
+import contextlib
 import logging
 import os
 import urllib.parse
@@ -15,6 +16,7 @@ import aiofiles
 from aiohttp import web
 
 from music_assistant.common.models.enums import ImageType, MediaType, ProviderFeature, ProviderType
+from music_assistant.common.models.errors import ProviderUnavailableError
 from music_assistant.common.models.media_items import (
     Album,
     Artist,
@@ -75,7 +77,7 @@ class MetaDataController:
             self._pref_lang = lang.upper()
 
     def start_scan(self) -> None:
-        """Start background scan for missing Artist metadata."""
+        """Start background scan for missing metadata."""
 
         async def scan_artist_metadata():
             """Background task that scans for artists missing metadata on filesystem providers."""
@@ -84,18 +86,19 @@ class MetaDataController:
 
             LOGGER.info("Start scan for missing artist metadata")
             self.scan_busy = True
-            for prov in self.mass.music.providers:
-                if not prov.is_file():
+            async for artist in self.mass.music.artists.iter_db_items():
+                if artist.metadata.last_refresh is not None:
                     continue
-                async for artist in self.mass.music.artists.iter_db_items_by_prov_id(
-                    provider_instance=prov.instance_id
-                ):
-                    if artist.metadata.last_refresh is not None:
-                        continue
-                    # simply grabbing the full artist will trigger a full fetch
+                # most important is to see artist thumb in listings
+                # so if that is already present, move on
+                # full details can be grabbed later
+                if artist.image:
+                    continue
+                # simply grabbing the full artist will trigger a full fetch
+                with contextlib.suppress(ProviderUnavailableError):
                     await self.mass.music.artists.get(artist.item_id, artist.provider, lazy=False)
-                    # this is slow on purpose to not cause stress on the metadata providers
-                    await asyncio.sleep(30)
+                # this is slow on purpose to not cause stress on the metadata providers
+                await asyncio.sleep(30)
             self.scan_busy = False
             LOGGER.info("Finished scan for missing artist metadata")
 
index daa31bc8cb9ccb533658fd252ee39599c38e9359..6ca314f374623f089aa032998683ad9fc5f03b3c 100755 (executable)
@@ -47,6 +47,7 @@ if TYPE_CHECKING:
     from music_assistant.server import MusicAssistant
 
 LOGGER = logging.getLogger(f"{ROOT_LOGGER_NAME}.music")
+SYNC_INTERVAL = 3 * 3600
 
 
 class MusicController:
@@ -68,6 +69,7 @@ class MusicController:
         """Async initialize of module."""
         # setup library database
         await self._setup_database()
+        self.mass.create_task(self.start_sync(reschedule=SYNC_INTERVAL))
 
     async def close(self) -> None:
         """Cleanup on exit."""
@@ -82,6 +84,7 @@ class MusicController:
         self,
         media_types: list[MediaType] | None = None,
         providers: list[str] | None = None,
+        reschedule: int | None = None,
     ) -> None:
         """Start running the sync of (all or selected) musicproviders.
 
@@ -97,9 +100,16 @@ class MusicController:
             if provider.instance_id not in providers:
                 continue
             self._start_provider_sync(provider.instance_id, media_types)
-        # trgger metadata scan after provider sync completed
+        # trigger metadata scan after provider sync completed
         self.mass.metadata.start_scan()
 
+        # reschedule task if needed
+        def create_sync_task():
+            self.mass.create_task(self.start_sync, media_types, providers, reschedule)
+
+        if reschedule is not None:
+            self.mass.loop.call_later(reschedule, create_sync_task)
+
     @api_command("music/synctasks")
     def get_running_sync_tasks(self) -> list[SyncTask]:
         """Return list with providers that are currently syncing."""
index afa8b2c15d93dbe42c2c7041765a7a651413abaa..5e8034019dfc0bca63e7a38e50c46b7b15897df4 100644 (file)
@@ -36,19 +36,16 @@ class MetadataProvider(Provider):
         """Retrieve metadata for an artist on this Metadata provider."""
         if ProviderFeature.ARTIST_METADATA in self.supported_features:
             raise NotImplementedError
-        return
 
     async def get_album_metadata(self, album: Album) -> MediaItemMetadata | None:
         """Retrieve metadata for an album on this Metadata provider."""
         if ProviderFeature.ALBUM_METADATA in self.supported_features:
             raise NotImplementedError
-        return
 
     async def get_track_metadata(self, track: Track) -> MediaItemMetadata | None:
         """Retrieve metadata for a track on this Metadata provider."""
         if ProviderFeature.TRACK_METADATA in self.supported_features:
             raise NotImplementedError
-        return
 
     async def get_musicbrainz_artist_id(
         self, artist: Artist, ref_albums: Iterable[Album], ref_tracks: Iterable[Track]
@@ -56,4 +53,3 @@ class MetadataProvider(Provider):
         """Discover MusicBrainzArtistId for an artist given some reference albums/tracks."""
         if ProviderFeature.GET_ARTIST_MBID in self.supported_features:
             raise NotImplementedError
-        return
index 09eca0ff39f730392cfc71e6c96f3d023a653b86..014a276c2ebf081bbb26784c4f34b4ebf5a35bd8 100644 (file)
@@ -13,7 +13,11 @@ import xmltodict
 
 from music_assistant.common.helpers.util import parse_title_and_version
 from music_assistant.common.models.enums import ProviderFeature
-from music_assistant.common.models.errors import MediaNotFoundError, MusicAssistantError
+from music_assistant.common.models.errors import (
+    InvalidDataError,
+    MediaNotFoundError,
+    MusicAssistantError,
+)
 from music_assistant.common.models.media_items import (
     Album,
     AlbumType,
@@ -38,6 +42,8 @@ from music_assistant.server.models.music_provider import MusicProvider
 
 from .helpers import get_parentdir
 
+CONF_MISSING_ALBUM_ARTIST_ACTION = "missing_album_artist_action"
+
 TRACK_EXTENSIONS = ("mp3", "m4a", "mp4", "flac", "wav", "ogg", "aiff", "wma", "dsf")
 PLAYLIST_EXTENSIONS = ("m3u", "pls")
 SUPPORTED_EXTENSIONS = TRACK_EXTENSIONS + PLAYLIST_EXTENSIONS
@@ -237,6 +243,21 @@ class FileSystemProviderBase(MusicProvider):
         if prev_checksums is None:
             prev_checksums = {}
 
+        # process all deleted (or renamed) files first
+        cur_filenames = set()
+        async for item in self.listdir("", recursive=True):
+            if "." not in item.name or not item.ext:
+                # skip system files and files without extension
+                continue
+
+            if item.ext not in SUPPORTED_EXTENSIONS:
+                # unsupported file extension
+                continue
+            cur_filenames.add(item.path)
+        # work out deletions
+        deleted_files = set(prev_checksums.keys()) - cur_filenames
+        await self._process_deletions(deleted_files)
+
         # find all music files in the music directory and all subfolders
         # we work bottom up, as-in we derive all info from the tracks
         cur_checksums = {}
@@ -287,9 +308,6 @@ class FileSystemProviderBase(MusicProvider):
 
         # store (final) checksums in cache
         await self.mass.cache.set(cache_key, cur_checksums, SCHEMA_VERSION)
-        # work out deletions
-        deleted_files = set(prev_checksums.keys()) - set(cur_checksums.keys())
-        await self._process_deletions(deleted_files)
 
     async def _process_deletions(self, deleted_files: set[str]) -> None:
         """Process all deletions."""
@@ -308,7 +326,7 @@ class FileSystemProviderBase(MusicProvider):
             if db_item := await controller.get_db_item_by_prov_id(
                 file_path, provider_instance=self.instance_id
             ):
-                await controller.remove_prov_mapping(db_item.item_id, self.instance_id)
+                await controller.delete_db_item(db_item.item_id, True)
 
     async def get_artist(self, prov_artist_id: str) -> Artist:
         """Get full artist details by id."""
@@ -371,14 +389,27 @@ class FileSystemProviderBase(MusicProvider):
                             artist.musicbrainz_id = tags.musicbrainz_albumartistids[index]
                     album_artists.append(artist)
             else:
-                # always fallback to various artists as album artist if user did not tag album artist
-                # ID3 tag properly because we must have an album artist
-                self.logger.warning(
-                    "%s is missing ID3 tag [albumartist], using %s as fallback",
-                    file_item.path,
-                    VARIOUS_ARTISTS,
-                )
-                album_artists = [await self._parse_artist(name=VARIOUS_ARTISTS)]
+                # album artist tag is missing, determine fallback
+                fallback_action = self.config.get_value(CONF_MISSING_ALBUM_ARTIST_ACTION)
+                if fallback_action == "various_artists":
+                    self.logger.warning(
+                        "%s is missing ID3 tag [albumartist], using %s as fallback",
+                        file_item.path,
+                        VARIOUS_ARTISTS,
+                    )
+                    album_artists = [await self._parse_artist(name=VARIOUS_ARTISTS)]
+                elif fallback_action == "track_artist":
+                    self.logger.warning(
+                        "%s is missing ID3 tag [albumartist], using track artist(s) as fallback",
+                        file_item.path,
+                    )
+                    album_artists = [
+                        await self._parse_artist(name=track_artist_str)
+                        for track_artist_str in tags.artists
+                    ]
+                else:
+                    # default action is to skip the track
+                    raise InvalidDataError(f"{file_item.path} is missing ID3 tag [albumartist]")
 
             track.album = await self._parse_album(
                 tags.album,
@@ -395,8 +426,8 @@ class FileSystemProviderBase(MusicProvider):
                 artist := next((x for x in track.album.artists if x.name == track_artist_str), None)
             ):
                 track.artists.append(artist)
-                continue
-            artist = await self._parse_artist(track_artist_str)
+            else:
+                artist = await self._parse_artist(track_artist_str)
             if not artist.musicbrainz_id:
                 with contextlib.suppress(IndexError):
                     artist.musicbrainz_id = tags.musicbrainz_artistids[index]
@@ -433,14 +464,11 @@ class FileSystemProviderBase(MusicProvider):
         # try to parse albumtype
         if track.album and track.album.album_type == AlbumType.UNKNOWN:
             album_type = tags.album_type
-            if album_type and "compilation" in album_type:
-                track.album.album_type = AlbumType.COMPILATION
-            elif album_type and "single" in album_type:
-                track.album.album_type = AlbumType.SINGLE
-            elif album_type and "album" in album_type:
-                track.album.album_type = AlbumType.ALBUM
-            elif track.album.sort_name in track.sort_name:
-                track.album.album_type = AlbumType.SINGLE
+            try:
+                track.album.album_type = AlbumType(album_type)
+            except (ValueError, KeyError):
+                if track.album.sort_name in track.sort_name:
+                    track.album.album_type = AlbumType.SINGLE
 
         # set checksum to invalidate any cached listings
         checksum_timestamp = str(int(time()))
index 174c52adbccfd0dcb0b7be4e5c7a72692f760073..3559933a37bee85abad114ac7bb1517e1cf040bc 100644 (file)
@@ -11,6 +11,7 @@ def get_parentdir(base_path: str, name: str) -> str | None:
     parentdir = os.path.dirname(base_path)
     for _ in range(3):
         dirname = parentdir.rsplit(os.sep)[-1]
+        dirname = dirname.split("(")[0].split("[")[0].strip()
         if compare_strings(name, dirname, False):
             return parentdir
         parentdir = os.path.dirname(parentdir)
index cc6be9cbfb2461a9c3d85aa0dee763f3258960c5..7290b5d8b94e582beab3aca765a8ef4d715ffd44 100644 (file)
       "type": "string",
       "label": "Path",
       "default_value": "/media"
+    },
+    {
+      "key": "missing_album_artist_action",
+      "type": "string",
+      "label": "Action when a track is missing the Albumartist ID3 tag",
+      "default_value": "skip",
+      "description": "Music Assistant prefers information stored in ID3 tags and only uses online sources for additional metadata. This means that the ID3 tags need to be accurate, preferably tagged with MusicBrainz Picard.",
+      "advanced": true,
+      "required": false,
+      "options": [
+        { "title": "Skip track and log warning", "value": "skip" },
+        { "title": "Use Track artist(s)", "value": "track_artist" },
+        { "title": "Use Various Artists", "value": "various_artists" }
+      ]
     }
   ],
 
index 2ef2a4224244db568af6e376d95b98272dcf8768..15d366b7387dbb486c036e5527a7861cdef1469d 100644 (file)
       "description": "Controls whether the NetBIOS over TCP/IP (is_direct_tcp=False) or the newer Direct hosting of SMB over TCP/IP (is_direct_tcp=True) will be used for the communication. The default parameter is False which will use NetBIOS over TCP/IP for wider compatibility (TCP port: 139).",
       "advanced": true,
       "required": false
+    },
+    {
+      "key": "missing_album_artist_action",
+      "type": "string",
+      "label": "Action when a track is missing the Albumartist ID3 tag",
+      "default_value": "skip",
+      "description": "Music Assistant prefers information stored in ID3 tags and only uses online sources for additional metadata. This means that the ID3 tags need to be accurate, preferably tagged with MusicBrainz Picard.",
+      "advanced": true,
+      "required": false,
+      "options": [
+        { "title": "Skip track and log warning", "value": "skip" },
+        { "title": "Use Track artist(s)", "value": "track_artist" },
+        { "title": "Use Various Artists", "value": "various_artists" }
+      ]
     }
   ],
 
index 8bfe882e2de4da58a25c9a15afd73e72c7d164cb..e7e19c06e50e1fa65674c04306cbd835c55485f9 100644 (file)
@@ -124,8 +124,8 @@ PlaylistItem = TypedDict(
 def playlist_item_from_mass(queue_item: QueueItem, index: int = 0) -> PlaylistItem:
     """Parse PlaylistItem for the Json RPC interface from MA QueueItem."""
     if queue_item.media_item and queue_item.media_type == MediaType.TRACK:
-        artist = queue_item.media_item.artist.name
-        album = queue_item.media_item.album.name
+        artist = queue_item.media_item.artist.name if queue_item.media_item.artist else ""
+        album = queue_item.media_item.album.name if queue_item.media_item.album else ""
         title = queue_item.media_item.name
     elif queue_item.streamdetails and queue_item.streamdetails.stream_title:
         if " - " in queue_item.streamdetails.stream_title:
index 63805e35fc43c416a7e3b9dd5b86106724da5fe8..2de9230508446666ccf7900781b55cbaa541a86e 100644 (file)
@@ -2,6 +2,7 @@
 from __future__ import annotations
 
 import asyncio
+import contextlib
 import json
 import os
 import platform
@@ -364,12 +365,10 @@ class SpotifyProvider(MusicProvider):
         album = Album(item_id=album_obj["id"], provider=self.domain, name=name, version=version)
         for artist_obj in album_obj["artists"]:
             album.artists.append(await self._parse_artist(artist_obj))
-        if album_obj["album_type"] == "single":
-            album.album_type = AlbumType.SINGLE
-        elif album_obj["album_type"] == "compilation":
-            album.album_type = AlbumType.COMPILATION
-        elif album_obj["album_type"] == "album":
-            album.album_type = AlbumType.ALBUM
+
+        with contextlib.suppress(ValueError):
+            album.album_type = AlbumType(album_obj["album_type"])
+
         if "genres" in album_obj:
             album.metadata.genre = set(album_obj["genres"])
         if album_obj.get("images"):
index c0d9acf83ad42f8e5723e3dcc6799722395be959..107a7e6cf40ca0eca407c394913223b49c745614 100644 (file)
@@ -64,6 +64,7 @@ ALBUMTYPE_MAPPING = {
     "Single": AlbumType.SINGLE,
     "Compilation": AlbumType.COMPILATION,
     "Album": AlbumType.ALBUM,
+    "EP": AlbumType.EP,
 }
 
 
index 7b7f205423473c9e7af4eaf070a63b38eee80f82..f76dbbbd41aa7296e0da12696e38c1469802695f 100644 (file)
@@ -544,6 +544,10 @@ class YoutubeMusicProvider(MusicProvider):
                 provider_instance=self.instance_id,
             )
         )
+        if authors := playlist_obj.get("author"):
+            playlist.owner = authors[0]["name"] if isinstance(authors, list) else authors["name"]
+        else:
+            playlist.owner = self.instance_id
         playlist.metadata.checksum = playlist_obj.get("checksum")
         return playlist