# retrieve genres from tracks
# TODO: retrieve style/mood ?
playlist.metadata.genres = set()
- image_urls = set()
+ images = set()
try:
playlist_genres: dict[str, int] = {}
async for track in self.mass.music.playlists.tracks(
playlist.item_id, playlist.provider
):
if not playlist.image and track.image:
- image_urls.add(track.image.path)
+ images.add(track.image)
if track.media_type != MediaType.TRACK:
# filter out radio items
continue
playlist.metadata.genres.update(playlist_genres_filtered)
# create collage thumb/fanart from playlist tracks
- if image_urls:
+ if images:
if playlist.image and self.mass.storage_path in playlist.image:
img_path = playlist.image
else:
img_path = os.path.join(self.mass.storage_path, f"{uuid4().hex}.png")
- img_data = await create_collage(self.mass, list(image_urls))
+ img_data = await create_collage(self.mass, list(images))
async with aiofiles.open(img_path, "wb") as _file:
await _file.write(img_data)
playlist.metadata.images = [MediaItemImage(ImageType.THUMB, img_path, True)]
import aiofiles
from PIL import Image
+from music_assistant.common.models.media_items import MediaItemImage
from music_assistant.server.helpers.tags import get_embedded_image
if TYPE_CHECKING:
return await asyncio.to_thread(_create_image)
-async def create_collage(mass: MusicAssistant, images: list[str]) -> bytes:
+async def create_collage(mass: MusicAssistant, images: list[MediaItemImage]) -> bytes:
"""Create a basic collage image from multiple image urls."""
def _new_collage():
for x_co in range(0, 1500, 500):
for y_co in range(0, 1500, 500):
- img_data = await get_image_data(mass, random.choice(images))
+ img = random.choice(images)
+ img_data = await get_image_data(mass, img.path, img.provider)
await asyncio.to_thread(_add_to_collage, img_data, x_co, y_co)
def _save_collage():
)
TRACK_EXTENSIONS = ("mp3", "m4a", "m4b", "mp4", "flac", "wav", "ogg", "aiff", "wma", "dsf")
-PLAYLIST_EXTENSIONS = ("m3u", "pls")
+PLAYLIST_EXTENSIONS = ("m3u", "pls", "m3u8")
SUPPORTED_EXTENSIONS = TRACK_EXTENSIONS + PLAYLIST_EXTENSIONS
IMAGE_EXTENSIONS = ("jpg", "jpeg", "JPG", "JPEG", "png", "PNG", "gif", "GIF")
SEEKABLE_FILES = (ContentType.MP3, ContentType.WAV, ContentType.FLAC)
async def _parse_playlist_line(self, line: str, playlist_path: str) -> Track | Radio | None:
"""Try to parse a track from a playlist line."""
try:
- # try to treat uri as (relative) filename
- if "://" not in line:
- for filename in (line, os.path.join(playlist_path, line)):
- if not await self.exists(filename):
- continue
- return await self.get_track(filename)
- # fallback to generic uri parsing
- return await self.mass.music.get_item_by_uri(line)
+ if "://" in line:
+ # handle as generic uri
+ return await self.mass.music.get_item_by_uri(line)
+
+ # if a relative path was given in an upper level from the playlist,
+ # try to resolve it
+ for parentpart in ("../", "..\\"):
+ while line.startswith(parentpart):
+ if len(playlist_path) < 3:
+ break # guard
+ playlist_path = parentpart[:-3]
+ line = line[3:]
+
+ # try to resolve the filename
+ for filename in (line, os.path.join(playlist_path, line)):
+ with contextlib.suppress(FileNotFoundError):
+ item = await self.resolve(filename)
+ return await self._parse_track(item)
+
except MusicAssistantError as err:
self.logger.warning("Could not parse uri/file %s to track: %s", line, str(err))
return None
"python-slugify==8.0.1",
"mashumaro==3.7",
"memory-tempfile==2.2.3",
- "music-assistant-frontend==20230609.0",
+ "music-assistant-frontend==20230616.0",
"pillow==9.5.0",
"unidecode==1.3.6",
"xmltodict==0.13.0",
git+https://github.com/pytube/pytube.git@refs/pull/1501/head
mashumaro==3.7
memory-tempfile==2.2.3
-music-assistant-frontend==20230609.0
+music-assistant-frontend==20230616.0
orjson==3.9.1
pillow==9.5.0
plexapi==4.14.0