BrowseFolder,
ItemMapping,
MediaItemType,
- MediaItemTypeOrItemMapping,
RecommendationFolder,
SearchResults,
)
return result
@api_command("music/browse")
- async def browse(self, path: str | None = None) -> list[MediaItemType]:
+ async def browse(self, path: str | None = None) -> Sequence[MediaItemType | BrowseFolder]:
"""Browse Music providers."""
if not path or path == "root":
# root level; folder per provider
- root_items: list[MediaItemType] = []
+ root_items: list[BrowseFolder] = []
for prov in self.providers:
if ProviderFeature.BROWSE not in prov.supported_features:
continue
return root_items
# provider level
- prepend_items: list[MediaItemType] = []
+ prepend_items: list[BrowseFolder] = []
provider_instance, sub_path = path.split("://", 1)
prov = self.mass.get_provider(provider_instance)
# handle regular provider listing, always add back folder first
return result
@api_command("music/item_by_uri")
- async def get_item_by_uri(self, uri: str) -> MediaItemType:
+ async def get_item_by_uri(self, uri: str) -> MediaItemType | BrowseFolder:
"""Fetch MediaItem by uri."""
media_type, provider_instance_id_or_domain, item_id = await parse_uri(uri)
return await self.get_item(
media_type: MediaType,
item_id: str,
provider_instance_id_or_domain: str,
- ) -> MediaItemType:
+ ) -> MediaItemType | BrowseFolder:
"""Get single music item by id and media type."""
if provider_instance_id_or_domain == "database":
# backwards compatibility - to remove when 2.0 stable is released
@api_command("music/favorites/add_item")
async def add_item_to_favorites(
self,
- item: str | MediaItemTypeOrItemMapping,
+ item: str | MediaItemType | ItemMapping,
) -> None:
"""Add an item to the favorites."""
if isinstance(item, str):
def _sort_search_result(
self,
search_query: str,
- items: Sequence[MediaItemTypeOrItemMapping],
- ) -> UniqueList[MediaItemTypeOrItemMapping]:
+ items: Sequence[MediaItemType | ItemMapping],
+ ) -> UniqueList[MediaItemType | ItemMapping]:
"""Sort search results on priority/preference."""
- scored_items: list[tuple[int, MediaItemTypeOrItemMapping]] = []
+ scored_items: list[tuple[int, MediaItemType | ItemMapping]] = []
# search results are already sorted by (streaming) providers on relevance
# but we prefer exact name matches and library items so we simply put those
# on top of the list.
BrowseFolder,
ItemMapping,
MediaItemType,
- MediaItemTypeOrItemMapping,
PlayableMediaItemType,
Playlist,
PodcastEpisode,
async def play_media(
self,
queue_id: str,
- media: MediaItemTypeOrItemMapping | list[MediaItemTypeOrItemMapping] | str | list[str],
+ media: MediaItemType | ItemMapping | list[MediaItemType | ItemMapping] | str | list[str],
option: QueueOption | None = None,
radio_mode: bool = False,
start_item: PlayableMediaItemType | str | None = None,
)
async def _resolve_media_items(
- self, media_item: MediaItemTypeOrItemMapping, start_item: str | None = None
+ self, media_item: MediaItemType | ItemMapping | BrowseFolder, start_item: str | None = None
) -> list[MediaItemType]:
"""Resolve/unwrap media items to enqueue."""
# resolve Itemmapping to full media item
ItemMapping,
MediaItem,
MediaItemMetadata,
- MediaItemTypeOrItemMapping,
+ MediaItemType,
Playlist,
Podcast,
Radio,
def compare_media_item(
- base_item: MediaItemTypeOrItemMapping,
- compare_item: MediaItemTypeOrItemMapping,
+ base_item: MediaItemType | ItemMapping,
+ compare_item: MediaItemType | ItemMapping,
strict: bool = True,
) -> bool | None:
"""Compare two media items and return True if they match."""
Artist,
Audiobook,
BrowseFolder,
+ ItemMapping,
MediaItemType,
- MediaItemTypeOrItemMapping,
Playlist,
Podcast,
PodcastEpisode,
return await self.get_podcast_episode(prov_item_id)
return await self.get_track(prov_item_id)
- async def browse(self, path: str) -> Sequence[MediaItemTypeOrItemMapping]: # noqa: PLR0911, PLR0915
+ async def browse(self, path: str) -> Sequence[MediaItemType | ItemMapping | BrowseFolder]: # noqa: PLR0911, PLR0915
"""Browse this provider's items.
:param path: The path to browse, (e.g. provider_id://artists).
raise KeyError(msg)
# no subpath: return main listing
- items: list[MediaItemType] = []
+ folders: list[BrowseFolder] = []
if ProviderFeature.LIBRARY_ARTISTS in self.supported_features:
- items.append(
+ folders.append(
BrowseFolder(
item_id="artists",
provider=self.instance_id,
)
)
if ProviderFeature.LIBRARY_ALBUMS in self.supported_features:
- items.append(
+ folders.append(
BrowseFolder(
item_id="albums",
provider=self.instance_id,
)
)
if ProviderFeature.LIBRARY_TRACKS in self.supported_features:
- items.append(
+ folders.append(
BrowseFolder(
item_id="tracks",
provider=self.domain,
)
)
if ProviderFeature.LIBRARY_PLAYLISTS in self.supported_features:
- items.append(
+ folders.append(
BrowseFolder(
item_id="playlists",
provider=self.instance_id,
)
)
if ProviderFeature.LIBRARY_RADIOS in self.supported_features:
- items.append(
+ folders.append(
BrowseFolder(
item_id="radios",
provider=self.instance_id,
)
)
if ProviderFeature.LIBRARY_AUDIOBOOKS in self.supported_features:
- items.append(
+ folders.append(
BrowseFolder(
item_id="audiobooks",
provider=self.instance_id,
)
)
if ProviderFeature.LIBRARY_PODCASTS in self.supported_features:
- items.append(
+ folders.append(
BrowseFolder(
item_id="podcasts",
provider=self.instance_id,
translation_key="podcasts",
)
)
- if len(items) == 1:
+ if len(folders) == 1:
# only one level, return the items directly
- return await self.browse(items[0].path)
- return items
+ return await self.browse(folders[0].path)
+ return folders
async def recommendations(self) -> list[RecommendationFolder]:
"""
Album,
Artist,
AudioFormat,
+ BrowseFolder,
+ ItemMapping,
MediaItemType,
- MediaItemTypeOrItemMapping,
Playlist,
ProviderMapping,
Radio,
# to false in a MediaItemImage object.
return path
- async def browse(self, path: str) -> Sequence[MediaItemTypeOrItemMapping]:
+ async def browse(self, path: str) -> Sequence[MediaItemType | ItemMapping | BrowseFolder]:
"""Browse this provider's items.
:param path: The path to browse, (e.g. provider_id://artists).
Audiobook,
AudioFormat,
BrowseFolder,
+ ItemMapping,
MediaItemType,
- MediaItemTypeOrItemMapping,
PodcastEpisode,
UniqueList,
)
# have multiple libraries. Instead we collect per ShelfId, and make sure, that we always get
# roughly the same amount of items per row, no matter the amount of libraries
# List of list (one list per lib) here, such that we can pick the items per lib later.
- items_by_shelf_id: dict[AbsShelfId, list[list[MediaItemType]]] = {}
+ items_by_shelf_id: dict[AbsShelfId, list[list[MediaItemType | BrowseFolder]]] = {}
all_libraries = {**self.libraries.audiobooks, **self.libraries.podcasts}
max_items_per_row = 20
# If there is only a single audiobook library, we add the folders
# from _browse_lib_audiobooks, i.e. Authors, Narrators etc.
# Podcast libs do not have filter folders, so always the root folders.
- browse_items: list[MediaItemTypeOrItemMapping] = []
+ browse_items: list[MediaItemType | BrowseFolder] = []
if len(self.libraries.audiobooks) <= 1:
browse_names = [
x.name for x in self.libraries.audiobooks.values()
self,
shelves: list[ShelfBook | ShelfPodcast | ShelfAuthors | ShelfEpisode | ShelfSeries],
library_id: str,
- items_by_shelf_id: dict[AbsShelfId, list[list[MediaItemType]]],
+ items_by_shelf_id: dict[AbsShelfId, list[list[MediaItemType | BrowseFolder]]],
) -> None:
for shelf in shelves:
media_type: MediaType
# this would be authors, currently
continue
- items: list[MediaItemType] = []
+ items: list[MediaItemType | BrowseFolder] = []
# Recently added is the _only_ case, where we get a full podcast
# We have a podcast object with only the episodes matching the
# shelf.id_ otherwise.
is_finished=fully_played,
)
- async def browse(self, path: str) -> Sequence[MediaItemTypeOrItemMapping]:
+ async def browse(self, path: str) -> Sequence[MediaItemType | ItemMapping | BrowseFolder]:
"""Browse for audiobookshelf.
Generates this view:
return await self._browse_series_books(series_id=series_id)
return []
- def _browse_root(
- self, append_mediatype_suffix: bool = True
- ) -> Sequence[MediaItemTypeOrItemMapping]:
+ def _browse_root(self, append_mediatype_suffix: bool = True) -> Sequence[BrowseFolder]:
items = []
def _get_folder(path: str, lib_id: str, lib_name: str) -> BrowseFolder:
items.append(_get_folder(path, lib_id, name))
return items
- async def _browse_lib_podcasts(self, library_id: str) -> list[MediaItemTypeOrItemMapping]:
+ async def _browse_lib_podcasts(self, library_id: str) -> list[MediaItemType]:
"""No sub categories for podcasts."""
if len(self.libraries.podcasts[library_id].item_ids) == 0:
self._log_no_helper_item_ids()
items.append(mass_item)
return sorted(items, key=lambda x: x.name)
- def _browse_lib_audiobooks(self, current_path: str) -> Sequence[MediaItemTypeOrItemMapping]:
+ def _browse_lib_audiobooks(self, current_path: str) -> Sequence[BrowseFolder]:
items = []
for item_name in AbsBrowseItemsBook:
path = current_path + "/" + ABS_BROWSE_ITEMS_TO_PATH[item_name]
)
return items
- async def _browse_authors(
- self, current_path: str, library_id: str
- ) -> Sequence[MediaItemTypeOrItemMapping]:
+ async def _browse_authors(self, current_path: str, library_id: str) -> Sequence[BrowseFolder]:
abs_authors = await self._client.get_library_authors(library_id=library_id)
items = []
for author in abs_authors:
return sorted(items, key=lambda x: x.name)
- async def _browse_narrators(
- self, current_path: str, library_id: str
- ) -> Sequence[MediaItemTypeOrItemMapping]:
+ async def _browse_narrators(self, current_path: str, library_id: str) -> Sequence[BrowseFolder]:
abs_narrators = await self._client.get_library_narrators(library_id=library_id)
items = []
for narrator in abs_narrators:
return sorted(items, key=lambda x: x.name)
- async def _browse_series(
- self, current_path: str, library_id: str
- ) -> Sequence[MediaItemTypeOrItemMapping]:
+ async def _browse_series(self, current_path: str, library_id: str) -> Sequence[BrowseFolder]:
items = []
async for response in self._client.get_library_series(library_id=library_id):
if not response.results:
async def _browse_collections(
self, current_path: str, library_id: str
- ) -> Sequence[MediaItemTypeOrItemMapping]:
+ ) -> Sequence[BrowseFolder]:
items = []
async for response in self._client.get_library_collections(library_id=library_id):
if not response.results:
)
return sorted(items, key=lambda x: x.name)
- async def _browse_books(self, library_id: str) -> Sequence[MediaItemTypeOrItemMapping]:
+ async def _browse_books(self, library_id: str) -> Sequence[MediaItemType]:
if len(self.libraries.audiobooks[library_id].item_ids) == 0:
self._log_no_helper_item_ids()
items = []
async def _browse_author_books(
self, current_path: str, author_id: str
- ) -> Sequence[MediaItemTypeOrItemMapping]:
- items: list[MediaItemTypeOrItemMapping] = []
+ ) -> Sequence[MediaItemType | BrowseFolder]:
+ items: list[MediaItemType | BrowseFolder] = []
abs_author = await self._client.get_author(
author_id=author_id, include_items=True, include_series=True
async def _browse_narrator_books(
self, library_id: str, narrator_filter_str: str
- ) -> Sequence[MediaItemTypeOrItemMapping]:
- items: list[MediaItemTypeOrItemMapping] = []
+ ) -> Sequence[MediaItemType]:
+ items: list[MediaItemType] = []
async for response in self._client.get_library_items(
library_id=library_id, filter_str=f"narrators.{narrator_filter_str}"
):
return sorted(items, key=lambda x: x.name)
- async def _browse_series_books(self, series_id: str) -> Sequence[MediaItemTypeOrItemMapping]:
+ async def _browse_series_books(self, series_id: str) -> Sequence[MediaItemType]:
items = []
abs_series = await self._client.get_series(series_id=series_id, include_progress=True)
return items
- async def _browse_collection_books(
- self, collection_id: str
- ) -> Sequence[MediaItemTypeOrItemMapping]:
+ async def _browse_collection_books(self, collection_id: str) -> Sequence[MediaItemType]:
items = []
abs_collection = await self._client.get_collection(collection_id=collection_id)
for book in abs_collection.books:
ItemMapping,
MediaItemChapter,
MediaItemImage,
- MediaItemTypeOrItemMapping,
+ MediaItemType,
Playlist,
Podcast,
PodcastEpisode,
)
return result
- async def browse(self, path: str) -> Sequence[MediaItemTypeOrItemMapping]:
+ async def browse(self, path: str) -> Sequence[MediaItemType | ItemMapping | BrowseFolder]:
"""Browse this provider's items.
:param path: The path to browse, (e.g. provid://artists).
return await self.mass.music.podcasts.library_items(provider=self.instance_id)
if self.media_content_type == "audiobooks":
return await self.mass.music.audiobooks.library_items(provider=self.instance_id)
- items: list[MediaItemTypeOrItemMapping] = []
+ items: list[MediaItemType | ItemMapping | BrowseFolder] = []
item_path = path.split("://", 1)[1]
if not item_path:
item_path = ""
return result
- async def browse(self, path: str) -> Sequence[MediaItemType]:
+ async def browse(self, path: str) -> Sequence[MediaItemType | BrowseFolder]:
"""Browse this provider's items.
:param path: The path to browse, (e.g. provid://artists).
from music_assistant_models.errors import LoginFailed, MediaNotFoundError
from music_assistant_models.media_items import (
AudioFormat,
+ BrowseFolder,
+ ItemMapping,
MediaItemImage,
MediaItemLink,
- MediaItemTypeOrItemMapping,
+ MediaItemType,
ProviderMapping,
Radio,
)
return self._current_stream_details
- async def browse(self, path: str) -> Sequence[MediaItemTypeOrItemMapping]:
+ async def browse(self, path: str) -> Sequence[MediaItemType | ItemMapping | BrowseFolder]:
"""Browse this provider's items.
:param path: The path to browse, (e.g. provider_id://artists).
Album,
Artist,
AudioFormat,
+ BrowseFolder,
ItemMapping,
MediaItemImage,
MediaItemType,
- MediaItemTypeOrItemMapping,
Playlist,
ProviderMapping,
RecommendationFolder,
item_id=item_id,
name=module_title,
provider=self.lookup_key,
- items=UniqueList[MediaItemTypeOrItemMapping](unique_items),
+ items=UniqueList[MediaItemType | ItemMapping | BrowseFolder](unique_items),
subtitle=f"From {page_name} • {len(unique_items)} items",
translation_key=item_id,
icon=get_icon_for_type(content_type),