Compare commits

..

No commits in common. "fcf7254e64863d13822bfa3f72351b34c7695a46" and "2f70c6f7fa1ff16a8a79f6f977785ba37c296870" have entirely different histories.

8 changed files with 87 additions and 142 deletions

116
pdm.lock
View file

@ -183,106 +183,100 @@ files = [
[[package]] [[package]]
name = "pyobjc-core" name = "pyobjc-core"
version = "10.3.1" version = "10.2"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Python<->ObjC Interoperability Module" summary = "Python<->ObjC Interoperability Module"
files = [ files = [
{file = "pyobjc_core-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6ff5823d13d0a534cdc17fa4ad47cf5bee4846ce0fd27fc40012e12b46db571b"}, {file = "pyobjc-core-10.2.tar.gz", hash = "sha256:0153206e15d0e0d7abd53ee8a7fbaf5606602a032e177a028fc8589516a8771c"},
{file = "pyobjc_core-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2581e8e68885bcb0e11ec619e81ef28e08ee3fac4de20d8cc83bc5af5bcf4a90"}, {file = "pyobjc_core-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a70546246177c23acb323c9324330e37638f1a0a3d13664abcba3bb75e43012c"},
{file = "pyobjc_core-10.3.1.tar.gz", hash = "sha256:b204a80ccc070f9ab3f8af423a3a25a6fd787e228508d00c4c30f8ac538ba720"},
] ]
[[package]] [[package]]
name = "pyobjc-framework-avfoundation" name = "pyobjc-framework-avfoundation"
version = "10.3.1" version = "10.2"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Wrappers for the framework AVFoundation on macOS" summary = "Wrappers for the framework AVFoundation on macOS"
dependencies = [ dependencies = [
"pyobjc-core>=10.3.1", "pyobjc-core>=10.2",
"pyobjc-framework-Cocoa>=10.3.1", "pyobjc-framework-Cocoa>=10.2",
"pyobjc-framework-CoreAudio>=10.3.1", "pyobjc-framework-CoreAudio>=10.2",
"pyobjc-framework-CoreMedia>=10.3.1", "pyobjc-framework-CoreMedia>=10.2",
"pyobjc-framework-Quartz>=10.3.1", "pyobjc-framework-Quartz>=10.2",
] ]
files = [ files = [
{file = "pyobjc_framework_AVFoundation-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:0896f6650df35f0229d1fb3aa3fbf632647dd815d4921cb61d9eb7fa26be6237"}, {file = "pyobjc-framework-AVFoundation-10.2.tar.gz", hash = "sha256:4d394014f2477c0c6a596dbb01ef5d92944058d0e0d954ce6121a676ae9395ce"},
{file = "pyobjc_framework_AVFoundation-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:0cb27cc95288d95df7504adf474596f8855de7fa7798bbc1bbfbdfbbcb940952"}, {file = "pyobjc_framework_AVFoundation-10.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5f20c11a8870d7d58f0e4f20f918e45e922520aa5c9dbee61dc59ca4bc4bd26d"},
{file = "pyobjc_framework_AVFoundation-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb606ef0806d952a04db45ae691167678121df1d8d7c2f8cc73745695097033"}, {file = "pyobjc_framework_AVFoundation-10.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:283355d1f96c184e5f5f479870eb3bf510747307697616737bbc5d224af3abcb"},
{file = "pyobjc_framework_AVFoundation-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:00889eb915479aa9ea392cdd241e4b635ae0fa3114f043d08cf3e1d1b5a23bd4"}, {file = "pyobjc_framework_AVFoundation-10.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a63a4e26c088023b0b1cb29d7da2c2246aa8eca2b56767fe1cc36a18c6fb650b"},
{file = "pyobjc_framework_avfoundation-10.3.1.tar.gz", hash = "sha256:2f94bee3a4217b46d9416cad066e4f357bf0f344079c328736114451ae19ae94"},
] ]
[[package]] [[package]]
name = "pyobjc-framework-cocoa" name = "pyobjc-framework-cocoa"
version = "10.3.1" version = "10.2"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Wrappers for the Cocoa frameworks on macOS" summary = "Wrappers for the Cocoa frameworks on macOS"
dependencies = [ dependencies = [
"pyobjc-core>=10.3.1", "pyobjc-core>=10.2",
] ]
files = [ files = [
{file = "pyobjc_framework_Cocoa-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:11b4e0bad4bbb44a4edda128612f03cdeab38644bbf174de0c13129715497296"}, {file = "pyobjc-framework-Cocoa-10.2.tar.gz", hash = "sha256:6383141379636b13855dca1b39c032752862b829f93a49d7ddb35046abfdc035"},
{file = "pyobjc_framework_Cocoa-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:de5e62e5ccf2871a94acf3bf79646b20ea893cc9db78afa8d1fe1b0d0f7cbdb0"}, {file = "pyobjc_framework_Cocoa-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:18886d5013cd7dc7ecd6e0df5134c767569b5247fc10a5e293c72ee3937b217b"},
{file = "pyobjc_framework_cocoa-10.3.1.tar.gz", hash = "sha256:1cf20714daaa986b488fb62d69713049f635c9d41a60c8da97d835710445281a"},
] ]
[[package]] [[package]]
name = "pyobjc-framework-coreaudio" name = "pyobjc-framework-coreaudio"
version = "10.3.1" version = "10.2"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Wrappers for the framework CoreAudio on macOS" summary = "Wrappers for the framework CoreAudio on macOS"
dependencies = [ dependencies = [
"pyobjc-core>=10.3.1", "pyobjc-core>=10.2",
"pyobjc-framework-Cocoa>=10.3.1", "pyobjc-framework-Cocoa>=10.2",
] ]
files = [ files = [
{file = "pyobjc_framework_CoreAudio-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e0aeca61a425d846afc92350ffba970e1e503469182f5f0ea436de98cfd00d96"}, {file = "pyobjc-framework-CoreAudio-10.2.tar.gz", hash = "sha256:5e97ae7a65be85aee83aef004b31146c5fbf28325d870362959f7312b303fb67"},
{file = "pyobjc_framework_CoreAudio-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:21cecd1b023b6960d1071c106345656de45a399196701b07c7e5c076321f25ad"}, {file = "pyobjc_framework_CoreAudio-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32608ce881b5e6a7cb332c2732762fa93829ac495c5344c33e8e8b72a2431b23"},
{file = "pyobjc_framework_coreaudio-10.3.1.tar.gz", hash = "sha256:c81c709bf955aea474a4de380b187f3c2e56c864ca7de520b08362b73070c795"},
] ]
[[package]] [[package]]
name = "pyobjc-framework-coremedia" name = "pyobjc-framework-coremedia"
version = "10.3.1" version = "10.2"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Wrappers for the framework CoreMedia on macOS" summary = "Wrappers for the framework CoreMedia on macOS"
dependencies = [ dependencies = [
"pyobjc-core>=10.3.1", "pyobjc-core>=10.2",
"pyobjc-framework-Cocoa>=10.3.1", "pyobjc-framework-Cocoa>=10.2",
] ]
files = [ files = [
{file = "pyobjc_framework_CoreMedia-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c6eaf48f202becab10679e3b5dd62607ddec2739495db45882524592cabf3997"}, {file = "pyobjc-framework-CoreMedia-10.2.tar.gz", hash = "sha256:d726d86636217eaa135e5626d05c7eb0f9b4529ce1ed504e08069fe1e0421483"},
{file = "pyobjc_framework_CoreMedia-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:98b8ab02e6ec979007b706e05166e16bd61121e47fbc6e449f4b2de2c58f3cb6"}, {file = "pyobjc_framework_CoreMedia-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7fa13166a14d384bb6442e5f635310dd075c2a4b3b3bd67ac63b1e2e1fd2d65e"},
{file = "pyobjc_framework_coremedia-10.3.1.tar.gz", hash = "sha256:bc3e0cddf5f546b5d8407d8f46b203f1bd4396ad5dbfdc0d064a560b3fe31221"},
] ]
[[package]] [[package]]
name = "pyobjc-framework-mediaplayer" name = "pyobjc-framework-mediaplayer"
version = "10.3.1" version = "10.2"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Wrappers for the framework MediaPlayer on macOS" summary = "Wrappers for the framework MediaPlayer on macOS"
dependencies = [ dependencies = [
"pyobjc-core>=10.3.1", "pyobjc-core>=10.2",
"pyobjc-framework-AVFoundation>=10.3.1", "pyobjc-framework-AVFoundation>=10.2",
] ]
files = [ files = [
{file = "pyobjc_framework_MediaPlayer-10.3.1-py2.py3-none-any.whl", hash = "sha256:5b428cc28e57c1778bd431156c3adb948650f7503f266689559d0ece94b34e8a"}, {file = "pyobjc-framework-MediaPlayer-10.2.tar.gz", hash = "sha256:4b6d296b084e01fb6e5c782b7b6308077db09f4051f50b0a6c3298ffbd1f1d70"},
{file = "pyobjc_framework_mediaplayer-10.3.1.tar.gz", hash = "sha256:97043df5ef89d4fbe217813e8f4ee1e226d8a43dee4eac00fff95e6b8a7772be"}, {file = "pyobjc_framework_MediaPlayer-10.2-py2.py3-none-any.whl", hash = "sha256:c501ea19380bfbf6b04fbe909fcfe9a78c5ff2a9b58dae87be259066b1ae3521"},
] ]
[[package]] [[package]]
name = "pyobjc-framework-quartz" name = "pyobjc-framework-quartz"
version = "10.3.1" version = "10.2"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Wrappers for the Quartz frameworks on macOS" summary = "Wrappers for the Quartz frameworks on macOS"
dependencies = [ dependencies = [
"pyobjc-core>=10.3.1", "pyobjc-core>=10.2",
"pyobjc-framework-Cocoa>=10.3.1", "pyobjc-framework-Cocoa>=10.2",
] ]
files = [ files = [
{file = "pyobjc_framework_Quartz-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ca35f92486869a41847a1703bb176aab8a53dbfd8e678d1f4d68d8e6e1581c71"}, {file = "pyobjc-framework-Quartz-10.2.tar.gz", hash = "sha256:9b947e081f5bd6cd01c99ab5d62c36500d2d6e8d3b87421c1cbb7f9c885555eb"},
{file = "pyobjc_framework_Quartz-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:00a0933267e3a46ea4afcc35d117b2efb920f06de797fa66279c52e7057e3590"}, {file = "pyobjc_framework_Quartz-10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3e8e33246d966c2bd7f5ee2cf3b431582fa434a6ec2b6dbe580045ebf1f55be5"},
{file = "pyobjc_framework_quartz-10.3.1.tar.gz", hash = "sha256:b6d7e346d735c9a7f147cd78e6da79eeae416a0b7d3874644c83a23786c6f886"},
] ]
[[package]] [[package]]
@ -327,27 +321,27 @@ files = [
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.4.10" version = "0.4.4"
requires_python = ">=3.7" requires_python = ">=3.7"
summary = "An extremely fast Python linter and code formatter, written in Rust." summary = "An extremely fast Python linter and code formatter, written in Rust."
files = [ files = [
{file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"}, {file = "ruff-0.4.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:29d44ef5bb6a08e235c8249294fa8d431adc1426bfda99ed493119e6f9ea1bf6"},
{file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"}, {file = "ruff-0.4.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c4efe62b5bbb24178c950732ddd40712b878a9b96b1d02b0ff0b08a090cbd891"},
{file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"}, {file = "ruff-0.4.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c8e2f1e8fc12d07ab521a9005d68a969e167b589cbcaee354cb61e9d9de9c15"},
{file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"}, {file = "ruff-0.4.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60ed88b636a463214905c002fa3eaab19795679ed55529f91e488db3fe8976ab"},
{file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"}, {file = "ruff-0.4.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b90fc5e170fc71c712cc4d9ab0e24ea505c6a9e4ebf346787a67e691dfb72e85"},
{file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"}, {file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8e7e6ebc10ef16dcdc77fd5557ee60647512b400e4a60bdc4849468f076f6eef"},
{file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"}, {file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9ddb2c494fb79fc208cd15ffe08f32b7682519e067413dbaf5f4b01a6087bcd"},
{file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"}, {file = "ruff-0.4.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c51c928a14f9f0a871082603e25a1588059b7e08a920f2f9fa7157b5bf08cfe9"},
{file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"}, {file = "ruff-0.4.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5eb0a4bfd6400b7d07c09a7725e1a98c3b838be557fee229ac0f84d9aa49c36"},
{file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"}, {file = "ruff-0.4.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b1867ee9bf3acc21778dcb293db504692eda5f7a11a6e6cc40890182a9f9e595"},
{file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"}, {file = "ruff-0.4.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1aecced1269481ef2894cc495647392a34b0bf3e28ff53ed95a385b13aa45768"},
{file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"}, {file = "ruff-0.4.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9da73eb616b3241a307b837f32756dc20a0b07e2bcb694fec73699c93d04a69e"},
{file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"}, {file = "ruff-0.4.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:958b4ea5589706a81065e2a776237de2ecc3e763342e5cc8e02a4a4d8a5e6f95"},
{file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"}, {file = "ruff-0.4.4-py3-none-win32.whl", hash = "sha256:cb53473849f011bca6e754f2cdf47cafc9c4f4ff4570003a0dad0b9b6890e876"},
{file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"}, {file = "ruff-0.4.4-py3-none-win_amd64.whl", hash = "sha256:424e5b72597482543b684c11def82669cc6b395aa8cc69acc1858b5ef3e5daae"},
{file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"}, {file = "ruff-0.4.4-py3-none-win_arm64.whl", hash = "sha256:39df0537b47d3b597293edbb95baf54ff5b49589eb7ff41926d8243caa995ea6"},
{file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"}, {file = "ruff-0.4.4.tar.gz", hash = "sha256:f87ea42d5cdebdc6a69761a9d0bc83ae9b3b30d0ad78952005ba6568d6c022af"},
] ]
[[package]] [[package]]

View file

@ -52,6 +52,7 @@ build-backend = "pdm.backend"
mypy_path = 'stubs' mypy_path = 'stubs'
[tool.ruff.lint] [tool.ruff.lint]
ignore-init-module-imports = true
select = [ select = [
# pycodestyle # pycodestyle
"E4", # import "E4", # import

View file

@ -78,12 +78,6 @@ def playback_state_to_cocoa(state: PlaybackState) -> MPMusicPlaybackState:
return mapping[state] return mapping[state]
def join_plural_field(field: list[str]) -> str | None:
if field:
return ", ".join(field)
return None
def song_to_media_item(song: Song) -> NSMutableDictionary: def song_to_media_item(song: Song) -> NSMutableDictionary:
nowplaying_info = nothing_to_media_item() nowplaying_info = nothing_to_media_item()
nowplaying_info[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaTypeAudio nowplaying_info[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaTypeAudio
@ -94,12 +88,12 @@ def song_to_media_item(song: Song) -> NSMutableDictionary:
nowplaying_info[MPMediaItemPropertyPersistentID] = song_to_persistent_id(song) nowplaying_info[MPMediaItemPropertyPersistentID] = song_to_persistent_id(song)
nowplaying_info[MPMediaItemPropertyTitle] = song.title nowplaying_info[MPMediaItemPropertyTitle] = song.title
nowplaying_info[MPMediaItemPropertyArtist] = join_plural_field(song.artist) nowplaying_info[MPMediaItemPropertyArtist] = song.artist
nowplaying_info[MPMediaItemPropertyAlbumTitle] = join_plural_field(song.album) nowplaying_info[MPMediaItemPropertyAlbumTitle] = song.album
nowplaying_info[MPMediaItemPropertyAlbumTrackNumber] = song.track nowplaying_info[MPMediaItemPropertyAlbumTrackNumber] = song.track
nowplaying_info[MPMediaItemPropertyDiscNumber] = song.disc nowplaying_info[MPMediaItemPropertyDiscNumber] = song.disc
nowplaying_info[MPMediaItemPropertyGenre] = join_plural_field(song.genre) nowplaying_info[MPMediaItemPropertyGenre] = song.genre
nowplaying_info[MPMediaItemPropertyComposer] = join_plural_field(song.composer) nowplaying_info[MPMediaItemPropertyComposer] = song.composer
nowplaying_info[MPMediaItemPropertyPlaybackDuration] = song.duration nowplaying_info[MPMediaItemPropertyPlaybackDuration] = song.duration
# MPD can't play back music at different rates, so we just want to set it # MPD can't play back music at different rates, so we just want to set it

View file

@ -6,7 +6,6 @@ from yarl import URL
from ..cache import Cache, make_cache from ..cache import Cache, make_cache
from ..tools.asyncio import run_background_task from ..tools.asyncio import run_background_task
from ..tools.types import un_maybe_plural
from .types import CurrentSongResponse, MpdStateHandler from .types import CurrentSongResponse, MpdStateHandler
CACHE_TTL = 60 * 60 # seconds = 1 hour CACHE_TTL = 60 * 60 # seconds = 1 hour
@ -17,11 +16,9 @@ class ArtCacheEntry(TypedDict):
def calc_album_key(song: CurrentSongResponse) -> str: def calc_album_key(song: CurrentSongResponse) -> str:
artist = sorted( artist = song.get("albumartist", song.get("artist", "Unknown Artist"))
un_maybe_plural(song.get("albumartist", song.get("artist", "Unknown Artist"))) album = song.get("album", "Unknown Album")
) return ":".join(t.replace(":", "-") for t in (artist, album))
album = sorted(un_maybe_plural(song.get("album", "Unknown Album")))
return ":".join(";".join(t).replace(":", "-") for t in (artist, album))
def calc_track_key(song: CurrentSongResponse) -> str: def calc_track_key(song: CurrentSongResponse) -> str:

View file

@ -9,7 +9,7 @@ from yarl import URL
from ..config.model import MpdConfig 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 ..tools.types import convert_if_exists, un_maybe_plural from ..tools.types import convert_if_exists
from .artwork_cache import MpdArtworkCache from .artwork_cache import MpdArtworkCache
from .types import CurrentSongResponse, StatusResponse from .types import CurrentSongResponse, StatusResponse
@ -27,11 +27,11 @@ def mpd_current_to_song(
current.get("musicbrainz_releasetrackid"), UUID current.get("musicbrainz_releasetrackid"), UUID
), ),
title=current.get("title"), title=current.get("title"),
artist=un_maybe_plural(current.get("artist")), artist=current.get("artist"),
album=un_maybe_plural(current.get("album")), album=current.get("album"),
album_artist=un_maybe_plural(current.get("albumartist")), album_artist=current.get("albumartist"),
composer=un_maybe_plural(current.get("composer")), composer=current.get("composer"),
genre=un_maybe_plural(current.get("genre")), genre=current.get("genre"),
track=convert_if_exists(current.get("track"), int), track=convert_if_exists(current.get("track"), int),
disc=convert_if_exists(current.get("disc"), int), disc=convert_if_exists(current.get("disc"), int),
duration=float(status["duration"]), duration=float(status["duration"]),

View file

@ -1,7 +1,5 @@
from typing import Literal, NotRequired, Protocol, TypedDict from typing import Literal, NotRequired, Protocol, TypedDict
from ..tools.types import MaybePlural
class MpdStateHandler(Protocol): class MpdStateHandler(Protocol):
async def get_art(self, file: str) -> bytes | None: ... async def get_art(self, file: str) -> bytes | None: ...
@ -49,19 +47,19 @@ class StatusResponse(TypedDict):
# tagged, since then it can pass more information on to Now Playing, but it # tagged, since then it can pass more information on to Now Playing, but it
# should work fine with completely untagged music too. # should work fine with completely untagged music too.
class CurrentSongTags(TypedDict, total=False): class CurrentSongTags(TypedDict, total=False):
artist: MaybePlural[str] artist: str
albumartist: MaybePlural[str] albumartist: str
artistsort: MaybePlural[str] artistsort: str
albumartistsort: MaybePlural[str] albumartistsort: str
title: str title: str
album: MaybePlural[str] album: str
track: str track: str
date: str date: str
originaldate: str originaldate: str
composer: MaybePlural[str] composer: str
disc: str disc: str
label: str label: str
genre: MaybePlural[str] genre: str
musicbrainz_albumid: str musicbrainz_albumid: str
musicbrainz_albumartistid: str musicbrainz_albumartistid: str
musicbrainz_releasetrackid: str musicbrainz_releasetrackid: str

View file

@ -21,17 +21,18 @@ class Song:
musicbrainz_trackid: UUID | None musicbrainz_trackid: UUID | None
musicbrainz_releasetrackid: UUID | None musicbrainz_releasetrackid: UUID | None
title: str | None title: str | None
artist: list[str] artist: str | None
composer: list[str] composer: str | None
album: list[str] album: str | None
album_artist: list[str] album_artist: str | None
track: int | None track: int | None
disc: int | None disc: int | None
genre: list[str] 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>")
class SongListener(Protocol): class SongListener(Protocol):
def update(self, song: Song | None) -> None: ... def update(self, song: Song | None) -> None:
...

View file

@ -1,51 +1,11 @@
from collections.abc import Callable from typing import Callable, TypeVar
from typing import Any, TypeAlias, TypeVar
__all__ = ( __all__ = ("convert_if_exists",)
"AnyExceptList",
"MaybePlural",
"convert_if_exists",
"un_maybe_plural",
)
# Accept as many types as possible that are not lists. Yes, having to identify T = TypeVar("T")
# them manually like this is kind of a pain! TypeScript can express this
# restriction using a conditional type, and with another conditional type it
# can correctly type a version of un_maybe_plural that *does* accept lists, but
# Python's type system isn't quite that bonkers powerful. Yet?
AnyExceptList = (
int
| float
| complex
| bool
| str
| bytes
| set[Any]
| dict[Any, Any]
| tuple[Any, ...]
| Callable[[Any], Any]
| type
)
U = TypeVar("U") def convert_if_exists(value: str | None, converter: Callable[[str], T]) -> T | None:
def convert_if_exists(value: str | None, converter: Callable[[str], U]) -> U | None:
if value is None: if value is None:
return None return None
return converter(value) return converter(value)
T = TypeVar("T", bound=AnyExceptList)
MaybePlural: TypeAlias = list[T] | T
def un_maybe_plural(value: MaybePlural[T] | None) -> list[T]:
match value:
case None:
return []
case list(values):
return values[:]
case item:
return [item]