diff --git a/schemata/song-v1.json b/schemata/song-v1.json index 6ee74de..a16bda4 100644 --- a/schemata/song-v1.json +++ b/schemata/song-v1.json @@ -17,10 +17,13 @@ "MusicBrainzIds": { "properties": { "artist": { - "description": "https://musicbrainz.org/doc/Artist", - "format": "uuid", + "description": "A MusicBrainz artist is pretty intuitively the artist who recorded the song. This particular ID refers to the individual recording's artist or artists, which may be distinct from the release artist below when a release contains recordings from many different artists. https://musicbrainz.org/doc/Artist", + "items": { + "format": "uuid", + "type": "string" + }, "title": "Artist", - "type": "string" + "type": "array" }, "recording": { "description": "A MusicBrainz recording represents audio from a specific performance. For example, if the same song was released as a studio recording and as a live performance, those two versions of the song are different recordings. The song itself is considered a \"work\", of which two recordings were made. However, recordings are not always associated with a work in the MusicBrainz database, and Picard won't load work IDs by default (you have to enable \"use track relationships\" in the options), so recording IDs are a much more reliable way to identify a particular song. https://musicbrainz.org/doc/Recording", @@ -30,15 +33,21 @@ }, "release": { "description": "A MusicBrainz release roughly corresponds to an \"album\", and indeed is stored in a tag called MUSICBRAINZ_ALBUMID. The more general name is meant to encompass all the different ways music can be released. https://musicbrainz.org/doc/Release", - "format": "uuid", + "items": { + "format": "uuid", + "type": "string" + }, "title": "Release", - "type": "string" + "type": "array" }, "release_artist": { "description": "Again, the release artist corresponds to an \"album artist\". These MBIDs refer to the same artists in the MusicBrainz database that individual recordings' artist MBIDs do.", - "format": "uuid", + "items": { + "format": "uuid", + "type": "string" + }, "title": "Release Artist", - "type": "string" + "type": "array" }, "release_group": { "description": "A MusicBrainz release group roughly corresponds to \"all the editions of a particular album\". For example, if the same album were released on CD, vinyl records, and as a digital download, then all of those would be different releases but share a release group. Note that MPD's support for this tag is relatively new (July 2023) and doesn't seem especially reliable, so it might be missing here even if your music has been tagged with it. Not sure why. https://musicbrainz.org/doc/Release_Group", @@ -59,6 +68,11 @@ "type": "string" } }, + "required": [ + "artist", + "release", + "release_artist" + ], "title": "MusicBrainzIds", "type": "object" }, diff --git a/src/mpd_now_playable/cache.py b/src/mpd_now_playable/cache.py index 6b27f71..58394e6 100644 --- a/src/mpd_now_playable/cache.py +++ b/src/mpd_now_playable/cache.py @@ -1,8 +1,8 @@ from __future__ import annotations -from contextlib import suppress from typing import Any, Generic, Optional, TypeVar +import ormsgpack from aiocache import Cache from aiocache.serializers import BaseSerializer from pydantic.type_adapter import TypeAdapter @@ -10,9 +10,6 @@ from yarl import URL T = TypeVar("T") -with suppress(ImportError): - import ormsgpack - class OrmsgpackSerializer(BaseSerializer, Generic[T]): DEFAULT_ENCODING = None diff --git a/src/mpd_now_playable/mpd/types.py b/src/mpd_now_playable/mpd/types.py index 8782ddc..4183e5d 100644 --- a/src/mpd_now_playable/mpd/types.py +++ b/src/mpd_now_playable/mpd/types.py @@ -1,5 +1,6 @@ from typing import Literal, NotRequired, Protocol, TypedDict +from ..song.musicbrainz import MusicBrainzTags from ..tools.types import MaybePlural @@ -48,7 +49,7 @@ class StatusResponse(TypedDict): # optional. mpd-now-playable will work better if your music is properly # tagged, since then it can pass more information on to Now Playing, but it # should work fine with completely untagged music too. -class CurrentSongTags(TypedDict, total=False): +class CurrentSongTags(MusicBrainzTags, total=False): artist: MaybePlural[str] albumartist: MaybePlural[str] artistsort: MaybePlural[str] @@ -62,11 +63,6 @@ class CurrentSongTags(TypedDict, total=False): disc: str label: str genre: MaybePlural[str] - musicbrainz_albumid: str - musicbrainz_albumartistid: str - musicbrainz_releasetrackid: str - musicbrainz_artistid: str - musicbrainz_trackid: str class CurrentSongResponse(CurrentSongTags): diff --git a/src/mpd_now_playable/receivers/websockets/receiver.py b/src/mpd_now_playable/receivers/websockets/receiver.py index 0439811..22c35bb 100644 --- a/src/mpd_now_playable/receivers/websockets/receiver.py +++ b/src/mpd_now_playable/receivers/websockets/receiver.py @@ -34,7 +34,7 @@ class WebsocketsReceiver(Receiver): async def start(self, player: Player) -> None: self.player = player - await serve(self.handle, host=self.config.host, port=self.config.port) + await serve(self.handle, host=self.config.host, port=self.config.port, reuse_port=True) async def handle(self, conn: WebSocketServerProtocol) -> None: self.connections.add(conn) diff --git a/src/mpd_now_playable/song/musicbrainz.py b/src/mpd_now_playable/song/musicbrainz.py index 9ab5278..23ae559 100644 --- a/src/mpd_now_playable/song/musicbrainz.py +++ b/src/mpd_now_playable/song/musicbrainz.py @@ -5,12 +5,16 @@ from uuid import UUID from pydantic import Field -from ..tools.types import option_fmap +from ..tools.types import MaybePlural, option_fmap, un_maybe_plural option_uuid = partial(option_fmap, UUID) OptionUUID = Annotated[UUID | None, Field(default=None)] +def to_uuids(values: MaybePlural[str] | None) -> list[UUID]: + return [UUID(i) for i in un_maybe_plural(values)] + + class MusicBrainzTags(TypedDict, total=False): """ The MusicBrainz tags mpd-now-playable expects and will load (all optional). @@ -24,11 +28,11 @@ class MusicBrainzTags(TypedDict, total=False): #: MusicBrainz Track ID musicbrainz_releasetrackid: str #: MusicBrainz Artist ID - musicbrainz_artistid: str + musicbrainz_artistid: MaybePlural[str] #: MusicBrainz Release ID - musicbrainz_albumid: str + musicbrainz_albumid: MaybePlural[str] #: MusicBrainz Release Artist ID - musicbrainz_albumartistid: str + musicbrainz_albumartistid: MaybePlural[str] #: MusicBrainz Release Group ID musicbrainz_releasegroupid: str #: MusicBrainz Work ID @@ -63,19 +67,23 @@ class MusicBrainzIds: #: https://musicbrainz.org/doc/Track track: OptionUUID + #: A MusicBrainz artist is pretty intuitively the artist who recorded the + #: song. This particular ID refers to the individual recording's artist or + #: artists, which may be distinct from the release artist below when a + #: release contains recordings from many different artists. #: https://musicbrainz.org/doc/Artist - artist: OptionUUID + artist: list[UUID] #: A MusicBrainz release roughly corresponds to an "album", and indeed is #: stored in a tag called MUSICBRAINZ_ALBUMID. The more general name is #: meant to encompass all the different ways music can be released. #: https://musicbrainz.org/doc/Release - release: OptionUUID + release: list[UUID] #: Again, the release artist corresponds to an "album artist". These MBIDs #: refer to the same artists in the MusicBrainz database that individual #: recordings' artist MBIDs do. - release_artist: OptionUUID + release_artist: list[UUID] #: A MusicBrainz release group roughly corresponds to "all the editions of #: a particular album". For example, if the same album were released on CD, @@ -92,8 +100,8 @@ def to_brainz(tags: MusicBrainzTags) -> MusicBrainzIds: recording=option_uuid(tags.get("musicbrainz_trackid")), work=option_uuid(tags.get("musicbrainz_workid")), track=option_uuid(tags.get("musicbrainz_releasetrackid")), - artist=option_uuid(tags.get("musicbrainz_artistid")), - release=option_uuid(tags.get("musicbrainz_albumid")), - release_artist=option_uuid(tags.get("musicbrainz_albumartistid")), + artist=to_uuids(tags.get("musicbrainz_artistid")), + release=to_uuids(tags.get("musicbrainz_albumid")), + release_artist=to_uuids(tags.get("musicbrainz_albumartistid")), release_group=option_uuid(tags.get("musicbrainz_releasegroupid")), )