Compare commits
No commits in common. "9ff488d8074c3d39f1516df045fe6001520f77cc" and "ffc399290f313f0e016528fd1321900f1b991252" have entirely different histories.
9ff488d807
...
ffc399290f
5 changed files with 55 additions and 105 deletions
|
@ -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:
|
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
|
```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:
|
However once PyPI opens registration again, this will work too, and it's the approach I definitely recommend:
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from pathlib import Path
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from mpd.asyncio import MPDClient
|
from mpd.asyncio import MPDClient
|
||||||
from mpd.base import CommandError
|
from mpd.base import CommandError
|
||||||
|
|
||||||
from ..player import Player
|
from ..player import Player
|
||||||
from ..song import PlaybackState, Song, SongListener
|
from ..song import PlaybackState, Song, SongListener
|
||||||
from ..type_tools import convert_if_exists
|
|
||||||
from .artwork_cache import MpdArtworkCache
|
from .artwork_cache import MpdArtworkCache
|
||||||
from .types import CurrentSongResponse, StatusResponse
|
from .types import CurrentSongResponse, StatusResponse
|
||||||
|
|
||||||
|
@ -17,18 +14,10 @@ def mpd_current_to_song(
|
||||||
) -> Song:
|
) -> Song:
|
||||||
return Song(
|
return Song(
|
||||||
state=PlaybackState(status["state"]),
|
state=PlaybackState(status["state"]),
|
||||||
queue_index=int(current["pos"]),
|
title=current["title"],
|
||||||
queue_length=int(status["playlistlength"]),
|
artist=current["artist"],
|
||||||
file=Path(current["file"]),
|
album=current["album"],
|
||||||
musicbrainz_trackid=convert_if_exists(current.get("musicbrainz_trackid"), UUID),
|
album_artist=current["albumartist"],
|
||||||
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"]),
|
duration=float(status["duration"]),
|
||||||
elapsed=float(status["elapsed"]),
|
elapsed=float(status["elapsed"]),
|
||||||
art=art,
|
art=art,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Literal, NotRequired, Protocol, TypedDict
|
from typing import Protocol, TypedDict
|
||||||
|
|
||||||
|
|
||||||
class MpdStateHandler(Protocol):
|
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):
|
class StatusResponse(TypedDict):
|
||||||
state: Literal["play", "stop", "pause"]
|
volume: str
|
||||||
|
repeat: str
|
||||||
# The total duration and elapsed playback of the current song, measured in seconds. Fractional seconds are allowed.
|
random: str
|
||||||
duration: str
|
single: str
|
||||||
elapsed: str
|
consume: 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
|
partition: str
|
||||||
|
playlist: str
|
||||||
# The total number of items in the play queue, which is called the "playlist" throughout the MPD protocol for legacy reasons.
|
|
||||||
playlistlength: str
|
playlistlength: str
|
||||||
|
mixrampdb: str
|
||||||
# The format of decoded audio MPD is producing, expressed as a string in the form "samplerate:bits:channels".
|
state: str
|
||||||
|
song: str
|
||||||
|
songid: str
|
||||||
|
time: str
|
||||||
|
elapsed: str
|
||||||
|
bitrate: str
|
||||||
|
duration: str
|
||||||
audio: str
|
audio: str
|
||||||
|
nextsong: str
|
||||||
|
nextsongid: str
|
||||||
|
|
||||||
|
|
||||||
# All of these are metadata tags read from your music, and are strictly
|
CurrentSongResponse = TypedDict(
|
||||||
# optional. mpd-now-playable will work better if your music is properly
|
"CurrentSongResponse",
|
||||||
# tagged, since then it can pass more information on to Now Playing, but it
|
{
|
||||||
# should work fine with completely untagged music too.
|
"file": str,
|
||||||
class CurrentSongTags(TypedDict, total=False):
|
"last-modified": str,
|
||||||
artist: str
|
"format": str,
|
||||||
albumartist: str
|
"artist": str,
|
||||||
artistsort: str
|
"albumartist": str,
|
||||||
albumartistsort: str
|
"artistsort": str,
|
||||||
title: str
|
"albumartistsort": str,
|
||||||
album: str
|
"title": str,
|
||||||
track: str
|
"album": str,
|
||||||
date: str
|
"track": str,
|
||||||
originaldate: str
|
"date": str,
|
||||||
composer: str
|
"originaldate": str,
|
||||||
disc: str
|
"composer": str,
|
||||||
label: str
|
"disc": str,
|
||||||
genre: str
|
"label": str,
|
||||||
musicbrainz_albumid: str
|
"musicbrainz_albumid": str,
|
||||||
musicbrainz_albumartistid: str
|
"musicbrainz_albumartistid": str,
|
||||||
musicbrainz_releasetrackid: str
|
"musicbrainz_releasetrackid": str,
|
||||||
musicbrainz_artistid: str
|
"musicbrainz_artistid": str,
|
||||||
musicbrainz_trackid: str
|
"musicbrainz_trackid": str,
|
||||||
|
"time": str,
|
||||||
|
"duration": str,
|
||||||
class CurrentSongResponse(CurrentSongTags):
|
"pos": str,
|
||||||
# The name of the music file currently being played, as MPD understands
|
"id": str,
|
||||||
# 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})
|
ReadPictureResponse = TypedDict("ReadPictureResponse", {"binary": bytes})
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
from pathlib import Path
|
|
||||||
from typing import Protocol
|
from typing import Protocol
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from attrs import define, field
|
from attrs import define, field
|
||||||
|
|
||||||
|
@ -15,18 +13,10 @@ class PlaybackState(StrEnum):
|
||||||
@define
|
@define
|
||||||
class Song:
|
class Song:
|
||||||
state: PlaybackState
|
state: PlaybackState
|
||||||
queue_index: int
|
title: str
|
||||||
queue_length: int
|
artist: str
|
||||||
file: Path
|
album: str
|
||||||
musicbrainz_trackid: UUID | None
|
album_artist: str
|
||||||
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
|
duration: float
|
||||||
elapsed: float
|
elapsed: float
|
||||||
art: bytes | None = field(repr=lambda a: "<has art>" if a else "<no art>")
|
art: bytes | None = field(repr=lambda a: "<has art>" if a else "<no art>")
|
||||||
|
|
|
@ -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)
|
|
Loading…
Reference in a new issue