From fdc942fa3e853f5cd9c47ca42b3c1fe28ee623e6 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Sun, 3 Nov 2024 23:53:18 +0100 Subject: [PATCH] Fix: use relative path for all default images and collages --- music_assistant/constants.py | 2 +- music_assistant/controllers/metadata.py | 38 +++++++++------ music_assistant/providers/builtin/__init__.py | 48 ++++++++++++------- 3 files changed, 55 insertions(+), 33 deletions(-) diff --git a/music_assistant/constants.py b/music_assistant/constants.py index 57e53c01a..381871b4f 100644 --- a/music_assistant/constants.py +++ b/music_assistant/constants.py @@ -18,7 +18,7 @@ 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")) diff --git a/music_assistant/controllers/metadata.py b/music_assistant/controllers/metadata.py index 1aadabe90..cfa1e4c27 100644 --- a/music_assistant/controllers/metadata.py +++ b/music_assistant/controllers/metadata.py @@ -181,6 +181,18 @@ async def setup(self, config: CoreConfig) -> None: # 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 @@ async def get_thumbnail( """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 @@ async def handle_imageproxy(self, request: web.Request) -> web.Response: 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 @@ async def create_collage_image( 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 @@ async def _update_playlist_metadata( # 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 @@ async def _update_playlist_metadata( # 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: diff --git a/music_assistant/providers/builtin/__init__.py b/music_assistant/providers/builtin/__init__.py index 504cd1075..6872d751d 100644 --- a/music_assistant/providers/builtin/__init__.py +++ b/music_assistant/providers/builtin/__init__.py @@ -39,7 +39,7 @@ ) 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 @@ class StoredItem(TypedDict): 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 @@ async def loaded_in_mass(self) -> None: 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 @@ async def parse_item( ) 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