Compare commits

..

No commits in common. "9ff488d8074c3d39f1516df045fe6001520f77cc" and "ffc399290f313f0e016528fd1321900f1b991252" have entirely different histories.

5 changed files with 55 additions and 105 deletions

View file

@ -7,7 +7,7 @@ This enables your keyboard's standard media keys to control MPD, as well as more
The recommended way to install mpd-now-playable and its dependencies is with [pipx](https://pypa.github.io/pipx/). I'm currently unable to register a PyPI account, so to install you'll currently need to do something like this:
```shell
pipx install 'git+https://git.00dani.me/00dani/mpd-now-playable'
pipx install 'https://git.00dani.me/00dani/mpd-now-playable/archive/main.tar.gz'
```
However once PyPI opens registration again, this will work too, and it's the approach I definitely recommend:

View file

@ -1,13 +1,10 @@
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
@ -17,18 +14,10 @@ def mpd_current_to_song(
) -> Song:
return Song(
state=PlaybackState(status["state"]),
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),
title=current["title"],
artist=current["artist"],
album=current["album"],
album_artist=current["albumartist"],
duration=float(status["duration"]),
elapsed=float(status["elapsed"]),
art=art,

View file

@ -1,4 +1,4 @@
from typing import Literal, NotRequired, Protocol, TypedDict
from typing import Protocol, TypedDict
class MpdStateHandler(Protocol):
@ -9,74 +9,56 @@ 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):
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.
volume: str
repeat: str
random: str
single: str
consume: str
partition: str
# The total number of items in the play queue, which is called the "playlist" throughout the MPD protocol for legacy reasons.
playlist: str
playlistlength: str
# The format of decoded audio MPD is producing, expressed as a string in the form "samplerate:bits:channels".
mixrampdb: str
state: str
song: str
songid: str
time: str
elapsed: str
bitrate: str
duration: str
audio: str
nextsong: str
nextsongid: 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
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,
},
)
ReadPictureResponse = TypedDict("ReadPictureResponse", {"binary": bytes})

View file

@ -1,7 +1,5 @@
from enum import StrEnum
from pathlib import Path
from typing import Protocol
from uuid import UUID
from attrs import define, field
@ -15,18 +13,10 @@ class PlaybackState(StrEnum):
@define
class Song:
state: PlaybackState
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
title: str
artist: str
album: str
album_artist: str
duration: float
elapsed: float
art: bytes | None = field(repr=lambda a: "<has art>" if a else "<no art>")

View file

@ -1,11 +0,0 @@
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)