From: Marcel van der Veldt Date: Sat, 1 Apr 2023 22:08:53 +0000 (+0200) Subject: fixes for local images resolving X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=3d9287c64e2776d1a9f61194f3eb2d88847ddde6;p=music-assistant-server.git fixes for local images resolving --- diff --git a/music_assistant/common/models/queue_item.py b/music_assistant/common/models/queue_item.py index faead81a..3b395f0e 100644 --- a/music_assistant/common/models/queue_item.py +++ b/music_assistant/common/models/queue_item.py @@ -8,10 +8,10 @@ from uuid import uuid4 from mashumaro import DataClassDictMixin from .enums import MediaType -from .media_items import ItemMapping, Radio, StreamDetails, Track +from .media_items import ItemMapping, MediaItemImage, Radio, StreamDetails, Track if TYPE_CHECKING: - from music_assistant.server import MusicAssistant + pass @dataclass @@ -26,7 +26,7 @@ class QueueItem(DataClassDictMixin): sort_index: int = 0 streamdetails: StreamDetails | None = None media_item: Track | Radio | None = None - image_url: str | None = None + image: MediaItemImage | None = None def __post_init__(self): """Set default values.""" @@ -56,7 +56,7 @@ class QueueItem(DataClassDictMixin): return MediaType.UNKNOWN @classmethod - def from_media_item(cls, queue_id: str, media_item: Track | Radio): + def from_media_item(cls, queue_id: str, media_item: Track | Radio) -> QueueItem: """Construct QueueItem from track/radio item.""" if media_item.media_type == MediaType.TRACK: artists = "/".join(x.name for x in media_item.artists) @@ -74,13 +74,16 @@ class QueueItem(DataClassDictMixin): name=name, duration=media_item.duration, media_item=media_item, - image_url=media_item.image.path if media_item.image else None, + image=get_image(media_item), ) - async def resolve_image_url(self, mass: MusicAssistant) -> None: - """Resolve Image URL for the MediaItem.""" - if self.image_url and self.image_url.startswith("http"): - return - if not self.media_item: - return - self.image_url = await mass.metadata.get_image_url_for_item(self.media_item, resolve=True) + +def get_image(media_item: Track | Radio | None) -> MediaItemImage | None: + """Find the Image for the MediaItem.""" + if not media_item: + return None + if media_item.image: + return media_item.image + if isinstance(media_item, Track) and media_item.album and getattr(media_item.album, "image"): + return media_item.album.image + return None diff --git a/music_assistant/server/controllers/metadata.py b/music_assistant/server/controllers/metadata.py index a6cda302..c96e4e3c 100755 --- a/music_assistant/server/controllers/metadata.py +++ b/music_assistant/server/controllers/metadata.py @@ -288,10 +288,7 @@ class MetaDataController: if img.provider != "url" and not resolve: continue if img.provider != "url" and resolve: - # return imageproxy url for images that need to be resolved - # the original path is double encoded - encoded_url = urllib.parse.quote(urllib.parse.quote(img.path)) - return f"{self.mass.webserver.base_url}/imageproxy?path={encoded_url}" + return self.get_image_url(img) return img.path # retry with track's album @@ -309,6 +306,15 @@ class MetaDataController: return None + def get_image_url(self, image: MediaItemImage) -> str: + """Get (proxied) URL for MediaItemImage.""" + if image.provider != "url": + # return imageproxy url for images that need to be resolved + # the original path is double encoded + encoded_url = urllib.parse.quote(urllib.parse.quote(image.path)) + return f"{self.mass.webserver.base_url}/imageproxy?path={encoded_url}" + return image.path + async def get_thumbnail( self, path: str, size: int | None = None, provider: str = "url", base64: bool = False ) -> bytes | str: diff --git a/music_assistant/server/controllers/player_queues.py b/music_assistant/server/controllers/player_queues.py index 60811d8a..e15b8111 100755 --- a/music_assistant/server/controllers/player_queues.py +++ b/music_assistant/server/controllers/player_queues.py @@ -467,8 +467,6 @@ class PlayerQueuesController: player_prov = self.mass.players.get_player_provider(queue_id) flow_mode = self.mass.config.get_player_config_value(queue.queue_id, CONF_FLOW_MODE) queue.flow_mode = flow_mode.value - # make sure that the queue item image is resolved - await queue_item.resolve_image_url(self.mass) await player_prov.cmd_play_media( queue_id, queue_item=queue_item, @@ -610,8 +608,6 @@ class PlayerQueuesController: next_item = self.get_item(queue.queue_id, next_index) if not next_item: raise QueueEmpty("No more tracks left in the queue.") - # make sure that the queue item image is resolved - await next_item.resolve_image_url(self.mass) queue.index_in_buffer = next_index # work out crossfade crossfade = queue.crossfade_enabled diff --git a/music_assistant/server/helpers/didl_lite.py b/music_assistant/server/helpers/didl_lite.py index 35e3a6fc..c35c875a 100644 --- a/music_assistant/server/helpers/didl_lite.py +++ b/music_assistant/server/helpers/didl_lite.py @@ -9,14 +9,18 @@ from music_assistant.constants import MASS_LOGO_ONLINE if TYPE_CHECKING: from music_assistant.common.models.queue_item import QueueItem + from music_assistant.server import MusicAssistant # ruff: noqa: E501 -def create_didl_metadata(url: str, queue_item: QueueItem, flow_mode: bool = False) -> str: +def create_didl_metadata( + mass: MusicAssistant, url: str, queue_item: QueueItem, flow_mode: bool = False +) -> str: """Create DIDL metadata string from url and QueueItem.""" ext = url.split(".")[-1] is_radio = queue_item.media_type != MediaType.TRACK or not queue_item.duration + image_url = mass.metadata.get_image_url(queue_item.image) if queue_item.image else "" if flow_mode: return ( @@ -34,13 +38,12 @@ def create_didl_metadata(url: str, queue_item: QueueItem, flow_mode: bool = Fals if is_radio: # radio or other non-track item - image = queue_item.image_url if queue_item.image_url else "" return ( '' f'' f"{_escape_str(queue_item.name)}" - f"{_escape_str(queue_item.image_url)}" - f"{image}" + f"{_escape_str(image_url)}" + f"{queue_item.queue_item_id}" "object.item.audioItem.audioBroadcast" f"audio/{ext}" f'{url}' @@ -68,7 +71,7 @@ def create_didl_metadata(url: str, queue_item: QueueItem, flow_mode: bool = Fals f"{queue_item.duration}" "Music Assistant" f"{queue_item.queue_item_id}" - f"{queue_item.image_url}" + f"{image_url}" f"{item_class}" f"audio/{ext}" f'{url}' diff --git a/music_assistant/server/providers/chromecast/__init__.py b/music_assistant/server/providers/chromecast/__init__.py index a4d13014..c1788f74 100644 --- a/music_assistant/server/providers/chromecast/__init__.py +++ b/music_assistant/server/providers/chromecast/__init__.py @@ -572,10 +572,10 @@ class ChromecastProvider(PlayerProvider): castplayer.status_listener = None self.castplayers.pop(castplayer.player_id, None) - @staticmethod - def _create_queue_item(queue_item: QueueItem, stream_url: str): + def _create_queue_item(self, queue_item: QueueItem, stream_url: str): """Create CC queue item from MA QueueItem.""" duration = int(queue_item.duration) if queue_item.duration else None + image_url = self.mass.metadata.get_image_url(queue_item.image) if queue_item.image else "" if queue_item.media_type == MediaType.TRACK and queue_item.media_item: stream_type = STREAM_TYPE_BUFFERED metadata = { @@ -588,14 +588,14 @@ class ChromecastProvider(PlayerProvider): if queue_item.media_item.artists else "", "title": queue_item.name, - "images": [{"url": queue_item.image_url}] if queue_item.image_url else None, + "images": [{"url": image_url}] if image_url else None, } else: stream_type = STREAM_TYPE_LIVE metadata = { "metadataType": 0, "title": queue_item.name, - "images": [{"url": queue_item.image_url}] if queue_item.image_url else None, + "images": [{"url": image_url}] if image_url else None, } return { "autoplay": True, diff --git a/music_assistant/server/providers/dlna/__init__.py b/music_assistant/server/providers/dlna/__init__.py index 3990506c..44e0b55d 100644 --- a/music_assistant/server/providers/dlna/__init__.py +++ b/music_assistant/server/providers/dlna/__init__.py @@ -268,7 +268,7 @@ class DLNAPlayerProvider(PlayerProvider): flow_mode=flow_mode, ) - didl_metadata = create_didl_metadata(url, queue_item, flow_mode) + didl_metadata = create_didl_metadata(self.mass, url, queue_item, flow_mode) await dlna_player.device.async_set_transport_uri(url, queue_item.name, didl_metadata) # Play it await dlna_player.device.async_wait_for_can_play(10) @@ -555,7 +555,7 @@ class DLNAPlayerProvider(PlayerProvider): # DLNA pre-caches pretty aggressively so do not yet start the runner auto_start_runner=False, ) - didl_metadata = create_didl_metadata(url, next_item) + didl_metadata = create_didl_metadata(self.mass, url, next_item) try: await dlna_player.device.async_set_next_transport_uri( url, next_item.name, didl_metadata diff --git a/music_assistant/server/providers/lms_cli/__init__.py b/music_assistant/server/providers/lms_cli/__init__.py index 7b0a2b34..54053d90 100644 --- a/music_assistant/server/providers/lms_cli/__init__.py +++ b/music_assistant/server/providers/lms_cli/__init__.py @@ -254,7 +254,9 @@ class LmsCli(PluginProvider): start_index : start_index + limit ] # we ignore the tags, just always send all info - return player_status_from_mass(player=player, queue=queue, queue_items=queue_items) + return player_status_from_mass( + self.mass, player=player, queue=queue, queue_items=queue_items + ) def _handle_mixer( self, diff --git a/music_assistant/server/providers/lms_cli/models.py b/music_assistant/server/providers/lms_cli/models.py index f3d1b715..4892ccf1 100644 --- a/music_assistant/server/providers/lms_cli/models.py +++ b/music_assistant/server/providers/lms_cli/models.py @@ -9,6 +9,7 @@ if TYPE_CHECKING: from music_assistant.common.models.player import Player from music_assistant.common.models.player_queue import PlayerQueue from music_assistant.common.models.queue_item import QueueItem + from music_assistant.server import MusicAssistant # ruff: noqa: UP013 @@ -121,7 +122,9 @@ PlaylistItem = TypedDict( ) -def playlist_item_from_mass(queue_item: QueueItem, index: int = 0) -> PlaylistItem: +def playlist_item_from_mass( + mass: MusicAssistant, queue_item: QueueItem, index: int = 0 +) -> PlaylistItem: """Parse PlaylistItem for the Json RPC interface from MA QueueItem.""" if queue_item.media_item and queue_item.media_type == MediaType.TRACK: artist = queue_item.media_item.artists[0].name if queue_item.media_item.artists else "" @@ -138,6 +141,7 @@ def playlist_item_from_mass(queue_item: QueueItem, index: int = 0) -> PlaylistIt artist = "" album = "" title = queue_item.name + image_url = mass.metadata.get_image_url(queue_item.image) if queue_item.image else "" return { "playlist index": index, "id": queue_item.queue_item_id, @@ -147,7 +151,7 @@ def playlist_item_from_mass(queue_item: QueueItem, index: int = 0) -> PlaylistIt "genre": "", "remote": 0, "remote_title": queue_item.streamdetails.stream_title if queue_item.streamdetails else "", - "artwork_url": queue_item.image_url or "", + "artwork_url": image_url, "bitrate": "", "duration": queue_item.duration or 0, "coverid": "-94099753136392", @@ -187,7 +191,7 @@ PlayerStatusResponse = TypedDict( def player_status_from_mass( - player: Player, queue: PlayerQueue, queue_items: list[QueueItem] + mass: MusicAssistant, player: Player, queue: PlayerQueue, queue_items: list[QueueItem] ) -> PlayerStatusResponse: """Parse PlayerStatusResponse for the Json RPC interface from MA info.""" return { @@ -217,7 +221,7 @@ def player_status_from_mass( "rate": 1, "playlist_tracks": queue.items, "playlist_loop": [ - playlist_item_from_mass(item, queue.current_index + index) + playlist_item_from_mass(mass, item, queue.current_index + index) for index, item in enumerate(queue_items) ], } diff --git a/music_assistant/server/providers/sonos/__init__.py b/music_assistant/server/providers/sonos/__init__.py index 5dab9d33..6e60f26d 100644 --- a/music_assistant/server/providers/sonos/__init__.py +++ b/music_assistant/server/providers/sonos/__init__.py @@ -292,7 +292,7 @@ class SonosPlayerProvider(PlayerProvider): if radio_mode: sonos_player.radio_mode_started = time.time() url = url.replace("http", "x-rincon-mp3radio") - metadata = create_didl_metadata(url, queue_item, flow_mode) + metadata = create_didl_metadata(self.mass, url, queue_item, flow_mode) # sonos does multiple get requests if no duration is known # our stream engine does not like that, hence the workaround self.mass.streams.workaround_players.add(sonos_player.player_id) @@ -563,7 +563,7 @@ class SonosPlayerProvider(PlayerProvider): flow_mode: bool = False, ) -> None: """Enqueue a queue item to the Sonos player Queue.""" - metadata = create_didl_metadata(url, queue_item, flow_mode) + metadata = create_didl_metadata(self.mass, url, queue_item, flow_mode) await asyncio.to_thread( sonos_player.soco.avTransport.AddURIToQueue, [