from __future__ import annotations
from collections.abc import AsyncGenerator
+from random import randint
from typing import TYPE_CHECKING
+from music_assistant_models.config_entries import ConfigEntry
from music_assistant_models.enums import (
+ ConfigEntryType,
ContentType,
ImageType,
MediaType,
from music_assistant_models.media_items import (
Album,
Artist,
+ Audiobook,
AudioFormat,
+ Chapter,
+ Episode,
+ ItemMapping,
MediaItemImage,
MediaItemMetadata,
+ Podcast,
ProviderMapping,
Track,
UniqueList,
from music_assistant.models.music_provider import MusicProvider
if TYPE_CHECKING:
- from music_assistant_models.config_entries import ConfigEntry, ConfigValueType, ProviderConfig
+ from music_assistant_models.config_entries import ConfigValueType, ProviderConfig
from music_assistant_models.provider import ProviderManifest
from music_assistant import MusicAssistant
remotely_accessible=False,
)
+CONF_KEY_NUM_ARTISTS = "num_artists"
+CONF_KEY_NUM_ALBUMS = "num_albums"
+CONF_KEY_NUM_TRACKS = "num_tracks"
+CONF_KEY_NUM_PODCASTS = "num_podcasts"
+CONF_KEY_NUM_AUDIOBOOKS = "num_audiobooks"
+
async def setup(
mass: MusicAssistant, manifest: ProviderManifest, config: ProviderConfig
action: [optional] action key called from config entries UI.
values: the (intermediate) raw values for config entries sent with the action.
"""
- return ()
+ return (
+ ConfigEntry(
+ key=CONF_KEY_NUM_ARTISTS,
+ type=ConfigEntryType.INTEGER,
+ label="Number of (test) artists",
+ description="Number of test artists to generate",
+ default_value=5,
+ required=False,
+ ),
+ ConfigEntry(
+ key=CONF_KEY_NUM_ALBUMS,
+ type=ConfigEntryType.INTEGER,
+ label="Number of (test) albums per artist",
+ description="Number of test albums to generate per artist",
+ default_value=5,
+ required=False,
+ ),
+ ConfigEntry(
+ key=CONF_KEY_NUM_TRACKS,
+ type=ConfigEntryType.INTEGER,
+ label="Number of (test) tracks per album",
+ description="Number of test tracks to generate per artist-album",
+ default_value=20,
+ required=False,
+ ),
+ ConfigEntry(
+ key=CONF_KEY_NUM_PODCASTS,
+ type=ConfigEntryType.INTEGER,
+ label="Number of (test) podcasts",
+ description="Number of test podcasts to generate",
+ default_value=5,
+ required=False,
+ ),
+ ConfigEntry(
+ key=CONF_KEY_NUM_AUDIOBOOKS,
+ type=ConfigEntryType.INTEGER,
+ label="Number of (test) audiobooks",
+ description="Number of test audiobooks to generate",
+ default_value=5,
+ required=False,
+ ),
+ )
class TestProvider(MusicProvider):
@property
def supported_features(self) -> set[ProviderFeature]:
"""Return the features supported by this Provider."""
- return {ProviderFeature.LIBRARY_TRACKS}
+ sup_features = {ProviderFeature.BROWSE}
+ if self.config.get_value(CONF_KEY_NUM_ARTISTS):
+ sup_features.add(ProviderFeature.LIBRARY_ARTISTS)
+ if self.config.get_value(CONF_KEY_NUM_ALBUMS):
+ sup_features.add(ProviderFeature.LIBRARY_ALBUMS)
+ if self.config.get_value(CONF_KEY_NUM_TRACKS):
+ sup_features.add(ProviderFeature.LIBRARY_TRACKS)
+ if self.config.get_value(CONF_KEY_NUM_PODCASTS):
+ sup_features.add(ProviderFeature.LIBRARY_PODCASTS)
+ if self.config.get_value(CONF_KEY_NUM_AUDIOBOOKS):
+ sup_features.add(ProviderFeature.LIBRARY_AUDIOBOOKS)
+ return sup_features
async def get_track(self, prov_track_id: str) -> Track:
"""Get full track details by id."""
metadata=MediaItemMetadata(images=UniqueList([DEFAULT_THUMB])),
)
+ async def get_podcast(self, prov_podcast_id: str) -> Album:
+ """Get full podcast details by id."""
+ return Podcast(
+ item_id=prov_podcast_id,
+ provider=self.instance_id,
+ name=f"Test Podcast {prov_podcast_id}",
+ metadata=MediaItemMetadata(images=UniqueList([DEFAULT_THUMB])),
+ provider_mappings={
+ ProviderMapping(
+ item_id=prov_podcast_id,
+ provider_domain=self.domain,
+ provider_instance=self.instance_id,
+ )
+ },
+ publisher="Test Publisher",
+ )
+
+ async def get_audiobook(self, prov_audiobook_id: str) -> Audiobook:
+ """Get full audiobook details by id."""
+ return Audiobook(
+ item_id=prov_audiobook_id,
+ provider=self.instance_id,
+ name=f"Test Audiobook {prov_audiobook_id}",
+ metadata=MediaItemMetadata(images=UniqueList([DEFAULT_THUMB])),
+ provider_mappings={
+ ProviderMapping(
+ item_id=prov_audiobook_id,
+ provider_domain=self.domain,
+ provider_instance=self.instance_id,
+ )
+ },
+ publisher="Test Publisher",
+ total_chapters=10,
+ )
+
+ async def get_chapter(self, prov_chapter_id: str) -> Chapter:
+ """Get (full) audiobook chapter details by id."""
+ prov_audiobook_id, chapter_idx = prov_chapter_id.split("_", 2)
+ return Chapter(
+ item_id=prov_chapter_id,
+ provider=self.instance_id,
+ name=f"Test Chapter {prov_audiobook_id}-{prov_chapter_id}",
+ duration=5,
+ audiobook=ItemMapping(
+ item_id=prov_audiobook_id,
+ provider=self.instance_id,
+ name=f"Test Audiobook {prov_audiobook_id}",
+ media_type=MediaType.AUDIOBOOK,
+ ),
+ )
+
+ async def get_episode(self, prov_episode_id: str) -> Episode:
+ """Get (full) podcast episode details by id."""
+ prov_podcast_id, episode_idx = prov_episode_id.split("_", 2)
+ return Episode(
+ item_id=f"{prov_podcast_id}_{episode_idx}",
+ provider=self.instance_id,
+ name=f"Test Episode {prov_podcast_id}-{episode_idx}",
+ duration=5,
+ podcast=ItemMapping(
+ item_id=prov_podcast_id,
+ provider=self.instance_id,
+ name=f"Test Podcast {prov_podcast_id}",
+ media_type=MediaType.PODCAST,
+ ),
+ provider_mappings={
+ ProviderMapping(
+ item_id=f"{prov_podcast_id}_{episode_idx}",
+ provider_domain=self.domain,
+ provider_instance=self.instance_id,
+ )
+ },
+ metadata=MediaItemMetadata(images=UniqueList([DEFAULT_THUMB])),
+ episode_number=episode_idx,
+ )
+
+ async def get_library_artists(self) -> AsyncGenerator[Artist, None]:
+ """Retrieve library artists from the provider."""
+ num_artists = self.config.get_value(CONF_KEY_NUM_ARTISTS)
+ for artist_idx in range(num_artists):
+ yield await self.get_artist(str(artist_idx))
+
+ async def get_library_albums(self) -> AsyncGenerator[Album, None]:
+ """Retrieve library albums from the provider."""
+ num_artists = self.config.get_value(CONF_KEY_NUM_ARTISTS) or 5
+ num_albums = self.config.get_value(CONF_KEY_NUM_ALBUMS)
+ for artist_idx in range(num_artists):
+ for album_idx in range(num_albums):
+ album_item_id = f"{artist_idx}_{album_idx}"
+ yield await self.get_album(album_item_id)
+
async def get_library_tracks(self) -> AsyncGenerator[Track, None]:
"""Retrieve library tracks from the provider."""
- for artist_idx in range(50):
- for album_idx in range(25):
- for track_idx in range(25):
+ num_artists = self.config.get_value(CONF_KEY_NUM_ARTISTS) or 5
+ num_albums = self.config.get_value(CONF_KEY_NUM_ALBUMS) or 5
+ num_tracks = self.config.get_value(CONF_KEY_NUM_TRACKS)
+ for artist_idx in range(num_artists):
+ for album_idx in range(num_albums):
+ for track_idx in range(num_tracks):
track_item_id = f"{artist_idx}_{album_idx}_{track_idx}"
yield await self.get_track(track_item_id)
+ async def get_library_podcasts(self) -> AsyncGenerator[Track, None]:
+ """Retrieve library tracks from the provider."""
+ num_podcasts = self.config.get_value(CONF_KEY_NUM_PODCASTS)
+ for podcast_idx in range(num_podcasts):
+ yield await self.get_podcast(str(podcast_idx))
+
+ async def get_library_audiobooks(self) -> AsyncGenerator[Audiobook, None]:
+ """Retrieve library audiobooks from the provider."""
+ num_audiobooks = self.config.get_value(CONF_KEY_NUM_AUDIOBOOKS)
+ for audiobook_idx in range(num_audiobooks):
+ yield await self.get_audiobook(str(audiobook_idx))
+
+ async def get_audiobook_chapters(
+ self,
+ prov_audiobook_id: str,
+ ) -> list[Chapter]:
+ """Get all Chapters for given audiobook id."""
+ num_chapters = randint(5, 75)
+ return [
+ await self.get_chapter(f"{prov_audiobook_id}_{chapter_idx}")
+ for chapter_idx in range(num_chapters)
+ ]
+
+ async def get_podcast_episodes(
+ self,
+ prov_podcast_id: str,
+ ) -> list[Episode]:
+ """Get all Episodes for given podcast id."""
+ num_episodes = randint(5, 75)
+ return [
+ await self.get_episode(f"{prov_podcast_id}_{episode_idx}")
+ for episode_idx in range(num_episodes)
+ ]
+
async def get_stream_details(
self, item_id: str, media_type: MediaType = MediaType.TRACK
) -> StreamDetails: