From 864f836def4a1a98774aca08f9b264f19da79a33 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Sun, 19 Mar 2023 22:16:35 +0100 Subject: [PATCH] Various small fixes and improvements (#551) @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) --- .../common/models/config_entries.py | 16 +++- music_assistant/server/controllers/config.py | 2 + .../server/controllers/media/albums.py | 12 +-- .../server/controllers/media/radio.py | 10 +-- .../server/controllers/media/tracks.py | 10 +-- .../server/controllers/metadata.py | 25 ++++--- music_assistant/server/controllers/music.py | 12 ++- .../server/models/metadata_provider.py | 4 - .../server/providers/filesystem_local/base.py | 74 +++++++++++++------ .../providers/filesystem_local/helpers.py | 1 + .../providers/filesystem_local/manifest.json | 14 ++++ .../providers/filesystem_smb/manifest.json | 14 ++++ .../server/providers/lms_cli/models.py | 4 +- .../server/providers/spotify/__init__.py | 11 ++- .../server/providers/theaudiodb/__init__.py | 1 + .../server/providers/ytmusic/__init__.py | 4 + 16 files changed, 138 insertions(+), 76 deletions(-) diff --git a/music_assistant/common/models/config_entries.py b/music_assistant/common/models/config_entries.py index 62aff62f..a9a5a7cb 100644 --- a/music_assistant/common/models/config_entries.py +++ b/music_assistant/common/models/config_entries.py @@ -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", diff --git a/music_assistant/server/controllers/config.py b/music_assistant/server/controllers/config.py index 029fdf6a..ea0a4597 100644 --- a/music_assistant/server/controllers/config.py +++ b/music_assistant/server/controllers/config.py @@ -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()) diff --git a/music_assistant/server/controllers/media/albums.py b/music_assistant/server/controllers/media/albums.py index 22fc4ee6..01ff5b26 100644 --- a/music_assistant/server/controllers/media/albums.py +++ b/music_assistant/server/controllers/media/albums.py @@ -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) diff --git a/music_assistant/server/controllers/media/radio.py b/music_assistant/server/controllers/media/radio.py index 2dafdd2e..c87138ad 100644 --- a/music_assistant/server/controllers/media/radio.py +++ b/music_assistant/server/controllers/media/radio.py @@ -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() diff --git a/music_assistant/server/controllers/media/tracks.py b/music_assistant/server/controllers/media/tracks.py index 6a56740c..5dcd9c01 100644 --- a/music_assistant/server/controllers/media/tracks.py +++ b/music_assistant/server/controllers/media/tracks.py @@ -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() diff --git a/music_assistant/server/controllers/metadata.py b/music_assistant/server/controllers/metadata.py index 570a48a8..8e243239 100755 --- a/music_assistant/server/controllers/metadata.py +++ b/music_assistant/server/controllers/metadata.py @@ -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") diff --git a/music_assistant/server/controllers/music.py b/music_assistant/server/controllers/music.py index daa31bc8..6ca314f3 100755 --- a/music_assistant/server/controllers/music.py +++ b/music_assistant/server/controllers/music.py @@ -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.""" diff --git a/music_assistant/server/models/metadata_provider.py b/music_assistant/server/models/metadata_provider.py index afa8b2c1..5e803401 100644 --- a/music_assistant/server/models/metadata_provider.py +++ b/music_assistant/server/models/metadata_provider.py @@ -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 diff --git a/music_assistant/server/providers/filesystem_local/base.py b/music_assistant/server/providers/filesystem_local/base.py index 09eca0ff..014a276c 100644 --- a/music_assistant/server/providers/filesystem_local/base.py +++ b/music_assistant/server/providers/filesystem_local/base.py @@ -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())) diff --git a/music_assistant/server/providers/filesystem_local/helpers.py b/music_assistant/server/providers/filesystem_local/helpers.py index 174c52ad..3559933a 100644 --- a/music_assistant/server/providers/filesystem_local/helpers.py +++ b/music_assistant/server/providers/filesystem_local/helpers.py @@ -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) diff --git a/music_assistant/server/providers/filesystem_local/manifest.json b/music_assistant/server/providers/filesystem_local/manifest.json index cc6be9cb..7290b5d8 100644 --- a/music_assistant/server/providers/filesystem_local/manifest.json +++ b/music_assistant/server/providers/filesystem_local/manifest.json @@ -10,6 +10,20 @@ "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" } + ] } ], diff --git a/music_assistant/server/providers/filesystem_smb/manifest.json b/music_assistant/server/providers/filesystem_smb/manifest.json index 2ef2a422..15d366b7 100644 --- a/music_assistant/server/providers/filesystem_smb/manifest.json +++ b/music_assistant/server/providers/filesystem_smb/manifest.json @@ -79,6 +79,20 @@ "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" } + ] } ], diff --git a/music_assistant/server/providers/lms_cli/models.py b/music_assistant/server/providers/lms_cli/models.py index 8bfe882e..e7e19c06 100644 --- a/music_assistant/server/providers/lms_cli/models.py +++ b/music_assistant/server/providers/lms_cli/models.py @@ -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: diff --git a/music_assistant/server/providers/spotify/__init__.py b/music_assistant/server/providers/spotify/__init__.py index 63805e35..2de92305 100644 --- a/music_assistant/server/providers/spotify/__init__.py +++ b/music_assistant/server/providers/spotify/__init__.py @@ -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"): diff --git a/music_assistant/server/providers/theaudiodb/__init__.py b/music_assistant/server/providers/theaudiodb/__init__.py index c0d9acf8..107a7e6c 100644 --- a/music_assistant/server/providers/theaudiodb/__init__.py +++ b/music_assistant/server/providers/theaudiodb/__init__.py @@ -64,6 +64,7 @@ ALBUMTYPE_MAPPING = { "Single": AlbumType.SINGLE, "Compilation": AlbumType.COMPILATION, "Album": AlbumType.ALBUM, + "EP": AlbumType.EP, } diff --git a/music_assistant/server/providers/ytmusic/__init__.py b/music_assistant/server/providers/ytmusic/__init__.py index 7b7f2054..f76dbbbd 100644 --- a/music_assistant/server/providers/ytmusic/__init__.py +++ b/music_assistant/server/providers/ytmusic/__init__.py @@ -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 -- 2.34.1