Compare commits

..

2 commits

Author SHA1 Message Date
dc037a0a4b
Support a TOML configuration file
The new config file currently only configures the same options that were
already available through environment variables. However I have ideas
for additional features that would be much nicer to support using a
structured configuration format like TOML rather than environment
variables, so config files exist now!

The previous environment variables are still supported and will be used
if you don't have a config file. I plan to keep supporting the MPD_HOST
and MPD_PORT environment variables forever since they're shared with
other MPD clients such as mpc, but I may eventually drop the environment
variables specific to mpd-now-playable in a future release.
2024-06-22 18:19:39 +10:00
796e3df87d
Display mpd-now-playable version on launch 2024-06-22 13:17:14 +10:00
13 changed files with 310 additions and 33 deletions

112
pdm.lock
View file

@ -5,7 +5,7 @@
groups = ["default", "all", "dev"] groups = ["default", "all", "dev"]
strategy = ["cross_platform"] strategy = ["cross_platform"]
lock_version = "4.4.1" lock_version = "4.4.1"
content_hash = "sha256:2b4fba8ca6883f5a1de28a420870d566efa031d62f9057c59095a25a0e51d36e" content_hash = "sha256:828f8051de2c2a04cede8130abf86764edb38f0428cd1d52dd797bff43452afe"
[[package]] [[package]]
name = "aiocache" name = "aiocache"
@ -54,6 +54,22 @@ files = [
{file = "aiomcache-0.8.2.tar.gz", hash = "sha256:43b220d7f499a32a71871c4f457116eb23460fa216e69c1d32b81e3209e51359"}, {file = "aiomcache-0.8.2.tar.gz", hash = "sha256:43b220d7f499a32a71871c4f457116eb23460fa216e69c1d32b81e3209e51359"},
] ]
[[package]]
name = "apischema"
version = "0.18.1"
requires_python = ">=3.7"
summary = "JSON (de)serialization, GraphQL and JSON schema generation using Python typing."
files = [
{file = "apischema-0.18.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:220aa56974f765dc100e875c66c688ff57bad3ae48d0aeaee4fb1ec90c5cd0fd"},
{file = "apischema-0.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:653492010d22acdcbe2240f0ceb3ca59de1b6d34640895e03fda15f944e216d8"},
{file = "apischema-0.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56a8acc8da59cf7a1052b5aec71d2f8ed6137b54aa1492709acb2c47f0547107"},
{file = "apischema-0.18.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d466ccc31cbc95b381037ed5b9e82e034f921eb28f8057263aefc2817678036f"},
{file = "apischema-0.18.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b005d5a4baba32eccec5bb0fbd616f9fd26b6a5fb4f15e9a8bb53a948adade0"},
{file = "apischema-0.18.1-cp312-cp312-win32.whl", hash = "sha256:bd06fc6a52d461bd6540409cb25c1d51aae23b22fcd10b1fb002a3f7f1f15d0f"},
{file = "apischema-0.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:429f13d9e35379bf8187c41a3c05562f8149358128382a90e415c63db528d6a2"},
{file = "apischema-0.18.1.tar.gz", hash = "sha256:355dc4dea7389f5b25f5326c26f06eebee8107efda7e82db8f09ee122cdf0c98"},
]
[[package]] [[package]]
name = "attrs" name = "attrs"
version = "23.2.0" version = "23.2.0"
@ -64,6 +80,41 @@ files = [
{file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
] ]
[[package]]
name = "idna"
version = "3.7"
requires_python = ">=3.5"
summary = "Internationalized Domain Names in Applications (IDNA)"
files = [
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
]
[[package]]
name = "multidict"
version = "6.0.5"
requires_python = ">=3.7"
summary = "multidict implementation"
files = [
{file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"},
{file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"},
{file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"},
{file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"},
{file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"},
{file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"},
{file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"},
]
[[package]] [[package]]
name = "mypy" name = "mypy"
version = "1.10.0" version = "1.10.0"
@ -215,6 +266,26 @@ files = [
{file = "python_mpd2-3.1.1-py2.py3-none-any.whl", hash = "sha256:86bf1100a0b135959d74a9a7a58cf0515bf30bb54eb25ae6fb8e175e50300fc3"}, {file = "python_mpd2-3.1.1-py2.py3-none-any.whl", hash = "sha256:86bf1100a0b135959d74a9a7a58cf0515bf30bb54eb25ae6fb8e175e50300fc3"},
] ]
[[package]]
name = "pytomlpp"
version = "1.0.13"
summary = "A python wrapper for toml++"
files = [
{file = "pytomlpp-1.0.13-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4710c72456c10a90e58084174312abef8f9652b0f91c240c008903c1bd99814d"},
{file = "pytomlpp-1.0.13-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b59acc12339a992404289ab7294f28ba06c7df3c2562e81d316a0e744ab4103b"},
{file = "pytomlpp-1.0.13-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:252e31a5e013a74b898784f4ffb8aa8068e136b910ad11f2af1ee8a5700e6e1e"},
{file = "pytomlpp-1.0.13-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:09e716c0f462d15f2334cecc736957777dd30f8a5bfa5cf8150679da7577d2fd"},
{file = "pytomlpp-1.0.13-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:19dbded2995370e802105fa6dce54ed60f79e58b4eb35fee7ef33f1fb5958f6c"},
{file = "pytomlpp-1.0.13-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f87f6c958309e4c2358b778902c80bd33611d1c392f1abe2c226e3a62909ca4"},
{file = "pytomlpp-1.0.13-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e285aca948b419301fdda1927723287ef28482752782c44c9ee8c57eae7a1dc8"},
{file = "pytomlpp-1.0.13-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:aad6ae19c056ea62a43fec82427ad4675b5c773dc255c4bdcf6da659cd7edff6"},
{file = "pytomlpp-1.0.13-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0e0b34b7a132856567714342e9a622f7be0b4c9bac561a6252f0f85626c1aa4b"},
{file = "pytomlpp-1.0.13-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac06ca7683f5a2737b3888ea1e38d6968abb24fab703bc7ceccbe589d5420e0c"},
{file = "pytomlpp-1.0.13-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35225c1d9d674df87b4682f04af97856049351c38822455b78258248d9309363"},
{file = "pytomlpp-1.0.13-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:dbc9208ac58ea2a9d5ebb77e69d54d146744007f4a704a3f4e56d9881d41ee1c"},
{file = "pytomlpp-1.0.13.tar.gz", hash = "sha256:a0bd639a8f624d1bdf5b3ea94363ca23dbfef38ab7b5b9348881a84afab434ad"},
]
[[package]] [[package]]
name = "redis" name = "redis"
version = "5.0.4" version = "5.0.4"
@ -259,3 +330,42 @@ files = [
{file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"},
{file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"},
] ]
[[package]]
name = "xdg-base-dirs"
version = "6.0.1"
requires_python = ">=3.10,<4.0"
summary = "Variables defined by the XDG Base Directory Specification"
files = [
{file = "xdg_base_dirs-6.0.1-py3-none-any.whl", hash = "sha256:63f6ebc1721ced2e86c340856e004ef829501a30a37e17079c52cfaf0e1741b9"},
{file = "xdg_base_dirs-6.0.1.tar.gz", hash = "sha256:b4c8f4ba72d1286018b25eea374ec6fbf4fddda3d4137edf50de95de53e195a6"},
]
[[package]]
name = "yarl"
version = "1.9.4"
requires_python = ">=3.7"
summary = "Yet another URL library"
dependencies = [
"idna>=2.0",
"multidict>=4.0",
]
files = [
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"},
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"},
{file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"},
{file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"},
{file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"},
{file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"},
{file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"},
]

View file

@ -10,6 +10,10 @@ dependencies = [
"attrs>=23.1.0", "attrs>=23.1.0",
"pyobjc-framework-MediaPlayer>=10.0", "pyobjc-framework-MediaPlayer>=10.0",
"python-mpd2>=3.1.0", "python-mpd2>=3.1.0",
"xdg-base-dirs>=6.0.1",
"pytomlpp>=1.0.13",
"apischema>=0.18.1",
"yarl>=1.9.4",
] ]
readme = "README.md" readme = "README.md"
@ -48,6 +52,8 @@ check = {composite = ['lint', 'typecheck']}
[tool.pdm.version] [tool.pdm.version]
source = "scm" source = "scm"
write_to = 'mpd_now_playable/__version__.py'
write_template = "__version__ = '{}'"
[build-system] [build-system]
requires = ["pdm-backend"] requires = ["pdm-backend"]

View file

@ -0,0 +1 @@
__version__ = '0.0.1'

View file

@ -2,10 +2,10 @@ from __future__ import annotations
from contextlib import suppress from contextlib import suppress
from typing import Any, Optional, TypeVar from typing import Any, Optional, TypeVar
from urllib.parse import parse_qsl, urlparse
from aiocache import Cache from aiocache import Cache
from aiocache.serializers import BaseSerializer, PickleSerializer from aiocache.serializers import BaseSerializer, PickleSerializer
from yarl import URL
T = TypeVar("T") T = TypeVar("T")
@ -28,24 +28,23 @@ class OrmsgpackSerializer(BaseSerializer):
return ormsgpack.unpackb(value) return ormsgpack.unpackb(value)
def make_cache(url: str, namespace: str = "") -> Cache[T]: def make_cache(url: URL, namespace: str = "") -> Cache[T]:
parsed_url = urlparse(url) backend = Cache.get_scheme_class(url.scheme)
backend = Cache.get_scheme_class(parsed_url.scheme)
if backend == Cache.MEMORY: if backend == Cache.MEMORY:
return Cache(backend) return Cache(backend)
kwargs: dict[str, Any] = dict(parse_qsl(parsed_url.query)) kwargs: dict[str, Any] = dict(url.query)
if parsed_url.path: if url.path:
kwargs.update(backend.parse_uri_path(parsed_url.path)) kwargs.update(backend.parse_uri_path(url.path))
if parsed_url.hostname: if url.host:
kwargs["endpoint"] = parsed_url.hostname kwargs["endpoint"] = url.host
if parsed_url.port: if url.port:
kwargs["port"] = parsed_url.port kwargs["port"] = url.port
if parsed_url.password: if url.password:
kwargs["password"] = parsed_url.password kwargs["password"] = url.password
namespace = ":".join(s for s in [kwargs.pop("namespace", ""), namespace] if s) namespace = ":".join(s for s in [kwargs.pop("namespace", ""), namespace] if s)

View file

@ -1,23 +1,19 @@
import asyncio import asyncio
from os import environ
from corefoundationasyncio import CoreFoundationEventLoop from corefoundationasyncio import CoreFoundationEventLoop
from .__version__ import __version__
from .cocoa.now_playing import CocoaNowPlaying from .cocoa.now_playing import CocoaNowPlaying
from .config.load import loadConfig
from .mpd.listener import MpdStateListener from .mpd.listener import MpdStateListener
async def listen() -> None: async def listen() -> None:
port = int(environ.get("MPD_PORT", "6600")) print(f"mpd-now-playable v{__version__}")
host = environ.get("MPD_HOST", "localhost") config = loadConfig()
password = environ.get("MPD_PASSWORD") listener = MpdStateListener(config.cache)
cache = environ.get("MPD_NOW_PLAYABLE_CACHE")
if password is None and "@" in host:
password, host = host.split("@", maxsplit=1)
listener = MpdStateListener(cache)
now_playing = CocoaNowPlaying(listener) now_playing = CocoaNowPlaying(listener)
await listener.start(host=host, port=port, password=password) await listener.start(config.mpd)
await listener.loop(now_playing) await listener.loop(now_playing)

View file

View file

@ -0,0 +1,44 @@
{
"$id": "https://cdn.00dani.me/m/schemata/mpd-now-playable/config-v1.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"definitions": {
"URL": {
"format": "uri",
"type": "string"
}
},
"properties": {
"$schema": {
"$ref": "#/definitions/URL"
},
"cache": {
"$ref": "#/definitions/URL"
},
"mpd": {
"additionalProperties": false,
"default": {
"host": "127.0.0.1",
"port": 6600
},
"properties": {
"host": {
"default": "127.0.0.1",
"format": "hostname",
"type": "string"
},
"password": {
"type": "string"
},
"port": {
"default": 6600,
"maximum": 65535,
"minimum": 1,
"type": "integer"
}
},
"type": "object"
}
},
"type": "object"
}

View file

@ -0,0 +1,28 @@
from dataclasses import field
from typing import NewType, Optional, TypeVar
from apischema import schema
from apischema.conversions import deserializer
from apischema.metadata import none_as_undefined
from yarl import URL
__all__ = ("Host", "Port", "optional")
T = TypeVar("T")
Host = NewType("Host", str)
schema(format="hostname")(Host)
Port = NewType("Port", int)
schema(min=1, max=65535)(Port)
schema(format="uri")(URL)
def optional() -> Optional[T]:
return field(default=None, metadata=none_as_undefined)
@deserializer
def from_yarl(url: str) -> URL:
return URL(url)

View file

@ -0,0 +1,34 @@
from os import environ
from apischema import deserialize
from pytomlpp import load
from xdg_base_dirs import xdg_config_home
from .model import Config
__all__ = "loadConfig"
def loadConfigFromFile() -> Config:
path = xdg_config_home() / "mpd-now-playable" / "config.toml"
data = load(path)
print(f"Loaded your configuration from {path}")
return deserialize(Config, data)
def loadConfigFromEnv() -> Config:
port = int(environ["MPD_PORT"]) if "MPD_PORT" in environ else None
host = environ.get("MPD_HOST")
password = environ.get("MPD_PASSWORD")
cache = environ.get("MPD_NOW_PLAYABLE_CACHE")
if password is None and host is not None and "@" in host:
password, host = host.split("@", maxsplit=1)
data = {"cache": cache, "mpd": {"port": port, "host": host, "password": password}}
return deserialize(Config, data)
def loadConfig() -> Config:
try:
return loadConfigFromFile()
except FileNotFoundError:
return loadConfigFromEnv()

View file

@ -0,0 +1,26 @@
from dataclasses import dataclass, field
from typing import Optional
from apischema import alias
from yarl import URL
from .fields import Host, Port, optional
__all__ = ("MpdConfig", "Config")
@dataclass(frozen=True)
class MpdConfig:
password: Optional[str] = optional()
host: Host = Host("127.0.0.1")
port: Port = Port(6600)
@dataclass(frozen=True)
class Config:
schema: URL = field(
default=URL("https://cdn.00dani.me/m/schemata/mpd-now-playable/config-v1.json"),
metadata=alias("$schema"),
)
cache: Optional[URL] = optional()
mpd: MpdConfig = field(default_factory=MpdConfig)

View file

@ -0,0 +1,28 @@
from json import dump
from pathlib import Path
from pprint import pp
from typing import Any, Mapping
from apischema.json_schema import JsonSchemaVersion, deserialization_schema
from .model import Config
def generate() -> Mapping[str, Any]:
return deserialization_schema(Config, version=JsonSchemaVersion.DRAFT_7)
def write() -> None:
schema = dict(generate())
schema["$id"] = Config.schema.human_repr()
schema_file = Path(__file__).parent / Config.schema.name
print(f"Writing this schema to {schema_file}")
pp(schema)
with open(schema_file, "w") as fp:
dump(schema, fp, indent="\t", sort_keys=True)
fp.write("\n")
if __name__ == "__main__":
write()

View file

@ -2,6 +2,8 @@ from __future__ import annotations
from typing import TypedDict from typing import TypedDict
from yarl import URL
from ..async_tools import run_background_task from ..async_tools import run_background_task
from ..cache import Cache, make_cache from ..cache import Cache, make_cache
from .types import CurrentSongResponse, MpdStateHandler from .types import CurrentSongResponse, MpdStateHandler
@ -23,12 +25,15 @@ def calc_track_key(song: CurrentSongResponse) -> str:
return song["file"] return song["file"]
MEMORY = URL("memory://")
class MpdArtworkCache: class MpdArtworkCache:
mpd: MpdStateHandler mpd: MpdStateHandler
album_cache: Cache[ArtCacheEntry] album_cache: Cache[ArtCacheEntry]
track_cache: Cache[ArtCacheEntry] track_cache: Cache[ArtCacheEntry]
def __init__(self, mpd: MpdStateHandler, cache_url: str = "memory://"): def __init__(self, mpd: MpdStateHandler, cache_url: URL = MEMORY):
self.mpd = mpd self.mpd = mpd
self.album_cache = make_cache(cache_url, "album") self.album_cache = make_cache(cache_url, "album")
self.track_cache = make_cache(cache_url, "track") self.track_cache = make_cache(cache_url, "track")

View file

@ -4,7 +4,9 @@ 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 yarl import URL
from ..config.model import MpdConfig
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 ..type_tools import convert_if_exists
@ -44,20 +46,18 @@ class MpdStateListener(Player):
art_cache: MpdArtworkCache art_cache: MpdArtworkCache
idle_count = 0 idle_count = 0
def __init__(self, cache: str | None = None) -> None: def __init__(self, cache: URL | None = None) -> None:
self.client = MPDClient() self.client = MPDClient()
self.art_cache = ( self.art_cache = (
MpdArtworkCache(self, cache) if cache else MpdArtworkCache(self) MpdArtworkCache(self, cache) if cache else MpdArtworkCache(self)
) )
async def start( async def start(self, conf: MpdConfig) -> None:
self, host: str = "localhost", port: int = 6600, password: str | None = None print(f"Connecting to MPD server {conf.host}:{conf.port}...")
) -> None: await self.client.connect(conf.host, conf.port)
print(f"Connecting to MPD server {host}:{port}...") if conf.password is not None:
await self.client.connect(host, port)
if password is not None:
print("Authorising to MPD with your password...") print("Authorising to MPD with your password...")
await self.client.password(password) await self.client.password(conf.password)
print(f"Connected to MPD v{self.client.mpd_version}") print(f"Connected to MPD v{self.client.mpd_version}")
async def refresh(self) -> None: async def refresh(self) -> None: