From f38770efc429911b2a94f1ef1b65586c3c2502a2 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Fri, 25 Oct 2024 14:23:12 +0200 Subject: [PATCH] Fix: Prevent playlist collage image take up all system memory --- music_assistant/__main__.py | 2 +- .../server/controllers/metadata.py | 21 ++++++++++++++----- music_assistant/server/helpers/images.py | 2 ++ music_assistant/server/server.py | 1 - 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/music_assistant/__main__.py b/music_assistant/__main__.py index 1a10de76..6f9ad40e 100644 --- a/music_assistant/__main__.py +++ b/music_assistant/__main__.py @@ -213,7 +213,7 @@ def main() -> None: run( start_mass(), shutdown_callback=on_shutdown, - executor_workers=32, + executor_workers=16, ) diff --git a/music_assistant/server/controllers/metadata.py b/music_assistant/server/controllers/metadata.py index bdabf7cf..4671ff39 100644 --- a/music_assistant/server/controllers/metadata.py +++ b/music_assistant/server/controllers/metadata.py @@ -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 [] diff --git a/music_assistant/server/helpers/images.py b/music_assistant/server/helpers/images.py index e44db3e4..931c6372 100644 --- a/music_assistant/server/helpers/images.py +++ b/music_assistant/server/helpers/images.py @@ -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(): diff --git a/music_assistant/server/server.py b/music_assistant/server/server.py index 26460e13..48e9e880 100644 --- a/music_assistant/server/server.py +++ b/music_assistant/server/server.py @@ -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( -- 2.34.1