Fix: use relative path for all default images and collages
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sun, 3 Nov 2024 22:53:18 +0000 (23:53 +0100)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Sun, 3 Nov 2024 22:53:18 +0000 (23:53 +0100)
music_assistant/constants.py
music_assistant/controllers/metadata.py
music_assistant/providers/builtin/__init__.py

index 57e53c01a329377792ea83b80222b148ad5ba078..381871b4f2f714456fb9cd8d2fb2387f6ec79298 100644 (file)
@@ -18,7 +18,7 @@ VARIOUS_ARTISTS_MBID: Final[str] = "89ad4ac3-39f7-470e-963a-56509c546377"
 
 
 RESOURCES_DIR: Final[pathlib.Path] = (
-    pathlib.Path(__file__).parent.resolve().joinpath("server/helpers/resources")
+    pathlib.Path(__file__).parent.resolve().joinpath("helpers/resources")
 )
 
 ANNOUNCE_ALERT_FILE: Final[str] = str(RESOURCES_DIR.joinpath("announce.mp3"))
index 1aadabe906e83717671a9134162e8a1e56c1d12e..cfa1e4c2780e565bceaf15fa065def838b5c01ea 100644 (file)
@@ -181,6 +181,18 @@ class MetaDataController(CoreController):
         # just tun the scan for missing metadata once at startup
         # TODO: allows to enable/disable this in the UI and configure interval/time
         self._missing_metadata_scan_task = self.mass.create_task(self._scan_missing_metadata())
+        # migrate old image path for collage images from absolute to relative
+        # TODO: remove this after 2.5+ release
+        old_path = f"{self.mass.storage_path}/collage_images/"
+        new_path = "/collage/"
+        query = (
+            "UPDATE playlists SET metadata = "
+            f"REPLACE (metadata, '{old_path}', '{new_path}') "
+            f"WHERE playlists.metadata LIKE '%{old_path}%'"
+        )
+        if self.mass.music.database:
+            await self.mass.music.database.execute(query)
+            await self.mass.music.database.commit()
 
     async def close(self) -> None:
         """Handle logic on server stop."""
@@ -353,6 +365,9 @@ class MetaDataController(CoreController):
         """Get/create thumbnail image for path (image url or local path)."""
         if not self.mass.get_provider(provider) and not path.startswith("http"):
             raise ProviderUnavailableError
+        if provider == "builtin" and path.startswith("/collage/"):
+            # special case for collage images
+            path = os.path.join(self._collage_images_dir, path.split("/collage/")[-1])
         thumbnail = await get_image_thumb(
             self.mass, path, size=size, provider=provider, image_format=image_format
         )
@@ -391,7 +406,7 @@ class MetaDataController(CoreController):
     async def create_collage_image(
         self,
         images: list[MediaItemImage],
-        img_path: str,
+        filename: str,
         fanart: bool = False,
     ) -> MediaItemImage | None:
         """Create collage thumb/fanart image for (in-library) playlist."""
@@ -409,12 +424,13 @@ class MetaDataController(CoreController):
             dimensions = (2500, 1750) if fanart else (1500, 1500)
             img_data = await create_collage(self.mass, images, dimensions)
             # always overwrite existing path
-            async with aiofiles.open(img_path, "wb") as _file:
+            file_path = os.path.join(self._collage_images_dir, filename)
+            async with aiofiles.open(file_path, "wb") as _file:
                 await _file.write(img_data)
             del img_data
             return MediaItemImage(
                 type=ImageType.FANART if fanart else ImageType.THUMB,
-                path=img_path,
+                path=f"/collage/{filename}",
                 provider="builtin",
                 remotely_accessible=False,
             )
@@ -641,13 +657,9 @@ class MetaDataController(CoreController):
         # thumb image
         thumb_image = next((x for x in cur_images if x.type == ImageType.THUMB), None)
         if not thumb_image or self._collage_images_dir in thumb_image.path:
-            thumb_image_path = (
-                thumb_image.path
-                if thumb_image
-                else os.path.join(self._collage_images_dir, f"{uuid4().hex}_thumb.jpg")
-            )
+            img_filename = thumb_image.path if thumb_image else f"{uuid4().hex}_thumb.jpg"
             if collage_thumb_image := await self.create_collage_image(
-                all_playlist_tracks_images, thumb_image_path
+                all_playlist_tracks_images, img_filename
             ):
                 new_images.append(collage_thumb_image)
         elif thumb_image:
