From: Marcel van der Veldt Date: Tue, 14 Oct 2025 06:49:34 +0000 (+0200) Subject: Simplify library sync/import settings (#2507) X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=a7bf5365fe19690cd744e12be6977e328fad3041;p=music-assistant-server.git Simplify library sync/import settings (#2507) * Simplify import options * bump models to 1.1.62 * Also simplify the sync back option * fix filesystem * Update music_assistant/models/music_provider.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- diff --git a/music_assistant/constants.py b/music_assistant/constants.py index c2a3ae9b..bf7d815e 100644 --- a/music_assistant/constants.py +++ b/music_assistant/constants.py @@ -656,157 +656,118 @@ CONF_ENTRY_MANUAL_DISCOVERY_IPS = ConfigEntry( multi_value=True, ) -CONF_LIBRARY_IMPORT_OPTIONS = [ - ConfigValueOption("Import into the library only", "import_only"), - ConfigValueOption("Import into the library, and mark as favorite", "import_as_favorite"), - ConfigValueOption("Do not import into the library", "no_import"), -] -CONF_ENTRY_LIBRARY_IMPORT_ARTISTS = ConfigEntry( - key="library_import_artists", - type=ConfigEntryType.STRING, - label="Import Artists from this provider into Music Assistant", - description="Whether to import (favourite/library) artists from this " - "provider into the Music Assistant Library.", - options=CONF_LIBRARY_IMPORT_OPTIONS, - default_value="import_as_favorite", +CONF_ENTRY_LIBRARY_SYNC_ARTISTS = ConfigEntry( + key="library_sync_artists", + type=ConfigEntryType.BOOLEAN, + label="Sync Library Artists from this provider to Music Assistant", + description="Whether to synchronize (favourited/in-library) Artists from this " + "provider to the Music Assistant Library.", + default_value=True, category="sync_options", ) -CONF_ENTRY_LIBRARY_IMPORT_ALBUMS = ConfigEntry( - key="library_import_albums", - type=ConfigEntryType.STRING, - label="Import Albums from this provider into Music Assistant", - description="Whether to import (favourite/library) albums from this " - "provider into the Music Assistant Library. \n\n" +CONF_ENTRY_LIBRARY_SYNC_ALBUMS = ConfigEntry( + key="library_sync_albums", + type=ConfigEntryType.BOOLEAN, + label="Sync Library Albums from this provider to Music Assistant", + description="Whether to import (favourited/in-library) Albums from this " + "provider to the Music Assistant Library. \n\n" "Please note that by adding an Album into the Music Assistant library, " - "the album artists will always be imported as well (not as favorites though).", - options=CONF_LIBRARY_IMPORT_OPTIONS, - default_value="import_as_favorite", + "the Album Artists will always be imported as well.", + default_value=True, category="sync_options", ) -CONF_ENTRY_LIBRARY_IMPORT_TRACKS = ConfigEntry( - key="library_import_tracks", - type=ConfigEntryType.STRING, - label="Import Tracks from this provider into Music Assistant", - description="Whether to import (favourite/library) tracks from this " - "provider into the Music Assistant Library. \n\n" +CONF_ENTRY_LIBRARY_SYNC_TRACKS = ConfigEntry( + key="library_sync_tracks", + type=ConfigEntryType.BOOLEAN, + label="Sync Library Tracks from this provider to Music Assistant", + description="Whether to import (favourited/in-library) Tracks from this " + "provider to the Music Assistant Library. \n\n" "Please note that by adding a Track into the Music Assistant library, " - "the track artists and album will always be imported as well (not as favorites though).", - options=CONF_LIBRARY_IMPORT_OPTIONS, - default_value="import_as_favorite", + "the Track's Artists and Album will always be imported as well.", + default_value=True, category="sync_options", ) -CONF_ENTRY_LIBRARY_IMPORT_PLAYLISTS = ConfigEntry( - key="library_import_playlists", - type=ConfigEntryType.STRING, - label="Import Playlists from this provider into Music Assistant", - description="Whether to import (favourite/library) playlists from this " - "provider into the Music Assistant Library.", - options=CONF_LIBRARY_IMPORT_OPTIONS, - default_value="import_as_favorite", +CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS = ConfigEntry( + key="library_sync_playlists", + type=ConfigEntryType.BOOLEAN, + label="Sync Library Playlists from this provider to Music Assistant", + description="Whether to import (favourited/in-library) Playlists from this " + "provider to the Music Assistant Library.", + default_value=True, category="sync_options", ) -CONF_ENTRY_LIBRARY_IMPORT_PODCASTS = ConfigEntry( - key="library_import_podcasts", - type=ConfigEntryType.STRING, - label="Import Podcasts from this provider into Music Assistant", - description="Whether to import (favourite/library) podcasts from this " - "provider into the Music Assistant Library.", - options=CONF_LIBRARY_IMPORT_OPTIONS, - default_value="import_as_favorite", +CONF_ENTRY_LIBRARY_SYNC_PODCASTS = ConfigEntry( + key="library_sync_podcasts", + type=ConfigEntryType.BOOLEAN, + label="Sync Library Podcasts from this provider to Music Assistant", + description="Whether to import (favourited/in-library) Podcasts from this " + "provider to the Music Assistant Library.", + default_value=True, category="sync_options", ) -CONF_ENTRY_LIBRARY_IMPORT_AUDIOBOOKS = ConfigEntry( - key="library_import_audiobooks", - type=ConfigEntryType.STRING, - label="Import Audiobooks from this provider into Music Assistant", - description="Whether to import (favourite/library) audiobooks from this " - "provider into the Music Assistant Library.", - options=CONF_LIBRARY_IMPORT_OPTIONS, - default_value="import_as_favorite", +CONF_ENTRY_LIBRARY_SYNC_AUDIOBOOKS = ConfigEntry( + key="library_sync_audiobooks", + type=ConfigEntryType.BOOLEAN, + label="Sync Library Audiobooks from this provider to Music Assistant", + description="Whether to import (favourited/in-library) Audiobooks from this " + "provider to the Music Assistant Library.", + default_value=True, category="sync_options", ) -CONF_ENTRY_LIBRARY_IMPORT_RADIOS = ConfigEntry( - key="library_import_radios", - type=ConfigEntryType.STRING, - label="Import Radios from this provider into Music Assistant", - description="Whether to import (favourite/library) radios from this " - "provider into the Music Assistant Library.", - options=CONF_LIBRARY_IMPORT_OPTIONS, - default_value="import_as_favorite", +CONF_ENTRY_LIBRARY_SYNC_RADIOS = ConfigEntry( + key="library_sync_radios", + type=ConfigEntryType.BOOLEAN, + label="Sync Library Radios from this provider to Music Assistant", + description="Whether to import (favourited/in-library) Radio stations from this " + "provider to the Music Assistant Library.", + default_value=True, category="sync_options", ) -CONF_ENTRY_LIBRARY_IMPORT_ALBUM_TRACKS = ConfigEntry( - key="library_import_album_tracks", +CONF_ENTRY_LIBRARY_SYNC_ALBUM_TRACKS = ConfigEntry( + key="library_sync_album_tracks", type=ConfigEntryType.BOOLEAN, label="Import album tracks", - description="By default, when importing albums into the library, " + description="By default, when importing Albums into the library, " "only the Album itself will be imported into the Music Assistant Library, " "allowing you to manually browse and select which tracks you want to import. \n\n" "If you want to override this default behavior, " "you can use this configuration option.\n\n" - "Please note that some streaming providers may already define this behavior unsolicited, " + "Please note that some (streaming) providers may already define this behavior unsolicited, " "by automatically adding all tracks from the album to their library/favorites.", default_value=False, category="sync_options", ) -CONF_ENTRY_LIBRARY_IMPORT_PLAYLIST_TRACKS = ConfigEntry( - key="library_import_playlist_tracks", +CONF_ENTRY_LIBRARY_SYNC_PLAYLIST_TRACKS = ConfigEntry( + key="library_sync_playlist_tracks", type=ConfigEntryType.STRING, label="Import playlist tracks", - description="By default, when importing playlists into the library, " + description="By default, when importing Playlists into the library, " "only the Playlist itself will be imported into the Music Assistant Library, " - "allowing you to browse and play the playlist and optionally add any individual " - "tracks of the playlist to the Music Assistant Library manually. \n\n" + "allowing you to browse and play the Playlist and optionally add any individual " + "tracks of the Playlist to the Music Assistant Library manually. \n\n" "Use this configuration option to override this default behavior, " - "by specifying the playlists for which you'd like to import all tracks.\n" + "by specifying the Playlists for which you'd like to import all tracks.\n" "You can either enter the Playlist name (case sensitive) or the Playlist URI.", default_value=[], category="sync_options", multi_value=True, ) -CONF_ENTRY_LIBRARY_EXPORT_ADD = ConfigEntry( - key="library_export_add", - type=ConfigEntryType.STRING, - label="Sync back library additions", - description="Specify the behavior if an item is (manually) added to the " - "Music Assistant Library (or favorites). \n" - "Should we synchronise that action back to the provider?\n\n" - "You can choose to add items to the provider's library as soon as you " - "add it to the Music Assistant Library or only do that when you mark the item as " - "favorite. \nIf you do not want to sync back to the provider at all, you can choose " - "the 'Don't sync back to the provider' option.", - default_value="export_favorite", - category="sync_options", - options=[ - ConfigValueOption("When an item is added to the library", "export_library"), - ConfigValueOption("When an item is marked as favorite", "export_favorite"), - ConfigValueOption("Don't sync back to the provider", "no_export"), - ], -) -CONF_ENTRY_LIBRARY_EXPORT_REMOVE = ConfigEntry( - key="library_export_remove", - type=ConfigEntryType.STRING, - label="Sync back library removals", - description="Specify the behavior if an item is (manually) removed from the " - "Music Assistant Library (or favorites). \n" +CONF_ENTRY_LIBRARY_SYNC_BACK = ConfigEntry( + key="library_sync_back", + type=ConfigEntryType.BOOLEAN, + label="Sync back library additions/removals (2-way sync)", + description="Specify the behavior if an item is manually added to " + "(or removed from) the Music Assistant Library. \n" "Should we synchronise that action back to the provider?\n\n" - "You can choose to remove items from the provider's library as soon as you (manually) " - "remove it from the Music Assistant Library or only do that when you unmark the item as " - "favorite. \nIf you do not want to sync back to the provider at all, you can choose " - "the 'Don't sync back to the provider' option.\n\n" - "Please note that if you you don't sync removals back to the provider and you have enabled " - "automatic sync/import for this provider, the item may reappear in the library " + "Please note that if you you don't sync back to the provider and you have enabled " + "automatic sync/import for this provider, a removed item may reappear in the library " "the next time a sync is performed.", - default_value="export_favorite", + default_value=True, category="sync_options", - options=[ - ConfigValueOption("When an item is removed from the library", "export_library"), - ConfigValueOption("When an item is unmarked as favorite", "export_favorite"), - ConfigValueOption("Don't sync back to the provider", "no_export"), - ], ) + CONF_PROVIDER_SYNC_INTERVAL_OPTIONS = [ ConfigValueOption("Disable automatic sync for this mediatype", 0), ConfigValueOption("Every 30 minutes", 30), @@ -827,7 +788,7 @@ CONF_ENTRY_PROVIDER_SYNC_INTERVAL_ARTISTS = ConfigEntry( options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS, default_value=720, category="sync_options", - depends_on=CONF_ENTRY_LIBRARY_IMPORT_ARTISTS.key, + depends_on=CONF_ENTRY_LIBRARY_SYNC_ARTISTS.key, depends_on_value_not="no_import", required=True, ) @@ -839,7 +800,7 @@ CONF_ENTRY_PROVIDER_SYNC_INTERVAL_ALBUMS = ConfigEntry( options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS, default_value=720, category="sync_options", - depends_on=CONF_ENTRY_LIBRARY_IMPORT_ALBUMS.key, + depends_on=CONF_ENTRY_LIBRARY_SYNC_ALBUMS.key, depends_on_value_not="no_import", required=True, ) @@ -851,7 +812,7 @@ CONF_ENTRY_PROVIDER_SYNC_INTERVAL_TRACKS = ConfigEntry( options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS, default_value=720, category="sync_options", - depends_on=CONF_ENTRY_LIBRARY_IMPORT_TRACKS.key, + depends_on=CONF_ENTRY_LIBRARY_SYNC_TRACKS.key, depends_on_value_not="no_import", required=True, ) @@ -863,7 +824,7 @@ CONF_ENTRY_PROVIDER_SYNC_INTERVAL_PLAYLISTS = ConfigEntry( options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS, default_value=720, category="sync_options", - depends_on=CONF_ENTRY_LIBRARY_IMPORT_PLAYLISTS.key, + depends_on=CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS.key, depends_on_value_not="no_import", required=True, ) @@ -875,7 +836,7 @@ CONF_ENTRY_PROVIDER_SYNC_INTERVAL_PODCASTS = ConfigEntry( options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS, default_value=720, category="sync_options", - depends_on=CONF_ENTRY_LIBRARY_IMPORT_PODCASTS.key, + depends_on=CONF_ENTRY_LIBRARY_SYNC_PODCASTS.key, depends_on_value_not="no_import", required=True, ) @@ -887,7 +848,7 @@ CONF_ENTRY_PROVIDER_SYNC_INTERVAL_AUDIOBOOKS = ConfigEntry( options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS, default_value=720, category="sync_options", - depends_on=CONF_ENTRY_LIBRARY_IMPORT_AUDIOBOOKS.key, + depends_on=CONF_ENTRY_LIBRARY_SYNC_AUDIOBOOKS.key, depends_on_value_not="no_import", required=True, ) @@ -899,7 +860,7 @@ CONF_ENTRY_PROVIDER_SYNC_INTERVAL_RADIOS = ConfigEntry( options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS, default_value=720, category="sync_options", - depends_on=CONF_ENTRY_LIBRARY_IMPORT_RADIOS.key, + depends_on=CONF_ENTRY_LIBRARY_SYNC_RADIOS.key, depends_on_value_not="no_import", required=True, ) diff --git a/music_assistant/controllers/config.py b/music_assistant/controllers/config.py index 09ba41eb..58ee0db4 100644 --- a/music_assistant/controllers/config.py +++ b/music_assistant/controllers/config.py @@ -36,17 +36,16 @@ from music_assistant.constants import ( CONF_DEPRECATED_EQ_BASS, CONF_DEPRECATED_EQ_MID, CONF_DEPRECATED_EQ_TREBLE, - CONF_ENTRY_LIBRARY_EXPORT_ADD, - CONF_ENTRY_LIBRARY_EXPORT_REMOVE, - CONF_ENTRY_LIBRARY_IMPORT_ALBUM_TRACKS, - CONF_ENTRY_LIBRARY_IMPORT_ALBUMS, - CONF_ENTRY_LIBRARY_IMPORT_ARTISTS, - CONF_ENTRY_LIBRARY_IMPORT_AUDIOBOOKS, - CONF_ENTRY_LIBRARY_IMPORT_PLAYLIST_TRACKS, - CONF_ENTRY_LIBRARY_IMPORT_PLAYLISTS, - CONF_ENTRY_LIBRARY_IMPORT_PODCASTS, - CONF_ENTRY_LIBRARY_IMPORT_RADIOS, - CONF_ENTRY_LIBRARY_IMPORT_TRACKS, + CONF_ENTRY_LIBRARY_SYNC_ALBUM_TRACKS, + CONF_ENTRY_LIBRARY_SYNC_ALBUMS, + CONF_ENTRY_LIBRARY_SYNC_ARTISTS, + CONF_ENTRY_LIBRARY_SYNC_AUDIOBOOKS, + CONF_ENTRY_LIBRARY_SYNC_BACK, + CONF_ENTRY_LIBRARY_SYNC_PLAYLIST_TRACKS, + CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS, + CONF_ENTRY_LIBRARY_SYNC_PODCASTS, + CONF_ENTRY_LIBRARY_SYNC_RADIOS, + CONF_ENTRY_LIBRARY_SYNC_TRACKS, CONF_ENTRY_PROVIDER_SYNC_INTERVAL_ALBUMS, CONF_ENTRY_PROVIDER_SYNC_INTERVAL_ARTISTS, CONF_ENTRY_PROVIDER_SYNC_INTERVAL_AUDIOBOOKS, @@ -283,23 +282,23 @@ class ConfigController: if manifest.type == ProviderType.MUSIC: # library sync settings if ProviderFeature.LIBRARY_ARTISTS in supported_features: - extra_entries.append(CONF_ENTRY_LIBRARY_IMPORT_ARTISTS) + extra_entries.append(CONF_ENTRY_LIBRARY_SYNC_ARTISTS) if ProviderFeature.LIBRARY_ALBUMS in supported_features: - extra_entries.append(CONF_ENTRY_LIBRARY_IMPORT_ALBUMS) + extra_entries.append(CONF_ENTRY_LIBRARY_SYNC_ALBUMS) if provider and provider.is_streaming_provider: - extra_entries.append(CONF_ENTRY_LIBRARY_IMPORT_ALBUM_TRACKS) + extra_entries.append(CONF_ENTRY_LIBRARY_SYNC_ALBUM_TRACKS) if ProviderFeature.LIBRARY_TRACKS in supported_features: - extra_entries.append(CONF_ENTRY_LIBRARY_IMPORT_TRACKS) + extra_entries.append(CONF_ENTRY_LIBRARY_SYNC_TRACKS) if ProviderFeature.LIBRARY_PLAYLISTS in supported_features: - extra_entries.append(CONF_ENTRY_LIBRARY_IMPORT_PLAYLISTS) + extra_entries.append(CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS) if provider and provider.is_streaming_provider: - extra_entries.append(CONF_ENTRY_LIBRARY_IMPORT_PLAYLIST_TRACKS) + extra_entries.append(CONF_ENTRY_LIBRARY_SYNC_PLAYLIST_TRACKS) if ProviderFeature.LIBRARY_AUDIOBOOKS in supported_features: - extra_entries.append(CONF_ENTRY_LIBRARY_IMPORT_AUDIOBOOKS) + extra_entries.append(CONF_ENTRY_LIBRARY_SYNC_AUDIOBOOKS) if ProviderFeature.LIBRARY_PODCASTS in supported_features: - extra_entries.append(CONF_ENTRY_LIBRARY_IMPORT_PODCASTS) + extra_entries.append(CONF_ENTRY_LIBRARY_SYNC_PODCASTS) if ProviderFeature.LIBRARY_RADIOS in supported_features: - extra_entries.append(CONF_ENTRY_LIBRARY_IMPORT_RADIOS) + extra_entries.append(CONF_ENTRY_LIBRARY_SYNC_RADIOS) # sync interval settings if ProviderFeature.LIBRARY_ARTISTS in supported_features: extra_entries.append(CONF_ENTRY_PROVIDER_SYNC_INTERVAL_ARTISTS) @@ -327,8 +326,7 @@ class ConfigController: ProviderFeature.LIBRARY_RADIOS_EDIT, } ): - extra_entries.append(CONF_ENTRY_LIBRARY_EXPORT_ADD) - extra_entries.append(CONF_ENTRY_LIBRARY_EXPORT_REMOVE) + extra_entries.append(CONF_ENTRY_LIBRARY_SYNC_BACK) return [ *DEFAULT_PROVIDER_CONFIG_ENTRIES, diff --git a/music_assistant/controllers/media/albums.py b/music_assistant/controllers/media/albums.py index f71e42e9..0bb634da 100644 --- a/music_assistant/controllers/media/albums.py +++ b/music_assistant/controllers/media/albums.py @@ -302,9 +302,7 @@ class AlbumsController(MediaControllerBase[Album]): extra_query_parts=[f"WHERE album_tracks.album_id = {item_id}"], ) - async def add_item_mapping_as_album_to_library( - self, item: ItemMapping, import_as_favorite: bool = False - ) -> Album: + async def add_item_mapping_as_album_to_library(self, item: ItemMapping) -> Album: """ Add an ItemMapping as an Album to the library. diff --git a/music_assistant/controllers/music.py b/music_assistant/controllers/music.py index a5e79c0a..a235409d 100644 --- a/music_assistant/controllers/music.py +++ b/music_assistant/controllers/music.py @@ -43,8 +43,6 @@ from music_assistant_models.provider import SyncTask from music_assistant_models.unique_list import UniqueList from music_assistant.constants import ( - CONF_ENTRY_LIBRARY_EXPORT_ADD, - CONF_ENTRY_LIBRARY_EXPORT_REMOVE, DB_TABLE_ALBUM_ARTISTS, DB_TABLE_ALBUM_TRACKS, DB_TABLE_ALBUMS, @@ -210,14 +208,13 @@ class MusicController(CoreController): if not provider.library_supported(media_type): continue # handle mediatype specific sync config - conf_key = f"library_import_{media_type}s" + conf_key = f"library_sync_{media_type}s" sync_conf = await self.mass.config.get_provider_config_value( provider.instance_id, conf_key ) - if sync_conf == "no_import": + if not sync_conf: continue - import_as_favorite = sync_conf == "import_as_favorite" - self._start_provider_sync(provider, media_type, import_as_favorite) + self._start_provider_sync(provider, media_type) @api_command("music/synctasks") def get_running_sync_tasks(self) -> list[SyncTask]: @@ -620,18 +617,21 @@ class MusicController(CoreController): provider = self.mass.get_provider(prov_mapping.provider_instance) if not provider.library_edit_supported(item.media_type): continue - if prov_mapping.in_library: + if not provider.library_sync_back_enabled(full_item.media_type): continue - conf_export_library = provider.config.get_value( - CONF_ENTRY_LIBRARY_EXPORT_ADD.key, CONF_ENTRY_LIBRARY_EXPORT_ADD.default_value - ) - if conf_export_library != "export_favorite": + if not prov_mapping.in_library: + # add to provider library first + prov_item = deepcopy(full_item) + prov_item.provider = prov_mapping.provider_instance + prov_item.item_id = prov_mapping.item_id + await provider.library_add(prov_item) + provider_mappings_updated = True + prov_mapping.in_library = True + # set favorite at provider + if not provider.library_favorites_edit_supported(full_item.media_type): continue - prov_item = deepcopy(full_item) - prov_item.provider = prov_mapping.provider_instance - prov_item.item_id = prov_mapping.item_id - self.mass.create_task(provider.library_add(prov_item)) - provider_mappings_updated = True + await provider.set_favorite(prov_mapping.item_id, full_item.media_type, True) + if provider_mappings_updated: await ctrl.set_provider_mappings(full_item.item_id, full_item.provider_mappings) @@ -654,14 +654,11 @@ class MusicController(CoreController): if not prov_mapping.in_library: continue provider = self.mass.get_provider(prov_mapping.provider_instance) - if not provider.library_edit_supported(full_item.media_type): + if not provider.library_favorites_edit_supported(full_item.media_type): continue - conf_export_library = provider.config.get_value( - CONF_ENTRY_LIBRARY_EXPORT_REMOVE.key, CONF_ENTRY_LIBRARY_EXPORT_REMOVE.default_value - ) - if conf_export_library != "export_favorite": + if not provider.library_sync_back_enabled(full_item.media_type): continue - self.mass.create_task(provider.library_remove(prov_mapping.item_id, media_type)) + self.mass.create_task(provider.set_favorite(prov_mapping.item_id, media_type, False)) prov_mapping.in_library = False provider_mappings_updated = True if provider_mappings_updated: @@ -685,11 +682,9 @@ class MusicController(CoreController): provider = self.mass.get_provider(prov_mapping.provider_instance) if not provider.library_edit_supported(full_item.media_type): continue - conf_export_library = provider.config.get_value( - CONF_ENTRY_LIBRARY_EXPORT_REMOVE.key, CONF_ENTRY_LIBRARY_EXPORT_REMOVE.default_value - ) - if conf_export_library != "export_library": + if not provider.library_sync_back_enabled(full_item.media_type): continue + prov_mapping.in_library = False self.mass.create_task(provider.library_remove(prov_mapping.item_id, media_type)) # remove from library await ctrl.remove_item_from_library(library_item_id, recursive) @@ -713,10 +708,7 @@ class MusicController(CoreController): provider = self.mass.get_provider(prov_mapping.provider_instance) if not provider.library_edit_supported(full_item.media_type): continue - conf_export_library = provider.config.get_value( - CONF_ENTRY_LIBRARY_EXPORT_ADD.key, CONF_ENTRY_LIBRARY_EXPORT_ADD.default_value - ) - if conf_export_library != "export_library": + if not provider.library_sync_back_enabled(full_item.media_type): continue prov_item = deepcopy(full_item) if full_item.provider == "library" else full_item prov_item.provider = prov_mapping.provider_instance @@ -1408,9 +1400,7 @@ class MusicController(CoreController): ) return [] - def _start_provider_sync( - self, provider: MusicProvider, media_type: MediaType, import_as_favorite: bool - ) -> None: + def _start_provider_sync(self, provider: MusicProvider, media_type: MediaType) -> None: """Start sync task on provider and track progress.""" # check if we're not already running a sync task for this provider/mediatype for sync_task in self.in_progress_syncs: @@ -1430,7 +1420,7 @@ class MusicController(CoreController): # Wrap the provider sync into a lock to prevent # race conditions when multiple providers are syncing at the same time. async with self._sync_lock: - await provider.sync_library(media_type, import_as_favorite) + await provider.sync_library(media_type) # we keep track of running sync tasks task = self.mass.create_task(run_sync()) @@ -1523,9 +1513,9 @@ class MusicController(CoreController): # cancel any existing timers self.mass.cancel_timer(job_key) # handle mediatype specific sync config - conf_key = f"library_import_{media_type}s" + conf_key = f"library_sync_{media_type}s" sync_conf = await self.mass.config.get_provider_config_value(provider.instance_id, conf_key) - if sync_conf == "no_import": + if not sync_conf: return conf_key = f"provider_sync_interval_{media_type.value}s" sync_interval = cast( @@ -1536,7 +1526,6 @@ class MusicController(CoreController): # sync disabled for this media type return sync_interval = sync_interval * 60 # config interval is in minutes - convert to seconds - import_as_favorite = sync_conf == "import_as_favorite" if is_initial: # schedule the first sync run @@ -1554,7 +1543,6 @@ class MusicController(CoreController): self._start_provider_sync, provider, media_type, - import_as_favorite, task_id=job_key, ) diff --git a/music_assistant/models/music_provider.py b/music_assistant/models/music_provider.py index d170b7b3..adf5d4df 100644 --- a/music_assistant/models/music_provider.py +++ b/music_assistant/models/music_provider.py @@ -29,8 +29,9 @@ from music_assistant_models.media_items import ( ) from music_assistant.constants import ( - CONF_ENTRY_LIBRARY_IMPORT_ALBUM_TRACKS, - CONF_ENTRY_LIBRARY_IMPORT_PLAYLIST_TRACKS, + CONF_ENTRY_LIBRARY_SYNC_ALBUM_TRACKS, + CONF_ENTRY_LIBRARY_SYNC_BACK, + CONF_ENTRY_LIBRARY_SYNC_PLAYLIST_TRACKS, ) from .provider import Provider @@ -312,6 +313,51 @@ class MusicProvider(Provider): ) return True + async def set_favorite(self, prov_item_id: str, media_type: MediaType, favorite: bool) -> None: + """ + Set favorite status for item in provider's library. + + Only called if provider supports ProviderFeature.FAVORITE_*_EDIT. + + Note that this should only be implemented by a provider implementation if + the provider differentiates between 'in library' and 'favorited' items. + """ + if ( + media_type == MediaType.ARTIST + and ProviderFeature.FAVORITE_ARTISTS_EDIT in self.supported_features + ): + raise NotImplementedError + if ( + media_type == MediaType.ALBUM + and ProviderFeature.FAVORITE_ALBUMS_EDIT in self.supported_features + ): + raise NotImplementedError + if ( + media_type == MediaType.TRACK + and ProviderFeature.FAVORITE_TRACKS_EDIT in self.supported_features + ): + raise NotImplementedError + if ( + media_type == MediaType.PLAYLIST + and ProviderFeature.FAVORITE_PLAYLISTS_EDIT in self.supported_features + ): + raise NotImplementedError + if ( + media_type == MediaType.RADIO + and ProviderFeature.FAVORITE_RADIOS_EDIT in self.supported_features + ): + raise NotImplementedError + if ( + media_type == MediaType.AUDIOBOOK + and ProviderFeature.FAVORITE_AUDIOBOOKS_EDIT in self.supported_features + ): + raise NotImplementedError + if ( + media_type == MediaType.PODCAST + and ProviderFeature.FAVORITE_PODCASTS_EDIT in self.supported_features + ): + raise NotImplementedError + async def add_playlist_tracks(self, prov_playlist_id: str, prov_track_ids: list[str]) -> None: """Add track(s) to playlist. @@ -578,7 +624,7 @@ class MusicProvider(Provider): raise NotImplementedError return [] - async def sync_library(self, media_type: MediaType, import_as_favorite: bool) -> None: + async def sync_library(self, media_type: MediaType) -> None: """Run library sync for this provider.""" # this reference implementation may be overridden # with a provider specific approach if needed @@ -587,19 +633,19 @@ class MusicProvider(Provider): raise UnsupportedFeaturedException("Library sync not supported for this media type") if media_type == MediaType.ARTIST: - cur_db_ids = await self._sync_library_artists(import_as_favorite) + cur_db_ids = await self._sync_library_artists() elif media_type == MediaType.ALBUM: - cur_db_ids = await self._sync_library_albums(import_as_favorite) + cur_db_ids = await self._sync_library_albums() elif media_type == MediaType.TRACK: - cur_db_ids = await self._sync_library_tracks(import_as_favorite) + cur_db_ids = await self._sync_library_tracks() elif media_type == MediaType.PLAYLIST: - cur_db_ids = await self._sync_library_playlists(import_as_favorite) + cur_db_ids = await self._sync_library_playlists() elif media_type == MediaType.PODCAST: - cur_db_ids = await self._sync_library_podcasts(import_as_favorite) + cur_db_ids = await self._sync_library_podcasts() elif media_type == MediaType.RADIO: - cur_db_ids = await self._sync_library_radios(import_as_favorite) + cur_db_ids = await self._sync_library_radios() elif media_type == MediaType.AUDIOBOOK: - cur_db_ids = await self._sync_library_audiobooks(import_as_favorite) + cur_db_ids = await self._sync_library_audiobooks() else: # this should not happen but catch it anyways raise UnsupportedFeaturedException(f"Unexpected media type to sync: {media_type}") @@ -664,7 +710,7 @@ class MusicProvider(Provider): category=CACHE_CATEGORY_PREV_LIBRARY_IDS, ) - async def _sync_library_artists(self, import_as_favorite: bool) -> set[int]: + async def _sync_library_artists(self) -> set[int]: """Sync Library Artists to Music Assistant library.""" self.logger.debug("Start sync of Artists to Music Assistant library.") cur_db_ids: set[int] = set() @@ -675,10 +721,8 @@ class MusicProvider(Provider): try: if not library_item: # add item to the library - if import_as_favorite: - prov_item.favorite = True library_item = await self.mass.music.artists.add_item_to_library(prov_item) - elif not library_item.favorite and import_as_favorite: + elif not library_item.favorite and prov_item.favorite: # existing library item not favorite but should be await self.mass.music.artists.set_favorite(library_item.item_id, True) elif not self._check_provider_mappings(library_item, prov_item, True): @@ -696,13 +740,13 @@ class MusicProvider(Provider): ) return cur_db_ids - async def _sync_library_albums(self, import_as_favorite: bool) -> set[int]: + async def _sync_library_albums(self) -> set[int]: """Sync Library Albums to Music Assistant library.""" self.logger.debug("Start sync of Albums to Music Assistant library.") cur_db_ids: set[int] = set() conf_sync_album_tracks = self.config.get_value( - CONF_ENTRY_LIBRARY_IMPORT_ALBUM_TRACKS.key, - CONF_ENTRY_LIBRARY_IMPORT_ALBUM_TRACKS.default_value, + CONF_ENTRY_LIBRARY_SYNC_ALBUM_TRACKS.key, + CONF_ENTRY_LIBRARY_SYNC_ALBUM_TRACKS.default_value, ) sync_album_tracks = bool(conf_sync_album_tracks) async for prov_item in self.get_library_albums(): @@ -712,10 +756,8 @@ class MusicProvider(Provider): try: if not library_item: # add item to the library - if import_as_favorite: - prov_item.favorite = True library_item = await self.mass.music.albums.add_item_to_library(prov_item) - elif not library_item.favorite and import_as_favorite: + elif not library_item.favorite and prov_item.favorite: # existing library item not favorite but should be await self.mass.music.albums.set_favorite(library_item.item_id, True) elif not self._check_provider_mappings(library_item, prov_item, True): @@ -763,7 +805,7 @@ class MusicProvider(Provider): str(err), ) - async def _sync_library_audiobooks(self, import_as_favorite: bool) -> set[int]: + async def _sync_library_audiobooks(self) -> set[int]: """Sync Library Audiobooks to Music Assistant library.""" self.logger.debug("Start sync of Audiobooks to Music Assistant library.") cur_db_ids: set[int] = set() @@ -774,10 +816,8 @@ class MusicProvider(Provider): try: if not library_item: # add item to the library - if import_as_favorite: - prov_item.favorite = True library_item = await self.mass.music.audiobooks.add_item_to_library(prov_item) - elif not library_item.favorite and import_as_favorite: + elif not library_item.favorite and prov_item.favorite: # existing library item not favorite but should be await self.mass.music.audiobooks.set_favorite(library_item.item_id, True) elif not self._check_provider_mappings(library_item, prov_item, True): @@ -809,12 +849,12 @@ class MusicProvider(Provider): ) return cur_db_ids - async def _sync_library_playlists(self, import_as_favorite: bool) -> set[int]: + async def _sync_library_playlists(self) -> set[int]: """Sync Library Playlists to Music Assistant library.""" self.logger.debug("Start sync of Playlists to Music Assistant library.") conf_sync_playlist_tracks = self.config.get_value( - CONF_ENTRY_LIBRARY_IMPORT_PLAYLIST_TRACKS.key, - CONF_ENTRY_LIBRARY_IMPORT_PLAYLIST_TRACKS.default_value, + CONF_ENTRY_LIBRARY_SYNC_PLAYLIST_TRACKS.key, + CONF_ENTRY_LIBRARY_SYNC_PLAYLIST_TRACKS.default_value, ) conf_sync_playlist_tracks = cast("list[str]", conf_sync_playlist_tracks) cur_db_ids: set[int] = set() @@ -825,10 +865,8 @@ class MusicProvider(Provider): try: if not library_item: # add item to the library - if import_as_favorite: - prov_item.favorite = True library_item = await self.mass.music.playlists.add_item_to_library(prov_item) - elif not library_item.favorite and import_as_favorite: + elif not library_item.favorite and prov_item.favorite: # existing library item not favorite but should be await self.mass.music.playlists.set_favorite(library_item.item_id, True) elif not self._check_provider_mappings(library_item, prov_item, True): @@ -879,7 +917,7 @@ class MusicProvider(Provider): str(err), ) - async def _sync_library_tracks(self, import_as_favorite: bool) -> set[int]: + async def _sync_library_tracks(self) -> set[int]: """Sync Library Tracks to Music Assistant library.""" self.logger.debug("Start sync of Tracks to Music Assistant library.") cur_db_ids: set[int] = set() @@ -898,15 +936,13 @@ class MusicProvider(Provider): continue if not library_item: # add item to the library - if import_as_favorite: - prov_item.favorite = True library_item = await self.mass.music.tracks.add_item_to_library(prov_item) elif library_item.available != prov_item.available: # existing library item but availability changed library_item = await self.mass.music.tracks.update_item_in_library( library_item.item_id, prov_item ) - elif not library_item.favorite and import_as_favorite: + elif not library_item.favorite and prov_item.favorite: # existing library item not favorite but should be await self.mass.music.tracks.set_favorite(library_item.item_id, True) elif not self._check_provider_mappings(library_item, prov_item, True): @@ -924,7 +960,7 @@ class MusicProvider(Provider): ) return cur_db_ids - async def _sync_library_podcasts(self, import_as_favorite: bool) -> set[int]: + async def _sync_library_podcasts(self) -> set[int]: """Sync Library Podcasts to Music Assistant library.""" self.logger.debug("Start sync of Podcasts to Music Assistant library.") cur_db_ids: set[int] = set() @@ -935,15 +971,13 @@ class MusicProvider(Provider): try: if not library_item: # add item to the library - if import_as_favorite: - prov_item.favorite = True library_item = await self.mass.music.podcasts.add_item_to_library(prov_item) elif library_item.available != prov_item.available: # existing library item but availability changed library_item = await self.mass.music.podcasts.update_item_in_library( library_item.item_id, prov_item ) - elif not library_item.favorite and import_as_favorite: + elif not library_item.favorite and prov_item.favorite: # existing library item not favorite but should be await self.mass.music.podcasts.set_favorite(library_item.item_id, True) elif not self._check_provider_mappings(library_item, prov_item, True): @@ -968,7 +1002,7 @@ class MusicProvider(Provider): ) return cur_db_ids - async def _sync_library_radios(self, import_as_favorite: bool) -> set[int]: + async def _sync_library_radios(self) -> set[int]: """Sync Library Radios to Music Assistant library.""" self.logger.debug("Start sync of Radios to Music Assistant library.") cur_db_ids: set[int] = set() @@ -979,10 +1013,8 @@ class MusicProvider(Provider): try: if not library_item: # add item to the library - if import_as_favorite: - prov_item.favorite = True library_item = await self.mass.music.radio.add_item_to_library(prov_item) - elif not library_item.favorite and import_as_favorite: + elif not library_item.favorite and prov_item.favorite: # existing library item not favorite but should be await self.mass.music.radio.set_favorite(library_item.item_id, True) elif not self._check_provider_mappings(library_item, prov_item, True): @@ -1040,6 +1072,31 @@ class MusicProvider(Provider): return ProviderFeature.LIBRARY_PODCASTS_EDIT in self.supported_features return False + def library_sync_back_enabled(self, media_type: MediaType) -> bool: + """Return if Library sync back is enabled for given MediaType on this provider.""" + conf_value = self.config.get_value( + CONF_ENTRY_LIBRARY_SYNC_BACK.key, CONF_ENTRY_LIBRARY_SYNC_BACK.default_value + ) + return bool(conf_value) + + def library_favorites_edit_supported(self, media_type: MediaType) -> bool: + """Return if favorites add/remove is supported for given MediaType on this provider.""" + if media_type == MediaType.ARTIST: + return ProviderFeature.FAVORITE_ARTISTS_EDIT in self.supported_features + if media_type == MediaType.ALBUM: + return ProviderFeature.FAVORITE_ALBUMS_EDIT in self.supported_features + if media_type == MediaType.TRACK: + return ProviderFeature.FAVORITE_TRACKS_EDIT in self.supported_features + if media_type == MediaType.PLAYLIST: + return ProviderFeature.FAVORITE_PLAYLISTS_EDIT in self.supported_features + if media_type == MediaType.RADIO: + return ProviderFeature.FAVORITE_RADIOS_EDIT in self.supported_features + if media_type == MediaType.AUDIOBOOK: + return ProviderFeature.FAVORITE_AUDIOBOOKS_EDIT in self.supported_features + if media_type == MediaType.PODCAST: + return ProviderFeature.FAVORITE_PODCASTS_EDIT in self.supported_features + return False + async def iter_playlist_tracks( self, prov_playlist_id: str, diff --git a/music_assistant/providers/_demo_music_provider/__init__.py b/music_assistant/providers/_demo_music_provider/__init__.py index 3bb4788f..67fbe7f8 100644 --- a/music_assistant/providers/_demo_music_provider/__init__.py +++ b/music_assistant/providers/_demo_music_provider/__init__.py @@ -584,7 +584,7 @@ class MyDemoMusicprovider(MusicProvider): # This is only called if you reported the RECOMMENDATIONS feature in the supported_features. return [] - async def sync_library(self, media_type: MediaType, import_as_favorite: bool) -> None: + async def sync_library(self, media_type: MediaType) -> None: """Run library sync for this provider.""" # Run a full sync of the library for the given media type. # This is called by the music controller to sync items from your provider to the library. diff --git a/music_assistant/providers/audiobookshelf/__init__.py b/music_assistant/providers/audiobookshelf/__init__.py index 4bcd8226..c67e326a 100644 --- a/music_assistant/providers/audiobookshelf/__init__.py +++ b/music_assistant/providers/audiobookshelf/__init__.py @@ -343,7 +343,7 @@ for more details. return False @handle_refresh_token - async def sync_library(self, media_type: MediaType, import_as_favorite: bool) -> None: + async def sync_library(self, media_type: MediaType) -> None: """Obtain audiobook library ids and podcast library ids.""" libraries = await self._client.get_all_libraries() if len(libraries) == 0: @@ -356,7 +356,7 @@ for more details. and media_type == MediaType.PODCAST ): self.libraries.podcasts[library.id_] = LibraryHelper(name=library.name) - await super().sync_library(media_type, import_as_favorite) + await super().sync_library(media_type) await self._cache_set_helper_libraries() # update playlog diff --git a/music_assistant/providers/builtin/__init__.py b/music_assistant/providers/builtin/__init__.py index 1fdb9d52..98a6390c 100644 --- a/music_assistant/providers/builtin/__init__.py +++ b/music_assistant/providers/builtin/__init__.py @@ -46,11 +46,10 @@ from .constants import ( BUILTIN_PLAYLISTS, BUILTIN_PLAYLISTS_ENTRIES, COLLAGE_IMAGE_PLAYLISTS, - CONF_ENTRY_LIBRARY_EXPORT_ADD_HIDDEN, - CONF_ENTRY_LIBRARY_EXPORT_REMOVE_HIDDEN, - CONF_ENTRY_LIBRARY_IMPORT_PLAYLISTS_HIDDEN, - CONF_ENTRY_LIBRARY_IMPORT_RADIOS_HIDDEN, - CONF_ENTRY_LIBRARY_IMPORT_TRACKS_HIDDEN, + CONF_ENTRY_LIBRARY_SYNC_BACK_HIDDEN, + CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS_HIDDEN, + CONF_ENTRY_LIBRARY_SYNC_RADIOS_HIDDEN, + CONF_ENTRY_LIBRARY_SYNC_TRACKS_HIDDEN, CONF_ENTRY_PROVIDER_SYNC_INTERVAL_PLAYLISTS_MOD, CONF_ENTRY_PROVIDER_SYNC_INTERVAL_RADIOS_HIDDEN, CONF_ENTRY_PROVIDER_SYNC_INTERVAL_TRACKS_HIDDEN, @@ -110,14 +109,13 @@ async def get_config_entries( return ( *BUILTIN_PLAYLISTS_ENTRIES, # hide some of the default (dynamic) entries for library management - CONF_ENTRY_LIBRARY_IMPORT_TRACKS_HIDDEN, - CONF_ENTRY_LIBRARY_IMPORT_PLAYLISTS_HIDDEN, - CONF_ENTRY_LIBRARY_IMPORT_RADIOS_HIDDEN, + CONF_ENTRY_LIBRARY_SYNC_TRACKS_HIDDEN, + CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS_HIDDEN, + CONF_ENTRY_LIBRARY_SYNC_RADIOS_HIDDEN, CONF_ENTRY_PROVIDER_SYNC_INTERVAL_TRACKS_HIDDEN, CONF_ENTRY_PROVIDER_SYNC_INTERVAL_RADIOS_HIDDEN, CONF_ENTRY_PROVIDER_SYNC_INTERVAL_PLAYLISTS_MOD, - CONF_ENTRY_LIBRARY_EXPORT_ADD_HIDDEN, - CONF_ENTRY_LIBRARY_EXPORT_REMOVE_HIDDEN, + CONF_ENTRY_LIBRARY_SYNC_BACK_HIDDEN, ) diff --git a/music_assistant/providers/builtin/constants.py b/music_assistant/providers/builtin/constants.py index 95e10b30..49210a90 100644 --- a/music_assistant/providers/builtin/constants.py +++ b/music_assistant/providers/builtin/constants.py @@ -9,11 +9,10 @@ from music_assistant_models.enums import ConfigEntryType, ImageType from music_assistant_models.media_items import MediaItemImage from music_assistant.constants import ( - CONF_ENTRY_LIBRARY_EXPORT_ADD, - CONF_ENTRY_LIBRARY_EXPORT_REMOVE, - CONF_ENTRY_LIBRARY_IMPORT_PLAYLISTS, - CONF_ENTRY_LIBRARY_IMPORT_RADIOS, - CONF_ENTRY_LIBRARY_IMPORT_TRACKS, + CONF_ENTRY_LIBRARY_SYNC_BACK, + CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS, + CONF_ENTRY_LIBRARY_SYNC_RADIOS, + CONF_ENTRY_LIBRARY_SYNC_TRACKS, CONF_ENTRY_PROVIDER_SYNC_INTERVAL_PLAYLISTS, CONF_ENTRY_PROVIDER_SYNC_INTERVAL_RADIOS, CONF_ENTRY_PROVIDER_SYNC_INTERVAL_TRACKS, @@ -74,32 +73,32 @@ DEFAULT_FANART = MediaItemImage( remotely_accessible=False, ) -CONF_ENTRY_LIBRARY_IMPORT_TRACKS_HIDDEN = ConfigEntry.from_dict( +CONF_ENTRY_LIBRARY_SYNC_TRACKS_HIDDEN = ConfigEntry.from_dict( { - **CONF_ENTRY_LIBRARY_IMPORT_TRACKS.to_dict(), + **CONF_ENTRY_LIBRARY_SYNC_TRACKS.to_dict(), "hidden": True, - "default_value": "import_only", + "default_value": True, } ) -CONF_ENTRY_LIBRARY_IMPORT_PLAYLISTS_HIDDEN = ConfigEntry.from_dict( +CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS_HIDDEN = ConfigEntry.from_dict( { - **CONF_ENTRY_LIBRARY_IMPORT_PLAYLISTS.to_dict(), + **CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS.to_dict(), "hidden": True, - "default_value": "import_only", + "default_value": True, } ) -CONF_ENTRY_LIBRARY_IMPORT_TRACKS_HIDDEN = ConfigEntry.from_dict( +CONF_ENTRY_LIBRARY_SYNC_TRACKS_HIDDEN = ConfigEntry.from_dict( { - **CONF_ENTRY_LIBRARY_IMPORT_TRACKS.to_dict(), + **CONF_ENTRY_LIBRARY_SYNC_TRACKS.to_dict(), "hidden": True, - "default_value": "import_only", + "default_value": True, } ) -CONF_ENTRY_LIBRARY_IMPORT_RADIOS_HIDDEN = ConfigEntry.from_dict( +CONF_ENTRY_LIBRARY_SYNC_RADIOS_HIDDEN = ConfigEntry.from_dict( { - **CONF_ENTRY_LIBRARY_IMPORT_RADIOS.to_dict(), + **CONF_ENTRY_LIBRARY_SYNC_RADIOS.to_dict(), "hidden": True, - "default_value": "import_only", + "default_value": True, } ) CONF_ENTRY_PROVIDER_SYNC_INTERVAL_PLAYLISTS_MOD = ConfigEntry.from_dict( @@ -126,17 +125,10 @@ CONF_ENTRY_PROVIDER_SYNC_INTERVAL_RADIOS_HIDDEN = ConfigEntry.from_dict( "default_value": 180, } ) -CONF_ENTRY_LIBRARY_EXPORT_ADD_HIDDEN = ConfigEntry.from_dict( +CONF_ENTRY_LIBRARY_SYNC_BACK_HIDDEN = ConfigEntry.from_dict( { - **CONF_ENTRY_LIBRARY_EXPORT_ADD.to_dict(), + **CONF_ENTRY_LIBRARY_SYNC_BACK.to_dict(), "hidden": True, - "default_value": "export_library", - } -) -CONF_ENTRY_LIBRARY_EXPORT_REMOVE_HIDDEN = ConfigEntry.from_dict( - { - **CONF_ENTRY_LIBRARY_EXPORT_REMOVE.to_dict(), - "hidden": True, - "default_value": "export_library", + "default_value": True, } ) diff --git a/music_assistant/providers/filesystem_local/__init__.py b/music_assistant/providers/filesystem_local/__init__.py index 557ce348..500cf579 100644 --- a/music_assistant/providers/filesystem_local/__init__.py +++ b/music_assistant/providers/filesystem_local/__init__.py @@ -82,10 +82,10 @@ from .constants import ( CONF_ENTRY_CONTENT_TYPE, CONF_ENTRY_CONTENT_TYPE_READ_ONLY, CONF_ENTRY_IGNORE_ALBUM_PLAYLISTS, - CONF_ENTRY_LIBRARY_IMPORT_AUDIOBOOKS, - CONF_ENTRY_LIBRARY_IMPORT_PLAYLISTS, - CONF_ENTRY_LIBRARY_IMPORT_PODCASTS, - CONF_ENTRY_LIBRARY_IMPORT_TRACKS, + CONF_ENTRY_LIBRARY_SYNC_AUDIOBOOKS, + CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS, + CONF_ENTRY_LIBRARY_SYNC_PODCASTS, + CONF_ENTRY_LIBRARY_SYNC_TRACKS, CONF_ENTRY_MISSING_ALBUM_ARTIST, CONF_ENTRY_PATH, IMAGE_EXTENSIONS, @@ -151,10 +151,10 @@ async def get_config_entries( CONF_ENTRY_PATH, CONF_ENTRY_MISSING_ALBUM_ARTIST, CONF_ENTRY_IGNORE_ALBUM_PLAYLISTS, - CONF_ENTRY_LIBRARY_IMPORT_TRACKS, - CONF_ENTRY_LIBRARY_IMPORT_PLAYLISTS, - CONF_ENTRY_LIBRARY_IMPORT_PODCASTS, - CONF_ENTRY_LIBRARY_IMPORT_AUDIOBOOKS, + CONF_ENTRY_LIBRARY_SYNC_TRACKS, + CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS, + CONF_ENTRY_LIBRARY_SYNC_PODCASTS, + CONF_ENTRY_LIBRARY_SYNC_AUDIOBOOKS, ] if instance_id is None or values is None: return (CONF_ENTRY_CONTENT_TYPE, *base_entries) @@ -318,7 +318,7 @@ class LocalFileSystemProvider(MusicProvider): ) return items - async def sync_library(self, media_type: MediaType, import_as_favorite: bool) -> None: + async def sync_library(self, media_type: MediaType) -> None: """Run library sync for this provider.""" assert self.mass.music.database start_time = time.time() @@ -370,7 +370,7 @@ class LocalFileSystemProvider(MusicProvider): try: for item in listdir(self.base_path): prev_checksum = file_checksums.get(item.relative_path) - if self._process_item(item, prev_checksum, import_as_favorite): + if self._process_item(item, prev_checksum): cur_filenames.add(item.relative_path) finally: self.sync_running = False @@ -390,9 +390,7 @@ class LocalFileSystemProvider(MusicProvider): # process orphaned albums and artists await self._process_orphaned_albums_and_artists() - def _process_item( - self, item: FileSystemItem, prev_checksum: str | None, import_as_favorite: bool - ) -> bool: + def _process_item(self, item: FileSystemItem, prev_checksum: str | None) -> bool: """Process a single item. NOT async friendly.""" try: self.logger.log(VERBOSE_LOG_LEVEL, "Processing: %s", item.relative_path) @@ -422,7 +420,7 @@ class LocalFileSystemProvider(MusicProvider): # add/update track to db # note that filesystem items are always overwriting existing info # when they are detected as changed - track.favorite = import_as_favorite + track.favorite = False # TODO: implement favorite status based on rating ? await self.mass.music.tracks.add_item_to_library( track, overwrite_existing=prev_checksum is not None ) @@ -442,7 +440,6 @@ class LocalFileSystemProvider(MusicProvider): # add/update audiobook to db # note that filesystem items are always overwriting existing info # when they are detected as changed - audiobook.favorite = import_as_favorite await self.mass.music.audiobooks.add_item_to_library( audiobook, overwrite_existing=prev_checksum is not None ) @@ -460,7 +457,6 @@ class LocalFileSystemProvider(MusicProvider): # add/update episode to db # note that filesystem items are always overwriting existing info # when they are detected as changed - episode.favorite = import_as_favorite await self.mass.music.podcasts.add_item_to_library( episode.podcast, overwrite_existing=prev_checksum is not None ) @@ -473,8 +469,7 @@ class LocalFileSystemProvider(MusicProvider): async def process_playlist() -> None: playlist = await self.get_playlist(item.relative_path) - # add/update] playlist to db - playlist.favorite = import_as_favorite + # add/update playlist to db await self.mass.music.playlists.add_item_to_library( playlist, overwrite_existing=prev_checksum is not None, diff --git a/music_assistant/providers/filesystem_local/constants.py b/music_assistant/providers/filesystem_local/constants.py index 3c54c5e6..181cf461 100644 --- a/music_assistant/providers/filesystem_local/constants.py +++ b/music_assistant/providers/filesystem_local/constants.py @@ -7,8 +7,6 @@ from typing import Final from music_assistant_models.config_entries import ConfigEntry, ConfigValueOption from music_assistant_models.enums import ConfigEntryType, ProviderFeature -from music_assistant.constants import CONF_LIBRARY_IMPORT_OPTIONS - CONF_MISSING_ALBUM_ARTIST_ACTION = "missing_album_artist_action" CONF_CONTENT_TYPE = "content_type" @@ -53,56 +51,52 @@ CONF_ENTRY_CONTENT_TYPE_READ_ONLY = ConfigEntry.from_dict( {**CONF_ENTRY_CONTENT_TYPE.to_dict(), "read_only": True} ) -CONF_ENTRY_LIBRARY_IMPORT_TRACKS = ConfigEntry( - key="library_import_tracks", - type=ConfigEntryType.STRING, +CONF_ENTRY_LIBRARY_SYNC_TRACKS = ConfigEntry( + key="library_sync_tracks", + type=ConfigEntryType.BOOLEAN, label="Import tracks/files into the Music Assistant library", description="Define how/if you want to import tracks/files from the filesystem " "into the Music Assistant Library. \nWhen not importing into the library, " "they can still be manually browsed using the Browse feature. \n\n" "Please note that by adding a Track into the Music Assistant library, " - "the track artists and album will always be imported as well (not as favorites though).", - options=CONF_LIBRARY_IMPORT_OPTIONS, - default_value="import_only", + "the track artists and album will always be imported as well.", + default_value=True, category="sync_options", depends_on=CONF_CONTENT_TYPE, depends_on_value="music", ) -CONF_ENTRY_LIBRARY_IMPORT_PLAYLISTS = ConfigEntry( - key="library_import_playlists", - type=ConfigEntryType.STRING, +CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS = ConfigEntry( + key="library_sync_playlists", + type=ConfigEntryType.BOOLEAN, label="Import playlists (m3u files) into the Music Assistant library", description="Define how/if you want to import playlists (m3u files) from the filesystem " "into the Music Assistant Library. \nWhen not importing into the library, " "they can still be manually browsed using the Browse feature.", - options=CONF_LIBRARY_IMPORT_OPTIONS, - default_value="import_only", + default_value=True, category="sync_options", depends_on=CONF_CONTENT_TYPE, depends_on_value="music", ) -CONF_ENTRY_LIBRARY_IMPORT_PODCASTS = ConfigEntry( - key="library_import_podcasts", - type=ConfigEntryType.STRING, +CONF_ENTRY_LIBRARY_SYNC_PODCASTS = ConfigEntry( + key="library_sync_podcasts", + type=ConfigEntryType.BOOLEAN, label="Import Podcasts(files) into the Music Assistant library", description="Define how/if you want to import Podcasts(files) from the filesystem " "into the Music Assistant Library. \nWhen not importing into the library, " "they can still be manually browsed using the Browse feature.", - options=CONF_LIBRARY_IMPORT_OPTIONS, - default_value="import_only", + default_value=True, category="sync_options", depends_on=CONF_CONTENT_TYPE, depends_on_value="podcasts", ) -CONF_ENTRY_LIBRARY_IMPORT_AUDIOBOOKS = ConfigEntry( - key="library_import_audiobooks", - type=ConfigEntryType.STRING, +CONF_ENTRY_LIBRARY_SYNC_AUDIOBOOKS = ConfigEntry( + key="library_sync_audiobooks", + type=ConfigEntryType.BOOLEAN, label="Import Audiobooks(files) into the Music Assistant library", description="Define how/if you want to import Audiobooks(files) from the filesystem " "into the Music Assistant Library. \nWhen not importing into the library, " "they can still be manually browsed using the Browse feature.", - options=CONF_LIBRARY_IMPORT_OPTIONS, - default_value="import_only", + default_value=True, category="sync_options", depends_on=CONF_CONTENT_TYPE, depends_on_value="audiobooks", diff --git a/music_assistant/providers/filesystem_smb/__init__.py b/music_assistant/providers/filesystem_smb/__init__.py index 62c95bdb..240a9a85 100644 --- a/music_assistant/providers/filesystem_smb/__init__.py +++ b/music_assistant/providers/filesystem_smb/__init__.py @@ -18,10 +18,10 @@ from music_assistant.providers.filesystem_local.constants import ( CONF_ENTRY_CONTENT_TYPE, CONF_ENTRY_CONTENT_TYPE_READ_ONLY, CONF_ENTRY_IGNORE_ALBUM_PLAYLISTS, - CONF_ENTRY_LIBRARY_IMPORT_AUDIOBOOKS, - CONF_ENTRY_LIBRARY_IMPORT_PLAYLISTS, - CONF_ENTRY_LIBRARY_IMPORT_PODCASTS, - CONF_ENTRY_LIBRARY_IMPORT_TRACKS, + CONF_ENTRY_LIBRARY_SYNC_AUDIOBOOKS, + CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS, + CONF_ENTRY_LIBRARY_SYNC_PODCASTS, + CONF_ENTRY_LIBRARY_SYNC_TRACKS, CONF_ENTRY_MISSING_ALBUM_ARTIST, ) @@ -127,10 +127,10 @@ async def get_config_entries( ), CONF_ENTRY_MISSING_ALBUM_ARTIST, CONF_ENTRY_IGNORE_ALBUM_PLAYLISTS, - CONF_ENTRY_LIBRARY_IMPORT_TRACKS, - CONF_ENTRY_LIBRARY_IMPORT_PLAYLISTS, - CONF_ENTRY_LIBRARY_IMPORT_PODCASTS, - CONF_ENTRY_LIBRARY_IMPORT_AUDIOBOOKS, + CONF_ENTRY_LIBRARY_SYNC_TRACKS, + CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS, + CONF_ENTRY_LIBRARY_SYNC_PODCASTS, + CONF_ENTRY_LIBRARY_SYNC_AUDIOBOOKS, ) if instance_id is None or values is None: diff --git a/music_assistant/providers/radiobrowser/__init__.py b/music_assistant/providers/radiobrowser/__init__.py index 4d2cca68..44d9bd30 100644 --- a/music_assistant/providers/radiobrowser/__init__.py +++ b/music_assistant/providers/radiobrowser/__init__.py @@ -31,9 +31,8 @@ from music_assistant_models.streamdetails import StreamDetails from radios import FilterBy, Order, RadioBrowser, RadioBrowserError, Station from music_assistant.constants import ( - CONF_ENTRY_LIBRARY_EXPORT_ADD, - CONF_ENTRY_LIBRARY_EXPORT_REMOVE, - CONF_ENTRY_LIBRARY_IMPORT_RADIOS, + CONF_ENTRY_LIBRARY_SYNC_BACK, + CONF_ENTRY_LIBRARY_SYNC_RADIOS, CONF_ENTRY_PROVIDER_SYNC_INTERVAL_RADIOS, ) from music_assistant.controllers.cache import use_cache @@ -55,9 +54,9 @@ if TYPE_CHECKING: CONF_STORED_RADIOS = "stored_radios" -CONF_ENTRY_LIBRARY_IMPORT_RADIOS_HIDDEN = ConfigEntry.from_dict( +CONF_ENTRY_LIBRARY_SYNC_RADIOS_HIDDEN = ConfigEntry.from_dict( { - **CONF_ENTRY_LIBRARY_IMPORT_RADIOS.to_dict(), + **CONF_ENTRY_LIBRARY_SYNC_RADIOS.to_dict(), "hidden": True, "default_value": "import_only", } @@ -69,18 +68,11 @@ CONF_ENTRY_PROVIDER_SYNC_INTERVAL_RADIOS_HIDDEN = ConfigEntry.from_dict( "default_value": 180, } ) -CONF_ENTRY_LIBRARY_EXPORT_ADD_HIDDEN = ConfigEntry.from_dict( +CONF_ENTRY_LIBRARY_SYNC_BACK_HIDDEN = ConfigEntry.from_dict( { - **CONF_ENTRY_LIBRARY_EXPORT_ADD.to_dict(), + **CONF_ENTRY_LIBRARY_SYNC_BACK.to_dict(), "hidden": True, - "default_value": "export_library", - } -) -CONF_ENTRY_LIBRARY_EXPORT_REMOVE_HIDDEN = ConfigEntry.from_dict( - { - **CONF_ENTRY_LIBRARY_EXPORT_REMOVE.to_dict(), - "hidden": True, - "default_value": "export_library", + "default_value": True, } ) @@ -119,10 +111,9 @@ async def get_config_entries( hidden=True, ), # hide some of the default (dynamic) entries for library management - CONF_ENTRY_LIBRARY_IMPORT_RADIOS_HIDDEN, + CONF_ENTRY_LIBRARY_SYNC_RADIOS_HIDDEN, CONF_ENTRY_PROVIDER_SYNC_INTERVAL_RADIOS_HIDDEN, - CONF_ENTRY_LIBRARY_EXPORT_ADD_HIDDEN, - CONF_ENTRY_LIBRARY_EXPORT_REMOVE_HIDDEN, + CONF_ENTRY_LIBRARY_SYNC_BACK_HIDDEN, ) diff --git a/pyproject.toml b/pyproject.toml index cae0f78e..7f40c5a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ dependencies = [ "ifaddr==0.2.0", "mashumaro==3.16", "music-assistant-frontend==2.16.5", - "music-assistant-models==1.1.61", + "music-assistant-models==1.1.62", "mutagen==1.47.0", "orjson==3.11.3", "pillow==11.3.0", diff --git a/requirements_all.txt b/requirements_all.txt index c0426365..03d420b4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -35,7 +35,7 @@ llvmlite==0.44.0 lyricsgenius==3.7.2 mashumaro==3.16 music-assistant-frontend==2.16.5 -music-assistant-models==1.1.61 +music-assistant-models==1.1.62 mutagen==1.47.0 numpy==2.2.6 orjson==3.11.3