Fix: Prevent playlist collage image take up all system memory
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Fri, 25 Oct 2024 12:23:12 +0000 (14:23 +0200)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Fri, 25 Oct 2024 12:23:12 +0000 (14:23 +0200)
music_assistant/__main__.py
music_assistant/server/controllers/metadata.py
music_assistant/server/helpers/images.py
music_assistant/server/server.py

index 1a10de76044aec14dd1add50266e219406c2dff2..6f9ad40ecf97827a345078fe044ae99aa05f29a7 100644 (file)
@@ -213,7 +213,7 @@ def main() -> None:
     run(
         start_mass(),
         shutdown_callback=on_shutdown,
-        executor_workers=32,
+        executor_workers=16,
     )
 
 
index bdabf7cf593c0def374b4cc37562f54b7cd0bb84..4671ff39573adcfdcc2484c05901055928b4ca45 100644 (file)
@@ -5,9 +5,9 @@ from __future__ import annotations
 import asyncio
 import logging
 import os
+import random
 import urllib.parse
 from base64 import b64encode
-from collections.abc import Iterable
 from contextlib import suppress
 from time import time
 from typing import TYPE_CHECKING, cast
@@ -394,7 +394,7 @@ class MetaDataController(CoreController):
 
     async def create_collage_image(
         self,
-        images: Iterable[MediaItemImage],
+        images: list[MediaItemImage],
         img_path: str,
         fanart: bool = False,
     ) -> MediaItemImage | None:
@@ -402,6 +402,8 @@ class MetaDataController(CoreController):
         if len(images) < 8 and fanart or len(images) < 3:
             # require at least some images otherwise this does not make a lot of sense
             return None
+        # limit to 50 images to prevent we're going OOM
+        images = random.sample(images, 50)
         try:
             # create collage thumb from playlist tracks
             # if playlist has no default image (e.g. a local playlist)
@@ -410,6 +412,7 @@ class MetaDataController(CoreController):
             # always overwrite existing path
             async with aiofiles.open(img_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,
@@ -604,13 +607,20 @@ class MetaDataController(CoreController):
             return
         self.logger.debug("Updating metadata for Playlist %s", playlist.name)
         playlist.metadata.genres = set()
-        all_playlist_tracks_images = set()
+        all_playlist_tracks_images: list[MediaItemImage] = []
         playlist_genres: dict[str, int] = {}
         # retrieve metadata for the playlist from the tracks (such as genres etc.)
         # TODO: retrieve style/mood ?
         async for track in self.mass.music.playlists.tracks(playlist.item_id, playlist.provider):
-            if track.image:
-                all_playlist_tracks_images.add(track.image)
+            if (
+                track.image
+                and track.image not in all_playlist_tracks_images
+                and (
+                    track.image.provider in ("url", "builtin", "http")
+                    or self.mass.get_provider(track.image.provider)
+                )
+            ):
+                all_playlist_tracks_images.append(track.image)
             if track.metadata.genres:
                 genres = track.metadata.genres
             elif track.album and isinstance(track.album, Album) and track.album.metadata.genres:
@@ -624,6 +634,7 @@ class MetaDataController(CoreController):
             await asyncio.sleep(0)  # yield to eventloop
 
         playlist_genres_filtered = {genre for genre, count in playlist_genres.items() if count > 5}
+        playlist_genres_filtered = list(playlist_genres_filtered)[:8]
         playlist.metadata.genres.update(playlist_genres_filtered)
         # create collage images
         cur_images = playlist.metadata.images or []
index e44db3e437bbb37b6fc591546a0f9e53af93edc9..931c637277b95d85ddf640673b864241ed32291d 100644 (file)
@@ -104,6 +104,7 @@ async def create_collage(
         photo = Image.open(data).convert("RGB")
         photo = photo.resize((image_size, image_size))
         collage.paste(photo, (coord_x, coord_y))
+        del data
 
     # prevent duplicates with a set
     images = list(set(images))
@@ -117,6 +118,7 @@ async def create_collage(
                 img_data = await get_image_data(mass, img.path, img.provider)
                 if img_data:
                     await asyncio.to_thread(_add_to_collage, img_data, x_co, y_co)
+                    del img_data
                     break
 
     def _save_collage():
index 26460e1360e5a3710de330d393809d602a84c368..48e9e8801649195a4c363ece4ab96afcc7283d6c 100644 (file)
@@ -256,7 +256,6 @@ class MusicAssistant:
                 continue
             if return_unavailable or prov.available:
                 return prov
-        LOGGER.debug("Provider %s is not available", provider_instance_or_domain)
         return None
 
     def signal_event(