@@ -656,13 +668,9 @@ class MetaDataController(CoreController):
         # fanart image
         fanart_image = next((x for x in cur_images if x.type == ImageType.FANART), None)
         if not fanart_image or self._collage_images_dir in fanart_image.path:
-            fanart_image_path = (
-                fanart_image.path
-                if fanart_image
-                else os.path.join(self._collage_images_dir, f"{uuid4().hex}_fanart.jpg")
-            )
+            img_filename = thumb_image.path if thumb_image else f"{uuid4().hex}_fanart.jpg"
             if collage_fanart_image := await self.create_collage_image(
-                all_playlist_tracks_images, fanart_image_path, fanart=True
+                all_playlist_tracks_images, img_filename, fanart=True
             ):
                 new_images.append(collage_fanart_image)
         elif fanart_image:
index 504cd1075be4bbee4ff8ece3f3873eed1d5386a7..6872d751db1b00642902009c0ec94813039b41b5 100644 (file)
@@ -39,7 +39,7 @@ from music_assistant_models.media_items import (
 )
 from music_assistant_models.streamdetails import StreamDetails
 
-from music_assistant.constants import MASS_LOGO, RESOURCES_DIR, VARIOUS_ARTISTS_FANART
+from music_assistant.constants import MASS_LOGO, VARIOUS_ARTISTS_FANART
 from music_assistant.helpers.tags import AudioTags, parse_tags
 from music_assistant.helpers.uri import parse_uri
 from music_assistant.models.music_provider import MusicProvider
@@ -84,14 +84,14 @@ COLLAGE_IMAGE_PLAYLISTS = (ALL_FAVORITE_TRACKS, RANDOM_TRACKS)
 
 DEFAULT_THUMB = MediaItemImage(
     type=ImageType.THUMB,
-    path=MASS_LOGO,
+    path="logo.png",
     provider="builtin",
     remotely_accessible=False,
 )
 
 DEFAULT_FANART = MediaItemImage(
     type=ImageType.FANART,
-    path=VARIOUS_ARTISTS_FANART,
+    path="fanart.jpg",
     provider="builtin",
     remotely_accessible=False,
 )
@@ -143,20 +143,21 @@ class BuiltinProvider(MusicProvider):
         if not await asyncio.to_thread(os.path.exists, self._playlists_dir):
             await asyncio.to_thread(os.mkdir, self._playlists_dir)
         await super().loaded_in_mass()
-        # migrate old image path
-        # TODO: remove this after 2.3+ release
-        old_path = (
-            "/usr/local/lib/python3.12/site-packages/music_assistant/server/helpers/resources"
-        )
-        new_path = str(RESOURCES_DIR)
-        query = (
-            "UPDATE playlists SET metadata = "
-            f"REPLACE (metadata, '{old_path}', '{new_path}') "
-            f"WHERE playlists.metadata LIKE '%{old_path}%'"
-        )
-        if self.mass.music.database:
-            await self.mass.music.database.execute(query)
-            await self.mass.music.database.commit()
+        # migrate old image path from absolute to relative
+        # TODO: remove this after 2.5+ release
+        for old_path in (
+            "/usr/local/lib/python3.12/site-packages/music_assistant/server/helpers/resources/",
+            "/app/venv/lib/python3.12/site-packages/music_assistant/server/helpers/resources/",
+            "/Users/marcelvanderveldt/Workdir/music-assistant/core/music_assistant/server/helpers/resources/",
+        ):
+            query = (
+                "UPDATE playlists SET metadata = "
+                f"REPLACE (metadata, '{old_path}', '') "
+                f"WHERE playlists.metadata LIKE '%{old_path}%'"
+            )
+            if self.mass.music.database:
+                await self.mass.music.database.execute(query)
+                await self.mass.music.database.commit()
 
     @property
     def is_streaming_provider(self) -> bool:
@@ -508,6 +509,19 @@ class BuiltinProvider(MusicProvider):
             )
         return media_item
 
+    async def resolve_image(self, path: str) -> str | bytes:
+        """
+        Resolve an image from an image path.
+
+        This either returns (a generator to get) raw bytes of the image or
+        a string with an http(s) URL or local path that is accessible from the server.
+        """
+        if path == "logo.png":
+            return MASS_LOGO
+        if path in ("fanart.jpg", "fallback_fanart.jpeg"):
+            return VARIOUS_ARTISTS_FANART
+        return path
+
     async def _get_media_info(self, url: str, force_refresh: bool = False) -> AudioTags:
         """Retrieve mediainfo for url."""
         cache_category = CacheCategory.MEDIA_INFO