From 932c63b704f7c63a58eed94c726006b3973fed83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Thu, 21 May 2026 23:44:16 +0200 Subject: [PATCH] music-assistant: 2.8.7 -> 2.9.4 something tried to use sched_setscheduler on startup, so @resources must be allowed. Could be torch but since that is a general requirement we cannot make this conditional. Changelog: https://github.com/music-assistant/server/releases/tag/2.9.0 Changelog: https://github.com/music-assistant/server/releases/tag/2.9.1 Changelog: https://github.com/music-assistant/server/releases/tag/2.9.2 Changelog: https://github.com/music-assistant/server/releases/tag/2.9.3 Changelog: https://github.com/music-assistant/server/releases/tag/2.9.4 Diff: https://github.com/music-assistant/server/compare/2.8.7...2.9.4 --- .../services/audio/music-assistant.nix | 3 +- .../music-assistant/dont-install-deps.patch | 2 +- pkgs/by-name/mu/music-assistant/frontend.nix | 4 +- pkgs/by-name/mu/music-assistant/package.nix | 41 ++++++- pkgs/by-name/mu/music-assistant/providers.nix | 109 ++++++++++++++++-- .../mu/music-assistant/update-providers.py | 25 +++- 6 files changed, 165 insertions(+), 19 deletions(-) diff --git a/nixos/modules/services/audio/music-assistant.nix b/nixos/modules/services/audio/music-assistant.nix index 505e57c163e9..8397c85592ba 100644 --- a/nixos/modules/services/audio/music-assistant.nix +++ b/nixos/modules/services/audio/music-assistant.nix @@ -171,6 +171,7 @@ in DevicePolicy = "closed"; LockPersonality = true; # breaks pyopenssl's cffi calls, used in remote access feature + # not compatible with llvmlite which is required by numba -> librosa MemoryDenyWriteExecute = false; ProcSubset = "pid"; ProtectClock = true; @@ -194,7 +195,7 @@ in SystemCallArchitectures = "native"; SystemCallFilter = [ "@system-service" - "~@privileged @resources" + "~@privileged" "mbind" ] ++ lib.optionals useYTMusic [ diff --git a/pkgs/by-name/mu/music-assistant/dont-install-deps.patch b/pkgs/by-name/mu/music-assistant/dont-install-deps.patch index d074e18fa67c..0df4cf58b369 100644 --- a/pkgs/by-name/mu/music-assistant/dont-install-deps.patch +++ b/pkgs/by-name/mu/music-assistant/dont-install-deps.patch @@ -43,7 +43,7 @@ index 0ac8a70f..5d74e25b 100644 """Set up the YTMusic provider.""" logging.getLogger("yt_dlp").setLevel(self.logger.level + 10) - await self._install_packages() - self._cookie = self.config.get_value(CONF_COOKIE) + self._cookie = str(self.config.get_value(CONF_COOKIE)) self._po_token_server_url = ( self.config.get_value(CONF_PO_TOKEN_SERVER_URL) or DEFAULT_PO_TOKEN_SERVER_URL @@ -1103,8 +1102,6 @@ async def _install_packages(self) -> None: diff --git a/pkgs/by-name/mu/music-assistant/frontend.nix b/pkgs/by-name/mu/music-assistant/frontend.nix index ab091182c71e..e6cf66afac4d 100644 --- a/pkgs/by-name/mu/music-assistant/frontend.nix +++ b/pkgs/by-name/mu/music-assistant/frontend.nix @@ -8,13 +8,13 @@ buildPythonPackage rec { pname = "music-assistant-frontend"; - version = "2.17.146"; + version = "2.17.186"; pyproject = true; src = fetchPypi { pname = "music_assistant_frontend"; inherit version; - hash = "sha256-VU2SXL6SLFiKntmJJtCabBOYOeImYFPHYxvMPmnhmEc="; + hash = "sha256-dNGzXDRZuQLRkMY0erjJZE4h26yFP4Fdn9a3K6T0RvM="; }; build-system = [ setuptools ]; diff --git a/pkgs/by-name/mu/music-assistant/package.nix b/pkgs/by-name/mu/music-assistant/package.nix index 3494bc89cdce..ce6c3ad5ac4f 100644 --- a/pkgs/by-name/mu/music-assistant/package.nix +++ b/pkgs/by-name/mu/music-assistant/package.nix @@ -6,6 +6,7 @@ ffmpeg_7-headless, nixosTests, replaceVars, + writableTmpDirAsHomeHook, providers ? [ ], }: @@ -15,10 +16,10 @@ let music-assistant-frontend = prev.callPackage ./frontend.nix { }; music-assistant-models = final.music-assistant-models.overridePythonAttrs (oldAttrs: { - version = "1.1.115"; + version = "1.1.129"; src = oldAttrs.src.override { - hash = "sha256-oEXL0B8JNH4PcltpES375ov7QGs+gtYKlMGr1B7BlKY="; + hash = "sha256-6gVHlFTt/bsj4nUGPS6HDUQ7zczpfos75U6l4Yk9W6k="; }; }); } @@ -39,14 +40,14 @@ assert pythonPackages.buildPythonApplication rec { pname = "music-assistant"; - version = "2.8.7"; + version = "2.9.4"; pyproject = true; src = fetchFromGitHub { owner = "music-assistant"; repo = "server"; tag = version; - hash = "sha256-m91q/8XYoZ5Azu79fKD0euRCuf29w3vj5cxdFheDsmI="; + hash = "sha256-PiSBghhlxknijRqghkO8wn1CB2XqaJrjrvGNvZUlNbo="; }; patches = [ @@ -82,6 +83,11 @@ pythonPackages.buildPythonApplication rec { ]; postPatch = '' + # Undo Python 3.14 only syntax + substituteInPlace music_assistant/controllers/streams/controller.py \ + --replace-fail "except BrokenPipeError, ConnectionResetError, ConnectionError:" "except (BrokenPipeError, ConnectionResetError, ConnectionError):" \ + --replace-fail "except BrokenPipeError, ConnectionResetError:" "except (BrokenPipeError, ConnectionResetError):" + substituteInPlace pyproject.toml \ --replace-fail "0.0.0" "${version}" \ --replace-fail "==" ">=" @@ -110,6 +116,7 @@ pythonPackages.buildPythonApplication rec { "mashumaro" "orjson" "xmltodict" + "zeroconf" ]; pythonRemoveDeps = [ @@ -140,6 +147,7 @@ pythonPackages.buildPythonApplication rec { ifaddr librosa mashumaro + modern-colorthief music-assistant-frontend music-assistant-models mutagen @@ -151,6 +159,8 @@ pythonPackages.buildPythonApplication rec { pyjwt python-slugify shortuuid + torch + torchaudio unidecode xmltodict zeroconf @@ -175,18 +185,36 @@ pythonPackages.buildPythonApplication rec { with pythonPackages; [ pytestCheckHook + writableTmpDirAsHomeHook ] ++ lib.concatAttrValues optional-dependencies - ++ (lib.concatMap (provider: providerPackages.${provider} python.pkgs) [ + ++ (lib.concatMap (provider: providerPackages.${provider} pythonPackages) [ + "acoustid_lookup" "audible" "dlna" + "fastmcp_server" "jellyfin" + "mpd" + "msx_bridge" "opensubsonic" "sendspin" + "smart_fades" "snapcast" + "sonic_analysis" + "sonic_similarity" "tidal" + "wiim" + "ytmusic" ]); + preCheck = '' + export NUMBA_CACHE_DIR=$(mktemp -d) + + # required for smart_fades tests + mkdir -p $HOME/.cache/torch/hub/checkpoints/ + cp ${pythonPackages.beat-this.passthru.small0Ckpt} $HOME/.cache/torch/hub/checkpoints/beat_this-small0.ckpt + ''; + disabledTestPaths = [ # no multicast support in build sandbox: # "OSError: [Errno 19] No such device" @@ -194,9 +222,12 @@ pythonPackages.buildPythonApplication rec { # provider is missing dependencies "tests/providers/apple_music" "tests/providers/bandcamp" + "tests/providers/hue_entertainment" "tests/providers/kion_music" "tests/providers/nicovideo" + "tests/providers/qqmusic" "tests/providers/yandex_music" + "tests/providers/yandex_ynison" "tests/providers/zvuk_music" # mocking music_assistant.providers.airplay.pairing.AirPlayPairing does not work "tests/providers/airplay/test_player.py::test_start_pairing__pin_decision" diff --git a/pkgs/by-name/mu/music-assistant/providers.nix b/pkgs/by-name/mu/music-assistant/providers.nix index 4acb6678c682..8c75a4f5c2c2 100644 --- a/pkgs/by-name/mu/music-assistant/providers.nix +++ b/pkgs/by-name/mu/music-assistant/providers.nix @@ -1,8 +1,27 @@ # Do not edit manually, run ./update-providers.py { - version = "2.8.7"; + version = "2.9.4"; + builtins = [ + "builtin" + "coverartarchive" + "fanarttv" + "itunes_artwork" + "local_audio" + "loudness_analysis" + "lrclib" + "musicbrainz" + "sendspin" + "sync_group" + "theaudiodb" + "universal_player" + "wikipedia" + ]; providers = { + acoustid_lookup = + ps: with ps; [ + pyacoustid + ]; airplay = ps: with ps; [ srptools @@ -65,6 +84,10 @@ ]; fanarttv = ps: [ ]; + fastmcp_server = + ps: with ps; [ + fastmcp + ]; filesystem_local = ps: [ ]; filesystem_nfs = ps: [ @@ -89,10 +112,14 @@ ps: with ps; [ pyheos ]; + hue_entertainment = ps: [ + ]; # missing hue-entertainment ibroadcast = ps: [ ]; # missing ibroadcastaio internet_archive = ps: [ ]; + itunes_artwork = ps: [ + ]; itunes_podcasts = ps: [ ]; jellyfin = @@ -101,6 +128,8 @@ ]; kion_music = ps: [ ]; # missing yandex-music + lastfm_recommendations = ps: [ + ]; lastfm_scrobble = ps: with ps; [ pylast @@ -109,16 +138,40 @@ ps: with ps; [ liblistenbrainz ]; + local_audio = + ps: with ps; [ + sounddevice + ]; + loudness_analysis = ps: [ + ]; lrclib = ps: [ ]; + motherearthradio = ps: [ + ]; + mpd = + ps: with ps; [ + python-mpd2 + ]; + msx_bridge = + ps: with ps; [ + pydantic + ]; musicbrainz = ps: [ ]; musiccast = ps: with ps; [ aiomusiccast ]; - nicovideo = ps: [ - ]; # missing niconico.py-ma + musicme = ps: [ + ]; + neteasecloudmusic = ps: [ + ]; + nicovideo = + ps: with ps; [ + pydantic + ]; # missing niconico.py-ma + nts = ps: [ + ]; nugs = ps: [ ]; opensubsonic = @@ -147,6 +200,8 @@ ]; qobuz = ps: [ ]; + qqmusic = ps: [ + ]; # missing qqmusic-api-python radiobrowser = ps: with ps; [ radios @@ -158,13 +213,26 @@ async-upnp-client rokuecp ]; + samsung_wam = ps: [ + ]; # missing pywam sendspin = - ps: with ps; [ + ps: + with ps; + [ aiosendspin av - ]; + ] + ++ aiosendspin.optional-dependencies.server; siriusxm = ps: [ ]; # missing sxm + smart_fades = + ps: with ps; [ + beat-this + nnaudio + threadpoolctl + ]; + smart_playlist = ps: [ + ]; snapcast = ps: with ps; [ bidict @@ -173,6 +241,20 @@ ]; somafm = ps: [ ]; + sonic_analysis = + ps: with ps; [ + huggingface-hub + pyyaml + threadpoolctl + torchlibrosa + transformers + ]; + sonic_similarity = + ps: with ps; [ + huggingface-hub + transformers + usearch + ]; sonos = ps: with ps; [ aiosonos @@ -220,8 +302,20 @@ ps: with ps; [ aiovban ]; + webdav = ps: [ + ]; + wiim = + ps: with ps; [ + wiim + ]; + wikipedia = ps: [ + ]; yandex_music = ps: [ - ]; # missing yandex-music + ]; # missing yandex-music, ya-passport-auth + yandex_smarthome = ps: [ + ]; # missing ya-passport-auth + yandex_ynison = ps: [ + ]; # missing ya-passport-auth yousee = ps: [ ]; ytmusic = @@ -232,6 +326,7 @@ ytmusicapi ]; # missing deno zvuk_music = ps: [ - ]; # missing zvuk-music + ]; + # missing zvuk-music }; } diff --git a/pkgs/by-name/mu/music-assistant/update-providers.py b/pkgs/by-name/mu/music-assistant/update-providers.py index c90eae86df44..b9273f4280b7 100755 --- a/pkgs/by-name/mu/music-assistant/update-providers.py +++ b/pkgs/by-name/mu/music-assistant/update-providers.py @@ -33,9 +33,13 @@ TEMPLATE = """# Do not edit manually, run ./update-providers.py {%- for provider in providers | sort(attribute='domain') %} {{ provider.domain }} = {% if provider.available %}ps: with ps;{% else %}ps:{% endif %} [ {%- for requirement in provider.available | sort %} - {{ requirement }} + {{ requirement }} {%- endfor %} - ];{% if provider.missing %} # missing {{ ", ".join(provider.missing) }}{% endif %} + ] +{%- for requirement in provider.extra_list_deps | sort %} + ++ {{ requirement }} +{%- endfor %} +;{% if provider.missing %} # missing {{ ", ".join(provider.missing) }}{% endif %} {%- endfor %} }; } @@ -62,6 +66,9 @@ PACKAGE_MAP = { EXTRA_DEPS = { + # Those providers cannot guard pydantic behind TYPE_CHECKING + "msx_bridge": ["pydantic"], + "nicovideo": ["pydantic"], "ytmusic": [ # https://github.com/music-assistant/server/blob/2.5.8/music_assistant/providers/ytmusic/__init__.py#L120 "bgutil-ytdlp-pot-provider", @@ -70,6 +77,11 @@ EXTRA_DEPS = { } +EXTRA_LIST_DEPS = { + "sendspin": ["aiosendspin.optional-dependencies.server"], +} + + def run_sync(cmd: List[str]) -> None: print(f"$ {' '.join(cmd)}") process = run(cmd) @@ -194,6 +206,7 @@ class Provider: domain: str available: list[str] = field(default_factory=list) missing: list[str] = field(default_factory=list) + extra_list_deps: list[str] = field(default_factory=list) def __eq__(self, other): return self.domain == other.domain @@ -207,7 +220,7 @@ async def resolve_providers(manifests) -> tuple[Set, Set]: providers = set() for manifest in manifests: provider = Provider(manifest.domain) - requirements = manifest.requirements + EXTRA_DEPS.get(manifest.domain, []) + requirements = manifest.requirements for requirement in requirements: # allow substituting requirement specifications that packaging cannot parse if requirement in PACKAGE_MAP: @@ -228,6 +241,12 @@ async def resolve_providers(manifests) -> tuple[Set, Set]: version = await get_package_version(attr) if version not in requirement.specifier: errors.append(f"{requirement} not satisfied by version {version}") + if manifest.domain in EXTRA_DEPS: + for requirement in EXTRA_DEPS[manifest.domain]: + provider.available.append(requirement) + if manifest.domain in EXTRA_LIST_DEPS: + for requirement in EXTRA_LIST_DEPS[manifest.domain]: + provider.extra_list_deps.append(requirement) providers.add(provider) if errors: print("\n - ", end="")