Make most song info optional, to support untagged music
This commit is contained in:
parent
88a38a1bbd
commit
9ff488d807
4 changed files with 105 additions and 55 deletions
|
@ -1,10 +1,13 @@
|
|||
import asyncio
|
||||
from pathlib import Path
|
||||
from uuid import UUID
|
||||
|
||||
from mpd.asyncio import MPDClient
|
||||
from mpd.base import CommandError
|
||||
|
||||
from ..player import Player
|
||||
from ..song import PlaybackState, Song, SongListener
|
||||
from ..type_tools import convert_if_exists
|
||||
from .artwork_cache import MpdArtworkCache
|
||||
from .types import CurrentSongResponse, StatusResponse
|
||||
|
||||
|
@ -14,10 +17,18 @@ def mpd_current_to_song(
|
|||
) -> Song:
|
||||
return Song(
|
||||
state=PlaybackState(status["state"]),
|
||||
title=current["title"],
|
||||
artist=current["artist"],
|
||||
album=current["album"],
|
||||
album_artist=current["albumartist"],
|
||||
queue_index=int(current["pos"]),
|
||||
queue_length=int(status["playlistlength"]),
|
||||
file=Path(current["file"]),
|
||||
musicbrainz_trackid=convert_if_exists(current.get("musicbrainz_trackid"), UUID),
|
||||
title=current.get("title"),
|
||||
artist=current.get("artist"),
|
||||
album=current.get("album"),
|
||||
album_artist=current.get("albumartist"),
|
||||
composer=current.get("composer"),
|
||||
genre=current.get("genre"),
|
||||
track=convert_if_exists(current.get("track"), int),
|
||||
disc=convert_if_exists(current.get("disc"), int),
|
||||
duration=float(status["duration"]),
|
||||
elapsed=float(status["elapsed"]),
|
||||
art=art,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Protocol, TypedDict
|
||||
from typing import Literal, NotRequired, Protocol, TypedDict
|
||||
|
||||
|
||||
class MpdStateHandler(Protocol):
|
||||
|
@ -9,56 +9,74 @@ class MpdStateHandler(Protocol):
|
|||
...
|
||||
|
||||
|
||||
BooleanFlag = Literal["0", "1"]
|
||||
|
||||
OneshotFlag = Literal[BooleanFlag, "oneshot"]
|
||||
|
||||
|
||||
# This is not the complete status response from MPD, just the parts of it mpd-now-playable uses.
|
||||
class StatusResponse(TypedDict):
|
||||
volume: str
|
||||
repeat: str
|
||||
random: str
|
||||
single: str
|
||||
consume: str
|
||||
partition: str
|
||||
playlist: str
|
||||
playlistlength: str
|
||||
mixrampdb: str
|
||||
state: str
|
||||
song: str
|
||||
songid: str
|
||||
time: str
|
||||
elapsed: str
|
||||
bitrate: str
|
||||
state: Literal["play", "stop", "pause"]
|
||||
|
||||
# The total duration and elapsed playback of the current song, measured in seconds. Fractional seconds are allowed.
|
||||
duration: str
|
||||
elapsed: str
|
||||
|
||||
# The volume value ranges from 0-100. It may be omitted from
|
||||
# the response entirely if MPD has no volume mixer configured.
|
||||
volume: NotRequired[str]
|
||||
|
||||
# Various toggle-able music playback settings, which can be addressed and modified by Now Playing.
|
||||
repeat: BooleanFlag
|
||||
random: BooleanFlag
|
||||
single: OneshotFlag
|
||||
consume: OneshotFlag
|
||||
|
||||
# Partitions essentially let one MPD server act as multiple music players.
|
||||
# For most folks, this will just be "default", but mpd-now-playable will
|
||||
# eventually support addressing specific partitions. Eventually.
|
||||
partition: str
|
||||
|
||||
# The total number of items in the play queue, which is called the "playlist" throughout the MPD protocol for legacy reasons.
|
||||
playlistlength: str
|
||||
|
||||
# The format of decoded audio MPD is producing, expressed as a string in the form "samplerate:bits:channels".
|
||||
audio: str
|
||||
nextsong: str
|
||||
nextsongid: str
|
||||
|
||||
|
||||
CurrentSongResponse = TypedDict(
|
||||
"CurrentSongResponse",
|
||||
{
|
||||
"file": str,
|
||||
"last-modified": str,
|
||||
"format": str,
|
||||
"artist": str,
|
||||
"albumartist": str,
|
||||
"artistsort": str,
|
||||
"albumartistsort": str,
|
||||
"title": str,
|
||||
"album": str,
|
||||
"track": str,
|
||||
"date": str,
|
||||
"originaldate": str,
|
||||
"composer": str,
|
||||
"disc": str,
|
||||
"label": str,
|
||||
"musicbrainz_albumid": str,
|
||||
"musicbrainz_albumartistid": str,
|
||||
"musicbrainz_releasetrackid": str,
|
||||
"musicbrainz_artistid": str,
|
||||
"musicbrainz_trackid": str,
|
||||
"time": str,
|
||||
"duration": str,
|
||||
"pos": str,
|
||||
"id": str,
|
||||
},
|
||||
)
|
||||
# All of these are metadata tags read from your music, and are strictly
|
||||
# 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):
|
||||
artist: str
|
||||
albumartist: str
|
||||
artistsort: str
|
||||
albumartistsort: str
|
||||
title: str
|
||||
album: str
|
||||
track: str
|
||||
date: str
|
||||
originaldate: str
|
||||
composer: str
|
||||
disc: str
|
||||
label: str
|
||||
genre: str
|
||||
musicbrainz_albumid: str
|
||||
musicbrainz_albumartistid: str
|
||||
musicbrainz_releasetrackid: str
|
||||
musicbrainz_artistid: str
|
||||
musicbrainz_trackid: str
|
||||
|
||||
|
||||
class CurrentSongResponse(CurrentSongTags):
|
||||
# The name of the music file currently being played, as MPD understands
|
||||
# it. For locally stored music files, this'll just be a simple file path
|
||||
# relative to your music directory.
|
||||
file: str
|
||||
# The index of the song in the play queue. Will change if you shuffle or
|
||||
# otherwise reorder the playlist.
|
||||
pos: str
|
||||
|
||||
|
||||
ReadPictureResponse = TypedDict("ReadPictureResponse", {"binary": bytes})
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from enum import StrEnum
|
||||
from pathlib import Path
|
||||
from typing import Protocol
|
||||
from uuid import UUID
|
||||
|
||||
from attrs import define, field
|
||||
|
||||
|
@ -13,10 +15,18 @@ class PlaybackState(StrEnum):
|
|||
@define
|
||||
class Song:
|
||||
state: PlaybackState
|
||||
title: str
|
||||
artist: str
|
||||
album: str
|
||||
album_artist: str
|
||||
queue_index: int
|
||||
queue_length: int
|
||||
file: Path
|
||||
musicbrainz_trackid: UUID | None
|
||||
title: str | None
|
||||
artist: str | None
|
||||
composer: str | None
|
||||
album: str | None
|
||||
album_artist: str | None
|
||||
track: int | None
|
||||
disc: int | None
|
||||
genre: str | None
|
||||
duration: float
|
||||
elapsed: float
|
||||
art: bytes | None = field(repr=lambda a: "<has art>" if a else "<no art>")
|
||||
|
|
11
src/mpd_now_playable/type_tools.py
Normal file
11
src/mpd_now_playable/type_tools.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from typing import Callable, TypeVar
|
||||
|
||||
__all__ = ("convert_if_exists",)
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def convert_if_exists(value: str | None, converter: Callable[[str], T]) -> T | None:
|
||||
if value is None:
|
||||
return None
|
||||
return converter(value)
|
Loading…
Reference in a new issue