Skip to content

Commit

Permalink
Several (small) Bugfixes (#817)
Browse files Browse the repository at this point in the history
* Do not add unavailable items in sync

* remove unneeded playlisttitle

* Fix single track repeat in non-flow mode

* fix some typos

* add album tracks to library

* bump frontend to 2.0.10

* fix typo
  • Loading branch information
marcelveldt authored Aug 3, 2023
1 parent 3df3f13 commit bbb1bc8
Show file tree
Hide file tree
Showing 16 changed files with 78 additions and 57 deletions.
23 changes: 15 additions & 8 deletions music_assistant/server/controllers/media/albums.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import asyncio
import contextlib
from random import choice, random
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from music_assistant.common.helpers.datetime import utc_timestamp
from music_assistant.common.helpers.json import serialize_to_json
Expand Down Expand Up @@ -65,7 +65,7 @@ async def get(
lazy: bool = True,
details: Album | ItemMapping = None,
add_to_library: bool = False,
skip_metadata_lookup: bool = False,
**kwargs: dict[str, Any],
) -> Album:
"""Return (full) details for a single media item."""
album = await super().get(
Expand All @@ -75,7 +75,7 @@ async def get(
lazy=lazy,
details=details,
add_to_library=add_to_library,
skip_metadata_lookup=skip_metadata_lookup,
**kwargs,
)
# append full artist details to full album item
album.artists = [
Expand All @@ -85,12 +85,19 @@ async def get(
lazy=lazy,
details=item,
add_to_library=add_to_library,
**kwargs,
)
for item in album.artists
]
return album

async def add_item_to_library(self, item: Album, skip_metadata_lookup: bool = False) -> Album:
async def add_item_to_library(
self,
item: Album,
metadata_lookup: bool = True,
add_album_tracks: bool = True,
**kwargs: dict[str, Any], # noqa: ARG002
) -> Album:
"""Add album to library and return the database item."""
if not isinstance(item, Album):
raise InvalidDataError("Not a valid Album object (ItemMapping can not be added to db)")
Expand All @@ -108,24 +115,24 @@ async def add_item_to_library(self, item: Album, skip_metadata_lookup: bool = Fa
if not item.artists:
raise InvalidDataError("Album is missing artist(s)")
# grab additional metadata
if not skip_metadata_lookup:
if not metadata_lookup:
await self.mass.metadata.get_album_metadata(item)
# actually add (or update) the item in the library db
# use the lock to prevent a race condition of the same item being added twice
async with self._db_add_lock:
library_item = await self._add_library_item(item)
# also fetch the same album on all providers
if not skip_metadata_lookup:
if not metadata_lookup:
await self._match(library_item)
library_item = await self.get_library_item(library_item.item_id)
# also add album tracks
if not skip_metadata_lookup and item.provider != "library":
if add_album_tracks and item.provider != "library":
async with asyncio.TaskGroup() as tg:
for track in await self._get_provider_album_tracks(item.item_id, item.provider):
track.album = library_item
tg.create_task(
self.mass.music.tracks.add_item_to_library(
track, skip_metadata_lookup=skip_metadata_lookup
track, metadata_lookup=metadata_lookup
)
)
self.mass.signal_event(
Expand Down
11 changes: 7 additions & 4 deletions music_assistant/server/controllers/media/artists.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,23 @@ def __init__(self, *args, **kwargs):
self.mass.register_api_command("music/artists/artist_tracks", self.tracks)

async def add_item_to_library(
self, item: Artist | ItemMapping, skip_metadata_lookup: bool = False
self,
item: Artist | ItemMapping,
metadata_lookup: bool = True,
**kwargs: dict[str, Any], # noqa: ARG002
) -> Artist:
"""Add artist to library and return the database item."""
if isinstance(item, ItemMapping):
skip_metadata_lookup = True
metadata_lookup = True
# grab musicbrainz id and additional metadata
if not skip_metadata_lookup:
if not metadata_lookup:
await self.mass.metadata.get_artist_metadata(item)
# actually add (or update) the item in the library db
# use the lock to prevent a race condition of the same item being added twice
async with self._db_add_lock:
library_item = await self._add_library_item(item)
# also fetch same artist on all providers
if not skip_metadata_lookup:
if not metadata_lookup:
await self.match_artist(library_item)
library_item = await self.get_library_item(library_item.item_id)
self.mass.signal_event(
Expand Down
13 changes: 4 additions & 9 deletions music_assistant/server/controllers/media/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ def __init__(self, mass: MusicAssistant):
self.logger = logging.getLogger(f"{ROOT_LOGGER_NAME}.music.{self.media_type.value}")

@abstractmethod
async def add_item_to_library(
self, item: ItemCls, skip_metadata_lookup: bool = False
) -> ItemCls:
async def add_item_to_library(self, item: ItemCls, **kwargs: dict[str, Any]) -> ItemCls:
"""Add item to library and return the database item."""
raise NotImplementedError

Expand Down Expand Up @@ -155,7 +153,7 @@ async def get(
lazy: bool = True,
details: ItemCls = None,
add_to_library: bool = False,
skip_metadata_lookup: bool = False,
**kwargs: dict[str, Any],
) -> ItemCls:
"""Return (full) details for a single media item."""
if provider_instance_id_or_domain == "database":
Expand Down Expand Up @@ -206,10 +204,7 @@ async def get(
# we can set lazy to false and we await the job to complete.
task_id = f"add_{self.media_type.value}.{details.provider}.{details.item_id}"
add_task = self.mass.create_task(
self.add_item_to_library,
item=details,
skip_metadata_lookup=skip_metadata_lookup,
task_id=task_id,
self.add_item_to_library, item=details, task_id=task_id, **kwargs
)
if not lazy:
await add_task
Expand Down Expand Up @@ -691,7 +686,7 @@ async def _get_artist_mapping(self, artist: Artist | ItemMapping) -> ItemMapping
# try to request the full item
with suppress(MediaNotFoundError, AssertionError, InvalidDataError):
db_artist = await self.mass.music.artists.add_item_to_library(
artist, skip_metadata_lookup=True
artist, metadata_lookup=False
)
return ItemMapping.from_item(db_artist)
# fallback to just the provider item
Expand Down
6 changes: 2 additions & 4 deletions music_assistant/server/controllers/media/playlists.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ def __init__(self, *args, **kwargs):
"music/playlists/remove_playlist_tracks", self.remove_playlist_tracks
)

async def add_item_to_library(
self, item: Playlist, skip_metadata_lookup: bool = False
) -> Playlist:
async def add_item_to_library(self, item: Playlist, metadata_lookup: bool = True) -> Playlist:
"""Add playlist to library and return the new database item."""
if not isinstance(item, Playlist):
raise InvalidDataError(
Expand All @@ -72,7 +70,7 @@ async def add_item_to_library(
async for _ in self.tracks(item.item_id, item.provider):
pass
# metadata lookup we need to do after adding it to the db
if not skip_metadata_lookup:
if not metadata_lookup:
await self.mass.metadata.get_playlist_metadata(library_item)
library_item = await self.update_item_in_library(library_item.item_id, library_item)
self.mass.signal_event(
Expand Down
7 changes: 5 additions & 2 deletions music_assistant/server/controllers/media/radio.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

import asyncio
from typing import Any

from music_assistant.common.helpers.datetime import utc_timestamp
from music_assistant.common.helpers.json import serialize_to_json
Expand Down Expand Up @@ -62,13 +63,15 @@ async def versions(
# return the aggregated result
return all_versions.values()

async def add_item_to_library(self, item: Radio, skip_metadata_lookup: bool = False) -> Radio:
async def add_item_to_library(
self, item: Radio, metadata_lookup: bool = True, **kwargs: dict[str, Any] # noqa: ARG002
) -> Radio:
"""Add radio to library and return the new database item."""
if not isinstance(item, Radio):
raise InvalidDataError("Not a valid Radio object (ItemMapping can not be added to db)")
if not item.provider_mappings:
raise InvalidDataError("Radio is missing provider mapping(s)")
if not skip_metadata_lookup:
if not metadata_lookup:
await self.mass.metadata.get_radio_metadata(item)
# actually add (or update) the item in the library db
# use the lock to prevent a race condition of the same item being added twice
Expand Down
20 changes: 12 additions & 8 deletions music_assistant/server/controllers/media/tracks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import asyncio
import urllib.parse
from typing import Any

from music_assistant.common.helpers.datetime import utc_timestamp
from music_assistant.common.helpers.json import serialize_to_json
Expand Down Expand Up @@ -63,7 +64,7 @@ async def get(
details: Track = None,
album_uri: str | None = None,
add_to_library: bool = False,
skip_metadata_lookup: bool = False,
**kwargs: dict[str, Any],
) -> Track:
"""Return (full) details for a single media item."""
track = await super().get(
Expand All @@ -73,7 +74,7 @@ async def get(
lazy=lazy,
details=details,
add_to_library=add_to_library,
skip_metadata_lookup=skip_metadata_lookup,
**kwargs,
)
# append full album details to full track item
try:
Expand All @@ -86,7 +87,7 @@ async def get(
lazy=lazy,
details=None if isinstance(track.album, ItemMapping) else track.album,
add_to_library=add_to_library,
skip_metadata_lookup=skip_metadata_lookup,
**kwargs,
)
elif provider_instance_id_or_domain == "library":
# grab the first album this track is attached to
Expand Down Expand Up @@ -116,13 +117,15 @@ async def get(
lazy=lazy,
details=None if isinstance(artist, ItemMapping) else artist,
add_to_library=add_to_library,
skip_metadata_lookup=skip_metadata_lookup,
**kwargs,
)
)
track.artists = full_artists
return track

async def add_item_to_library(self, item: Track, skip_metadata_lookup: bool = False) -> Track:
async def add_item_to_library(
self, item: Track, metadata_lookup: bool = True, **kwargs: dict[str, Any] # noqa: ARG002
) -> Track:
"""Add track to library and return the new database item."""
if not isinstance(item, Track):
raise InvalidDataError("Not a valid Track object (ItemMapping can not be added to db)")
Expand Down Expand Up @@ -160,7 +163,7 @@ async def add_item_to_library(self, item: Track, skip_metadata_lookup: bool = Fa
for artist in item.album.artists
]
# grab additional metadata
if not skip_metadata_lookup:
if not metadata_lookup:
await self.mass.metadata.get_track_metadata(item)
# fallback track image from album (only if albumtype = single)
if (
Expand All @@ -177,7 +180,7 @@ async def add_item_to_library(self, item: Track, skip_metadata_lookup: bool = Fa
async with self._db_add_lock:
library_item = await self._add_library_item(item)
# also fetch same track on all providers (will also get other quality versions)
if not skip_metadata_lookup:
if not metadata_lookup:
await self._match(library_item)
library_item = await self.get_library_item(library_item.item_id)
self.mass.signal_event(
Expand Down Expand Up @@ -448,7 +451,8 @@ async def _set_track_album(self, db_id: int, album: Album, disc_number: int, tra
lazy=False,
details=album,
add_to_library=True,
skip_metadata_lookup=True,
metadata_lookup=False,
add_album_tracks=False,
)
album_mapping = {"track_id": db_id, "album_id": int(db_album.item_id)}
if db_row := await self.mass.music.database.get_row(DB_TABLE_ALBUM_TRACKS, album_mapping):
Expand Down
2 changes: 1 addition & 1 deletion music_assistant/server/controllers/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ async def get_artist_metadata(self, artist: Artist) -> None:

async def get_album_metadata(self, album: Album) -> None:
"""Get/update rich metadata for an album."""
# ensure the album has a musicbrainz id or artist
# ensure the album has a musicbrainz id or artist(s)
if not (album.mbid or album.artists):
return
# collect metadata from all providers
Expand Down
8 changes: 6 additions & 2 deletions music_assistant/server/controllers/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import asyncio
import logging
import time
import urllib.parse
from collections.abc import AsyncGenerator
from contextlib import suppress
Expand Down Expand Up @@ -414,8 +415,11 @@ async def resolve_stream_url(
query_params["seek_position"] = str(seek_position)
if fade_in:
query_params["fade_in"] = "1"
if query_params:
url += "?" + urllib.parse.urlencode(query_params)
# we add a timestamp as basic checksum
# most importantly this is to invalidate any caches
# but also to handle edge cases such as single track repeat
query_params["ts"] = str(int(time.time()))
url += "?" + urllib.parse.urlencode(query_params)
return url

async def create_multi_client_stream_job(
Expand Down
1 change: 0 additions & 1 deletion music_assistant/server/helpers/didl_lite.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ def create_didl_metadata(
f"<upnp:album>{album}</upnp:album>"
f"<upnp:artist>{artist}</upnp:artist>"
f"<upnp:duration>{int(queue_item.duration)}</upnp:duration>"
"<upnp:playlistTitle>Music Assistant</upnp:playlistTitle>"
f"<dc:queueItemId>{queue_item.queue_item_id}</dc:queueItemId>"
f"<upnp:albumArtURI>{escape_string(image_url)}</upnp:albumArtURI>"
"<upnp:class>object.item.audioItem.audioBroadcast</upnp:class>"
Expand Down
8 changes: 7 additions & 1 deletion music_assistant/server/models/music_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,13 +410,19 @@ async def sync_library(self, media_types: tuple[MediaType, ...]) -> None:
prov_item.provider_mappings,
)
try:
if not library_item and not prov_item.available:
# skip unavailable tracks
self.logger.debg(
"Skipping sync of item %s because it is unavailable", prov_item.uri
)
continue
if not library_item:
# create full db item
# note that we skip the metadata lookup purely to speed up the sync
# the additional metadata is then lazy retrieved afterwards
prov_item.favorite = True
library_item = await controller.add_item_to_library(
prov_item, skip_metadata_lookup=True
prov_item, metadata_lookup=False
)
elif (
library_item.metadata.checksum and prov_item.metadata.checksum
Expand Down
12 changes: 5 additions & 7 deletions music_assistant/server/providers/filesystem_local/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ async def browse(self, path: str) -> BrowseFolder:
# make sure that the item exists
# https://github.com/music-assistant/hass-music-assistant/issues/707
library_item = await self.mass.music.tracks.add_item_to_library(
track, skip_metadata_lookup=True
track, metadata_lookup=False
)
subitems.append(library_item)
continue
Expand All @@ -284,7 +284,7 @@ async def browse(self, path: str) -> BrowseFolder:
# make sure that the item exists
# https://github.com/music-assistant/hass-music-assistant/issues/707
library_item = await self.mass.music.playlists.add_item_to_library(
playlist, skip_metadata_lookup=True
playlist, metadata_lookup=False
)
subitems.append(library_item)
continue
Expand Down Expand Up @@ -345,17 +345,15 @@ async def sync_library(self, media_types: tuple[MediaType, ...]) -> None: # noq
if item.ext in TRACK_EXTENSIONS:
# add/update track to db
track = await self._parse_track(item)
await self.mass.music.tracks.add_item_to_library(
track, skip_metadata_lookup=True
)
await self.mass.music.tracks.add_item_to_library(track, metadata_lookup=False)
elif item.ext in PLAYLIST_EXTENSIONS:
playlist = await self.get_playlist(item.path)
# add/update] playlist to db
playlist.metadata.checksum = item.checksum
# playlist is always in-library
playlist.favorite = True
await self.mass.music.playlists.add_item_to_library(
playlist, skip_metadata_lookup=True
playlist, metadata_lookup=False
)
except Exception as err: # pylint: disable=broad-except
# we don't want the whole sync to crash on one file so we catch all exceptions here
Expand Down Expand Up @@ -746,7 +744,7 @@ async def _parse_track(
# much space and bandwidth. Instead we set the filename as value so the image can
# be retrieved later in realtime.
track.metadata.images = [
MediaItemImage(type=ImageType.THUMB, url=file_item.path, provider=self.instance_id)
MediaItemImage(type=ImageType.THUMB, path=file_item.path, provider=self.instance_id)
]

if track.album and not track.album.metadata.images:
Expand Down
6 changes: 4 additions & 2 deletions music_assistant/server/providers/musicbrainz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ async def _search_artist_by_album(
album_barcode: str | None = None,
) -> str | None:
"""Retrieve musicbrainz artist id by providing the artist name and albumname or barcode."""
assert albumname or album_barcode
if not (albumname or album_barcode):
return None # may not happen, but guard just in case
for searchartist in (
artistname,
re.sub(LUCENE_SPECIAL, r"\\\1", artistname),
Expand Down Expand Up @@ -159,7 +160,8 @@ async def _search_artist_by_track(
track_isrc: str | None = None,
) -> str | None:
"""Retrieve artist id by providing the artist name and trackname or track isrc."""
assert trackname or track_isrc
if not (trackname or track_isrc):
return None # may not happen, but guard just in case
searchartist = re.sub(LUCENE_SPECIAL, r"\\\1", artistname)
if track_isrc:
result = await self.get_data(f"isrc/{track_isrc}", inc="artist-credits")
Expand Down
Loading

0 comments on commit bbb1bc8

Please sign in to comment.