from music_assistant.helpers.database import TABLE_ALBUMS, TABLE_TRACKS
from music_assistant.helpers.json import json_serializer
from music_assistant.helpers.tags import FALLBACK_ARTIST
-from music_assistant.models.enums import ProviderType
+from music_assistant.models.enums import MusicProviderFeature, ProviderType
from music_assistant.models.media_controller import MediaControllerBase
from music_assistant.models.media_items import (
Album,
for provider in self.mass.music.providers:
if provider.type in cur_prov_types:
continue
- if MediaType.ALBUM not in provider.supported_mediatypes:
+ if MusicProviderFeature.SEARCH not in provider.supported_features:
continue
if await find_prov_match(provider):
cur_prov_types.add(provider.type)
from music_assistant.helpers.database import TABLE_ALBUMS, TABLE_ARTISTS, TABLE_TRACKS
from music_assistant.helpers.json import json_serializer
-from music_assistant.models.enums import ProviderType
+from music_assistant.models.enums import MusicProviderFeature, ProviderType
from music_assistant.models.media_controller import MediaControllerBase
from music_assistant.models.media_items import (
Album,
for provider in self.mass.music.providers:
if provider.type in cur_prov_types:
continue
- if MediaType.ARTIST not in provider.supported_mediatypes:
+ if MusicProviderFeature.SEARCH not in provider.supported_features:
continue
if await self._match(db_artist, provider):
cur_prov_types.add(provider.type)
from music_assistant.helpers.compare import compare_artists, compare_track
from music_assistant.helpers.database import TABLE_TRACKS
from music_assistant.helpers.json import json_serializer
-from music_assistant.models.enums import MediaType, ProviderType
+from music_assistant.models.enums import MediaType, MusicProviderFeature, ProviderType
from music_assistant.models.media_controller import MediaControllerBase
from music_assistant.models.media_items import (
Album,
# matching only works if we have a full track object
db_track = await self.get_db_item(db_track.item_id)
for provider in self.mass.music.providers:
- if MediaType.TRACK not in provider.supported_mediatypes:
+ if MusicProviderFeature.SEARCH not in provider.supported_features:
continue
self.logger.debug(
"Trying to match track %s on provider %s", db_track.name, provider.name
ERROR = "error"
+class MusicProviderFeature(Enum):
+ """Enum with features for a MusicProvider."""
+
+ # browse/explore/recommendations
+ BROWSE = "browse"
+ SEARCH = "search"
+ RECOMMENDATIONS = "recommendations"
+ # library feature per mediatype
+ LIBRARY_ARTISTS = "library_artists"
+ LIBRARY_ALBUMS = "library_albums"
+ LIBRARY_TRACKS = "library_tracks"
+ LIBRARY_PLAYLISTS = "library_playlists"
+ LIBRARY_RADIOS = "library_radios"
+ # additional library features
+ ARTIST_ALBUMS = "artist_albums"
+ ARTIST_TOPTRACKS = "artist_toptracks"
+ # library edit (=add/remove) feature per mediatype
+ LIBRARY_ARTISTS_EDIT = "library_artists_edit"
+ LIBRARY_ALBUMS_EDIT = "library_albums_edit"
+ LIBRARY_TRACKS_EDIT = "library_tracks_edit"
+ LIBRARY_PLAYLISTS_EDIT = "library_playlists_edit"
+ LIBRARY_RADIOS_EDIT = "library_radios_edit"
+ # playlist-specific features
+ PLAYLIST_TRACKS_EDIT = "playlist_tracks_edit"
+ PLAYLIST_CREATE = "playlist_create"
+
+
class ProviderType(Enum):
"""Enum with supported music providers."""
from typing import TYPE_CHECKING, Any, AsyncGenerator, Dict, List, Optional, Tuple
from music_assistant.models.config import MusicProviderConfig
-from music_assistant.models.enums import MediaType, ProviderType
+from music_assistant.models.enums import MediaType, MusicProviderFeature, ProviderType
from music_assistant.models.media_items import (
Album,
Artist,
_attr_name: str = None
_attr_type: ProviderType = None
_attr_available: bool = True
- _attr_supports_browse: bool = True
- _attr_supported_mediatypes: List[MediaType] = []
def __init__(self, mass: MusicAssistant, config: MusicProviderConfig) -> None:
"""Initialize MusicProvider."""
self.logger = mass.logger
self.cache = mass.cache
+ @property
+ def supported_features(self) -> Tuple[MusicProviderFeature]:
+ """Return the features supported by this MusicProvider."""
+ return tuple()
+
@abstractmethod
async def setup(self) -> bool:
"""
"""Return boolean if this provider is available/initialized."""
return self._attr_available
- @property
- def supports_browse(self) -> bool:
- """Return boolean if this provider supports browsing."""
- return self._attr_supports_browse
-
- @property
- def supported_mediatypes(self) -> List[MediaType]:
- """Return MediaTypes the provider supports."""
- return self._attr_supported_mediatypes
-
async def search(
self, search_query: str, media_types=Optional[List[MediaType]], limit: int = 5
) -> List[MediaItemType]:
:param media_types: A list of media_types to include. All types if None.
:param limit: Number of items to return in the search (per type).
"""
- raise NotImplementedError
+ if MusicProviderFeature.SEARCH in self.supported_features:
+ raise NotImplementedError
async def get_library_artists(self) -> AsyncGenerator[Artist, None]:
"""Retrieve library artists from the provider."""
- if MediaType.ARTIST in self.supported_mediatypes:
+ if MusicProviderFeature.LIBRARY_ARTISTS in self.supported_features:
raise NotImplementedError
async def get_library_albums(self) -> AsyncGenerator[Album, None]:
"""Retrieve library albums from the provider."""
- if MediaType.ALBUM in self.supported_mediatypes:
+ if MusicProviderFeature.LIBRARY_ALBUMS in self.supported_features:
raise NotImplementedError
async def get_library_tracks(self) -> AsyncGenerator[Track, None]:
"""Retrieve library tracks from the provider."""
- if MediaType.TRACK in self.supported_mediatypes:
+ if MusicProviderFeature.LIBRARY_TRACKS in self.supported_features:
raise NotImplementedError
async def get_library_playlists(self) -> AsyncGenerator[Playlist, None]:
"""Retrieve library/subscribed playlists from the provider."""
- if MediaType.PLAYLIST in self.supported_mediatypes:
+ if MusicProviderFeature.LIBRARY_PLAYLISTS in self.supported_features:
raise NotImplementedError
async def get_library_radios(self) -> AsyncGenerator[Radio, None]:
"""Retrieve library/subscribed radio stations from the provider."""
- if MediaType.RADIO in self.supported_mediatypes:
+ if MusicProviderFeature.LIBRARY_RADIOS in self.supported_features:
raise NotImplementedError
async def get_artist(self, prov_artist_id: str) -> Artist:
async def get_artist_albums(self, prov_artist_id: str) -> List[Album]:
"""Get a list of all albums for the given artist."""
- if MediaType.ALBUM in self.supported_mediatypes:
+ if MusicProviderFeature.ARTIST_ALBUMS in self.supported_features:
raise NotImplementedError
return []
async def get_artist_toptracks(self, prov_artist_id: str) -> List[Track]:
"""Get a list of most popular tracks for the given artist."""
- if MediaType.TRACK in self.supported_mediatypes:
+ if MusicProviderFeature.ARTIST_TOPTRACKS in self.supported_features:
raise NotImplementedError
return []
async def get_radio(self, prov_radio_id: str) -> Radio:
"""Get full radio details by id."""
- if MediaType.RADIO in self.supported_mediatypes:
- raise NotImplementedError
+ raise NotImplementedError
async def get_album_tracks(self, prov_album_id: str) -> List[Track]:
"""Get album tracks for given album id."""
- if MediaType.ALBUM in self.supported_mediatypes:
- raise NotImplementedError
- return []
+ raise NotImplementedError
async def get_playlist_tracks(self, prov_playlist_id: str) -> List[Track]:
"""Get all playlist tracks for given playlist id."""
- if MediaType.PLAYLIST in self.supported_mediatypes:
- raise NotImplementedError
- return []
+ raise NotImplementedError
async def library_add(self, prov_item_id: str, media_type: MediaType) -> bool:
"""Add item to provider's library. Return true on succes."""
- return True
+ if (
+ media_type == MediaType.ARTIST
+ and MusicProviderFeature.LIBRARY_ARTISTS_EDIT in self.supported_features
+ ):
+ raise NotImplementedError
+ if (
+ media_type == MediaType.ALBUM
+ and MusicProviderFeature.LIBRARY_ALBUMS_EDIT in self.supported_features
+ ):
+ raise NotImplementedError
+ if (
+ media_type == MediaType.TRACK
+ and MusicProviderFeature.LIBRARY_TRACKS_EDIT in self.supported_features
+ ):
+ raise NotImplementedError
+ if (
+ media_type == MediaType.PLAYLIST
+ and MusicProviderFeature.LIBRARY_PLAYLISTS_EDIT in self.supported_features
+ ):
+ raise NotImplementedError
+ if (
+ media_type == MediaType.RADIO
+ and MusicProviderFeature.LIBRARY_RADIOS_EDIT in self.supported_features
+ ):
+ raise NotImplementedError
+ self.logger.info(
+ "Provider %s does not support library edit, "
+ "the action will only be performed in the local database.",
+ self.type.value,
+ )
async def library_remove(self, prov_item_id: str, media_type: MediaType) -> bool:
"""Remove item from provider's library. Return true on succes."""
- return True
+ if (
+ media_type == MediaType.ARTIST
+ and MusicProviderFeature.LIBRARY_ARTISTS_EDIT in self.supported_features
+ ):
+ raise NotImplementedError
+ if (
+ media_type == MediaType.ALBUM
+ and MusicProviderFeature.LIBRARY_ALBUMS_EDIT in self.supported_features
+ ):
+ raise NotImplementedError
+ if (
+ media_type == MediaType.TRACK
+ and MusicProviderFeature.LIBRARY_TRACKS_EDIT in self.supported_features
+ ):
+ raise NotImplementedError
+ if (
+ media_type == MediaType.PLAYLIST
+ and MusicProviderFeature.LIBRARY_PLAYLISTS_EDIT in self.supported_features
+ ):
+ raise NotImplementedError
+ if (
+ media_type == MediaType.RADIO
+ and MusicProviderFeature.LIBRARY_RADIOS_EDIT in self.supported_features
+ ):
+ raise NotImplementedError
+ self.logger.info(
+ "Provider %s does not support library edit, "
+ "the action will only be performed in the local database.",
+ self.type.value,
+ )
async def add_playlist_tracks(
self, prov_playlist_id: str, prov_track_ids: List[str]
) -> None:
"""Add track(s) to playlist."""
- if MediaType.PLAYLIST in self.supported_mediatypes:
+ if MusicProviderFeature.PLAYLIST_TRACKS_EDIT in self.supported_features:
raise NotImplementedError
async def remove_playlist_tracks(
self, prov_playlist_id: str, prov_track_ids: List[str]
) -> None:
"""Remove track(s) from playlist."""
- if MediaType.PLAYLIST in self.supported_mediatypes:
+ if MusicProviderFeature.PLAYLIST_TRACKS_EDIT in self.supported_features:
raise NotImplementedError
async def get_stream_details(self, item_id: str) -> StreamDetails | None:
:param path: The path to browse, (e.g. artists) or None for root level.
"""
+ if MusicProviderFeature.BROWSE not in self.supported_features:
+ # we may NOT use the default implementation if the browser does not support browse
+ raise NotImplementedError
# this reference implementation can be overridden with provider specific approach
if not path:
# return main listing
root_items = []
- if MediaType.ARTIST in self.supported_mediatypes:
+ if MusicProviderFeature.LIBRARY_ARTISTS in self.supported_features:
root_items.append(
BrowseFolder(
item_id="artists",
uri=f"{self.type.value}://artists",
)
)
- if MediaType.ALBUM in self.supported_mediatypes:
+ if MusicProviderFeature.LIBRARY_ALBUMS in self.supported_features:
root_items.append(
BrowseFolder(
item_id="albums",
uri=f"{self.type.value}://albums",
)
)
- if MediaType.TRACK in self.supported_mediatypes:
+ if MusicProviderFeature.LIBRARY_TRACKS in self.supported_features:
root_items.append(
BrowseFolder(
item_id="tracks",
uri=f"{self.type.value}://tracks",
)
)
- if MediaType.PLAYLIST in self.supported_mediatypes:
+ if MusicProviderFeature.LIBRARY_PLAYLISTS in self.supported_features:
root_items.append(
BrowseFolder(
item_id="playlists",
uri=f"{self.type.value}://playlists",
)
)
- if MediaType.RADIO in self.supported_mediatypes:
+ if MusicProviderFeature.LIBRARY_RADIOS in self.supported_features:
root_items.append(
BrowseFolder(
item_id="radios",
if path == "playlists":
return [x async for x in self.get_library_playlists()]
- @abstractmethod
async def recommendations(self) -> List[BrowseFolder]:
"""
Get this provider's recommendations.
Returns a list of BrowseFolder items with (max 25) mediaitems in the items attribute.
"""
- return []
+ if MusicProviderFeature.RECOMMENDATIONS in self.supported_features:
+ raise NotImplementedError
async def sync_library(
self, media_types: Optional[Tuple[MediaType]] = None
# this logic is aimed at streaming/online providers,
# which all have more or less the same structure.
# filesystem implementation(s) just override this.
- for media_type in self.supported_mediatypes:
- if media_types is not None and media_type not in media_types:
+ if media_types is None:
+ media_types = (x for x in MediaType)
+ for media_type in media_types:
+ if not self.library_supported(media_type):
continue
self.logger.debug("Start sync of %s items.", media_type.value)
controller = self.mass.music.get_controller(media_type)
"type": self.type.value,
"name": self.name,
"id": self.id,
- "supported_mediatypes": [x.value for x in self.supported_mediatypes],
+ "supported_features": [x.value for x in self.supported_features],
}
+ def library_supported(self, media_type: MediaType) -> bool:
+ """Return if Library is upported for given MediaType on this provider."""
+ if media_type == MediaType.ARTIST:
+ return MusicProviderFeature.LIBRARY_ARTISTS in self.supported_features
+ if media_type == MediaType.ALBUM:
+ return MusicProviderFeature.LIBRARY_ALBUMS in self.supported_features
+ if media_type == MediaType.TRACK:
+ return MusicProviderFeature.LIBRARY_TRACKS in self.supported_features
+ if media_type == MediaType.PLAYLIST:
+ return MusicProviderFeature.LIBRARY_PLAYLISTS in self.supported_features
+ if media_type == MediaType.RADIO:
+ return MusicProviderFeature.LIBRARY_RADIOS in self.supported_features
+
def _get_library_gen(self, media_type: MediaType) -> AsyncGenerator[MediaItemType]:
"""Return library generator for given media_type."""
if media_type == MediaType.ARTIST:
from music_assistant.helpers.compare import compare_strings
from music_assistant.helpers.tags import FALLBACK_ARTIST, parse_tags, split_items
from music_assistant.helpers.util import create_safe_string, parse_title_and_version
-from music_assistant.models.enums import ProviderType
+from music_assistant.models.enums import MusicProviderFeature, ProviderType
from music_assistant.models.errors import MediaNotFoundError, MusicAssistantError
from music_assistant.models.media_items import (
Album,
_attr_name = "Filesystem"
_attr_type = ProviderType.FILESYSTEM_LOCAL
- _attr_supported_mediatypes = [
- MediaType.TRACK,
- MediaType.PLAYLIST,
- MediaType.ARTIST,
- MediaType.ALBUM,
- ]
+
+ @property
+ def supported_features(self) -> Tuple[MusicProviderFeature]:
+ """Return the features supported by this MusicProvider."""
+ return (
+ MusicProviderFeature.LIBRARY_ARTISTS,
+ MusicProviderFeature.LIBRARY_ALBUMS,
+ MusicProviderFeature.LIBRARY_TRACKS,
+ MusicProviderFeature.LIBRARY_PLAYLISTS,
+ MusicProviderFeature.LIBRARY_RADIOS,
+ MusicProviderFeature.LIBRARY_ARTISTS_EDIT,
+ MusicProviderFeature.LIBRARY_ALBUMS_EDIT,
+ MusicProviderFeature.LIBRARY_PLAYLISTS_EDIT,
+ MusicProviderFeature.LIBRARY_RADIOS_EDIT,
+ MusicProviderFeature.LIBRARY_TRACKS_EDIT,
+ MusicProviderFeature.PLAYLIST_TRACKS_EDIT,
+ MusicProviderFeature.BROWSE,
+ MusicProviderFeature.SEARCH,
+ MusicProviderFeature.ARTIST_ALBUMS,
+ MusicProviderFeature.ARTIST_TOPTRACKS,
+ )
async def setup(self) -> bool:
"""Handle async initialization of the provider."""
import hashlib
import time
from json import JSONDecodeError
-from typing import AsyncGenerator, List, Optional
+from typing import AsyncGenerator, List, Optional, Tuple
import aiohttp
from asyncio_throttle import Throttler
app_var,
)
from music_assistant.helpers.util import parse_title_and_version, try_parse_int
-from music_assistant.models.enums import ProviderType
+from music_assistant.models.enums import MusicProviderFeature, ProviderType
from music_assistant.models.errors import LoginFailed, MediaNotFoundError
from music_assistant.models.media_items import (
Album,
_attr_type = ProviderType.QOBUZ
_attr_name = "Qobuz"
- _attr_supported_mediatypes = [
- MediaType.ARTIST,
- MediaType.ALBUM,
- MediaType.TRACK,
- MediaType.PLAYLIST,
- ]
_user_auth_info = None
_throttler = Throttler(rate_limit=4, period=1)
+ @property
+ def supported_features(self) -> Tuple[MusicProviderFeature]:
+ """Return the features supported by this MusicProvider."""
+ return (
+ MusicProviderFeature.LIBRARY_ARTISTS,
+ MusicProviderFeature.LIBRARY_ALBUMS,
+ MusicProviderFeature.LIBRARY_TRACKS,
+ MusicProviderFeature.LIBRARY_PLAYLISTS,
+ MusicProviderFeature.LIBRARY_RADIOS,
+ MusicProviderFeature.LIBRARY_ARTISTS_EDIT,
+ MusicProviderFeature.LIBRARY_ALBUMS_EDIT,
+ MusicProviderFeature.LIBRARY_PLAYLISTS_EDIT,
+ MusicProviderFeature.LIBRARY_RADIOS_EDIT,
+ MusicProviderFeature.LIBRARY_TRACKS_EDIT,
+ MusicProviderFeature.PLAYLIST_TRACKS_EDIT,
+ MusicProviderFeature.BROWSE,
+ MusicProviderFeature.SEARCH,
+ MusicProviderFeature.ARTIST_ALBUMS,
+ MusicProviderFeature.ARTIST_TOPTRACKS,
+ )
+
async def setup(self) -> bool:
"""Handle async initialization of the provider."""
if not self.config.enabled:
import time
from json.decoder import JSONDecodeError
from tempfile import gettempdir
-from typing import AsyncGenerator, List, Optional
+from typing import AsyncGenerator, List, Optional, Tuple
import aiohttp
from asyncio_throttle import Throttler
)
from music_assistant.helpers.process import AsyncProcess
from music_assistant.helpers.util import parse_title_and_version
-from music_assistant.models.enums import ProviderType
+from music_assistant.models.enums import MusicProviderFeature, ProviderType
from music_assistant.models.errors import LoginFailed, MediaNotFoundError
from music_assistant.models.media_items import (
Album,
_attr_type = ProviderType.SPOTIFY
_attr_name = "Spotify"
- _attr_supported_mediatypes = [
- MediaType.ARTIST,
- MediaType.ALBUM,
- MediaType.TRACK,
- MediaType.PLAYLIST
- # TODO: Return spotify radio
- ]
_auth_token = None
_sp_user = None
_librespot_bin = None
_cache_dir = CACHE_DIR
_ap_workaround = False
+ @property
+ def supported_features(self) -> Tuple[MusicProviderFeature]:
+ """Return the features supported by this MusicProvider."""
+ return (
+ MusicProviderFeature.LIBRARY_ARTISTS,
+ MusicProviderFeature.LIBRARY_ALBUMS,
+ MusicProviderFeature.LIBRARY_TRACKS,
+ MusicProviderFeature.LIBRARY_PLAYLISTS,
+ MusicProviderFeature.LIBRARY_RADIOS,
+ MusicProviderFeature.LIBRARY_ARTISTS_EDIT,
+ MusicProviderFeature.LIBRARY_ALBUMS_EDIT,
+ MusicProviderFeature.LIBRARY_PLAYLISTS_EDIT,
+ MusicProviderFeature.LIBRARY_RADIOS_EDIT,
+ MusicProviderFeature.LIBRARY_TRACKS_EDIT,
+ MusicProviderFeature.PLAYLIST_TRACKS_EDIT,
+ MusicProviderFeature.BROWSE,
+ MusicProviderFeature.SEARCH,
+ MusicProviderFeature.ARTIST_ALBUMS,
+ MusicProviderFeature.ARTIST_TOPTRACKS,
+ )
+
async def setup(self) -> bool:
"""Handle async initialization of the provider."""
if not self.config.enabled:
from __future__ import annotations
from time import time
-from typing import AsyncGenerator, List, Optional
+from typing import AsyncGenerator, List, Optional, Tuple
from asyncio_throttle import Throttler
from music_assistant.helpers.audio import get_radio_stream
from music_assistant.helpers.playlists import fetch_playlist
from music_assistant.helpers.util import create_sort_name
-from music_assistant.models.enums import ProviderType
+from music_assistant.models.enums import MusicProviderFeature, ProviderType
from music_assistant.models.errors import LoginFailed, MediaNotFoundError
from music_assistant.models.media_items import (
ContentType,
ImageType,
MediaItemImage,
MediaItemProviderId,
- MediaItemType,
MediaQuality,
MediaType,
Radio,
_attr_type = ProviderType.TUNEIN
_attr_name = "Tune-in Radio"
- _attr_supports_browse: bool = False
- _attr_supported_mediatypes = [MediaType.RADIO]
_throttler = Throttler(rate_limit=1, period=1)
+ @property
+ def supported_features(self) -> Tuple[MusicProviderFeature]:
+ """Return the features supported by this MusicProvider."""
+ return (
+ MusicProviderFeature.LIBRARY_RADIOS,
+ MusicProviderFeature.BROWSE,
+ )
+
async def setup(self) -> bool:
"""Handle async initialization of the provider."""
if not self.config.enabled:
)
return True
- async def search(
- self, search_query: str, media_types=Optional[List[MediaType]], limit: int = 5
- ) -> List[MediaItemType]:
- """
- Perform search on musicprovider.
-
- :param search_query: Search query.
- :param media_types: A list of media_types to include. All types if None.
- :param limit: Number of items to return in the search (per type).
- """
- # TODO: search for radio stations
- return []
-
async def get_library_radios(self) -> AsyncGenerator[Radio, None]:
"""Retrieve library/subscribed radio stations from the provider."""
from __future__ import annotations
import os
-from typing import AsyncGenerator, List, Optional, Tuple
+from typing import AsyncGenerator, Tuple
from music_assistant.helpers.audio import (
get_file_stream,
ImageType,
MediaQuality,
MediaType,
+ MusicProviderFeature,
ProviderType,
)
from music_assistant.models.media_items import (
_attr_name: str = "URL"
_attr_type: ProviderType = ProviderType.URL
_attr_available: bool = True
- _attr_supports_browse: bool = False
- _attr_supported_mediatypes: List[MediaType] = []
_full_url = {}
+ @property
+ def supported_features(self) -> Tuple[MusicProviderFeature]:
+ """Return the features supported by this MusicProvider."""
+ # return empty tuple because we do not really support features directly here
+ return tuple()
+
async def setup(self) -> bool:
"""
Handle async initialization of the provider.
await self.mass.cache.set(cache_key, media_info.raw)
return (item_id, url, media_info)
- async def search(
- self, search_query: str, media_types=Optional[List[MediaType]], limit: int = 5
- ) -> List[MediaItemType]:
- """Perform search on musicprovider."""
- return []
-
async def get_stream_details(self, item_id: str) -> StreamDetails | None:
"""Get streamdetails for a track/radio."""
item_id, url, media_info = await self._get_media_info(item_id)
import re
from operator import itemgetter
from time import time
-from typing import AsyncGenerator, Dict, List, Optional
+from typing import AsyncGenerator, Dict, List, Optional, Tuple
from urllib.parse import unquote
import pytube
import ytmusicapi
-from music_assistant.models.enums import ProviderType
+from music_assistant.models.enums import MusicProviderFeature, ProviderType
from music_assistant.models.errors import (
InvalidDataError,
LoginFailed,
_attr_type = ProviderType.YTMUSIC
_attr_name = "Youtube Music"
- _attr_supported_mediatypes = [
- MediaType.ARTIST,
- MediaType.ALBUM,
- MediaType.TRACK,
- MediaType.PLAYLIST,
- ]
_headers = None
_context = None
_cookies = None
_signature_timestamp = 0
_cipher = None
+ @property
+ def supported_features(self) -> Tuple[MusicProviderFeature]:
+ """Return the features supported by this MusicProvider."""
+ return (
+ MusicProviderFeature.LIBRARY_ARTISTS,
+ MusicProviderFeature.LIBRARY_ALBUMS,
+ MusicProviderFeature.LIBRARY_TRACKS,
+ MusicProviderFeature.LIBRARY_PLAYLISTS,
+ MusicProviderFeature.BROWSE,
+ MusicProviderFeature.SEARCH,
+ MusicProviderFeature.ARTIST_ALBUMS,
+ MusicProviderFeature.ARTIST_TOPTRACKS,
+ )
+
async def setup(self) -> bool:
"""Set up the YTMusic provider."""
if not self.config.enabled: