fixes for local images resolving
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 1 Apr 2023 22:08:53 +0000 (00:08 +0200)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 1 Apr 2023 22:08:53 +0000 (00:08 +0200)
music_assistant/common/models/queue_item.py
music_assistant/server/controllers/metadata.py
music_assistant/server/controllers/player_queues.py
music_assistant/server/helpers/didl_lite.py
music_assistant/server/providers/chromecast/__init__.py
music_assistant/server/providers/dlna/__init__.py
music_assistant/server/providers/lms_cli/__init__.py
music_assistant/server/providers/lms_cli/models.py
music_assistant/server/providers/sonos/__init__.py

index faead81a32a8ae59cbce0885ad084c016d05826f..3b395f0e2ef86c75fa1ffdf13816b7636ee1b880 100644 (file)
@@ -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
index a6cda302c90be84d97cfa8420ceb3512ad8b5c39..c96e4e3c27efad7ca483bc0df05f7174020bfa24 100755 (executable)
@@ -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:
index 60811d8a960fe58bf87a0af6c00ec381336144c5..e15b811124abb810811f3bd54e68ff3f01e1a717 100755 (executable)
@@ -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
index 35e3a6fcce965ce70b228ffd982f7f99bf3b9ffe..c35c875a7fdcad244344aaf56fdaf403ba1d30cd 100644 (file)
@@ -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 (
             '<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:dlna="urn:schemas-dlna-org:metadata-1-0/">'
             f'<item id="{queue_item.queue_item_id}" parentID="0" restricted="1">'
             f"<dc:title>{_escape_str(queue_item.name)}</dc:title>"
-            f"<upnp:albumArtURI>{_escape_str(queue_item.image_url)}</upnp:albumArtURI>"
-            f"<dc:queueItemId>{image}</dc:queueItemId>"
+            f"<upnp:albumArtURI>{_escape_str(image_url)}</upnp:albumArtURI>"
+            f"<dc:queueItemId>{queue_item.queue_item_id}</dc:queueItemId>"
             "<upnp:class>object.item.audioItem.audioBroadcast</upnp:class>"
             f"<upnp:mimeType>audio/{ext}</upnp:mimeType>"
             f'<res protocolInfo="http-get:*:audio/{ext}:DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=0d500000000000000000000000000000">{url}</res>'
@@ -68,7 +71,7 @@ def create_didl_metadata(url: str, queue_item: QueueItem, flow_mode: bool = Fals
         f"<upnp:duration>{queue_item.duration}</upnp:duration>"
         "<upnp:playlistTitle>Music Assistant</upnp:playlistTitle>"
         f"<dc:queueItemId>{queue_item.queue_item_id}</dc:queueItemId>"
-        f"<upnp:albumArtURI>{queue_item.image_url}</upnp:albumArtURI>"
+        f"<upnp:albumArtURI>{image_url}</upnp:albumArtURI>"
         f"<upnp:class>{item_class}</upnp:class>"
         f"<upnp:mimeType>audio/{ext}</upnp:mimeType>"
         f'<res duration="{duration_str}" protocolInfo="http-get:*:audio/{ext}:DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=0d500000000000000000000000000000">{url}</res>'
index a4d130143e6723806a466672f0223834efd77fb8..c1788f749ed2813fea36a911e731f6096907f131 100644 (file)
@@ -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,
index 3990506c229f606f8aa36e836a74b78bdecfa102..44e0b55d9a3f5870b155295ecd6990ec70b05226 100644 (file)
@@ -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
index 7b0a2b34418ad2519ecb86dd4e2a9f97e83b0f84..54053d90180d77afa453e0c2e56d382571501bbc 100644 (file)
@@ -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,
index f3d1b715d5fb54771d93db5967976bde38da90d6..4892ccf185d6da6acfcd6ac866c5659c3d07bea3 100644 (file)
@@ -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)
         ],
     }
index 5dab9d33775e81cde5adb242b86913e95a815a99..6e60f26d3990c7fd6d0a8cd70622d53a45d8c0a0 100644 (file)
@@ -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,
             [