# 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."""
"""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
)
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."""
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,
)
# 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:
# 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:
)
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
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,
)
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:
)
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