diff --git a/nixos/modules/services/audio/music-assistant.nix b/nixos/modules/services/audio/music-assistant.nix index d48bb7f99f84..b7233878ecae 100644 --- a/nixos/modules/services/audio/music-assistant.nix +++ b/nixos/modules/services/audio/music-assistant.nix @@ -79,6 +79,18 @@ in PYTHONPATH = finalPackage.pythonPath; }; + path = + with pkgs; + [ + lsof + ] + ++ lib.optionals (lib.elem "librespot" cfg.providers) [ + librespot + ] + ++ lib.optionals (lib.elem "snapcast" cfg.providers) [ + snapcast + ]; + serviceConfig = { ExecStart = utils.escapeSystemdExecArgs ( [ 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 5281f578bedf..21e9522a9bfe 100644 --- a/pkgs/by-name/mu/music-assistant/dont-install-deps.patch +++ b/pkgs/by-name/mu/music-assistant/dont-install-deps.patch @@ -1,10 +1,10 @@ diff --git a/music_assistant/helpers/util.py b/music_assistant/helpers/util.py -index 8daf159d..af5a6f38 100644 +index 74540dd3..14f8f864 100644 --- a/music_assistant/helpers/util.py +++ b/music_assistant/helpers/util.py -@@ -429,30 +429,11 @@ async def load_provider_module(domain: str, requirements: list[str]) -> Provider - def _get_provider_module(domain: str) -> ProviderModuleType: - return importlib.import_module(f".{domain}", "music_assistant.providers") +@@ -434,30 +434,11 @@ async def load_provider_module(domain: str, requirements: list[str]) -> Provider + "ProviderModuleType", importlib.import_module(f".{domain}", "music_assistant.providers") + ) - # ensure module requirements are met - for requirement in requirements: @@ -30,7 +30,19 @@ index 8daf159d..af5a6f38 100644 - # this will fail if something else is wrong (as it should) - return await asyncio.to_thread(_get_provider_module, domain) - -+ raise RuntimeError(f"Missing dependencies for provider {domain}.") ++ raise RuntimeError(f"Configure {domain} in `services.music-assistant.providers` to install the required dependencies.") - def create_tempfile(): - """Return a (named) temporary file.""" + async def has_tmpfs_mount() -> bool: + """Check if we have a tmpfs mount.""" +diff --git a/music_assistant/providers/ytmusic/__init__.py b/music_assistant/providers/ytmusic/__init__.py +index 52a7544a..816d0425 100644 +--- a/music_assistant/providers/ytmusic/__init__.py ++++ b/music_assistant/providers/ytmusic/__init__.py +@@ -197,7 +197,6 @@ class YoutubeMusicProvider(MusicProvider): + async def handle_async_init(self) -> None: + """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._po_token_server_url = ( + self.config.get_value(CONF_PO_TOKEN_SERVER_URL) or DEFAULT_PO_TOKEN_SERVER_URL diff --git a/pkgs/by-name/mu/music-assistant/librespot.patch b/pkgs/by-name/mu/music-assistant/librespot.patch index 95b135a46b15..e69de29bb2d1 100644 --- a/pkgs/by-name/mu/music-assistant/librespot.patch +++ b/pkgs/by-name/mu/music-assistant/librespot.patch @@ -1,29 +0,0 @@ -diff --git a/music_assistant/providers/spotify/helpers.py b/music_assistant/providers/spotify/helpers.py -index 8b6c4e78..20c2a269 100644 ---- a/music_assistant/providers/spotify/helpers.py -+++ b/music_assistant/providers/spotify/helpers.py -@@ -11,23 +11,4 @@ from music_assistant.helpers.process import check_output - async def get_librespot_binary() -> str: - """Find the correct librespot binary belonging to the platform.""" - -- # ruff: noqa: SIM102 -- async def check_librespot(librespot_path: str) -> str | None: -- try: -- returncode, output = await check_output(librespot_path, "--version") -- if returncode == 0 and b"librespot" in output: -- return librespot_path -- except OSError: -- return None -- -- base_path = os.path.join(os.path.dirname(__file__), "bin") -- system = platform.system().lower().replace("darwin", "macos") -- architecture = platform.machine().lower() -- -- if bridge_binary := await check_librespot( -- os.path.join(base_path, f"librespot-{system}-{architecture}") -- ): -- return bridge_binary -- -- msg = f"Unable to locate Librespot for {system}/{architecture}" -- raise RuntimeError(msg) -+ return "@librespot@" diff --git a/pkgs/by-name/mu/music-assistant/package.nix b/pkgs/by-name/mu/music-assistant/package.nix index ead07456c2cc..71fe4275b77a 100644 --- a/pkgs/by-name/mu/music-assistant/package.nix +++ b/pkgs/by-name/mu/music-assistant/package.nix @@ -3,7 +3,6 @@ python3, fetchFromGitHub, ffmpeg-headless, - librespot, nixosTests, replaceVars, providers ? [ ], @@ -48,14 +47,14 @@ assert python.pkgs.buildPythonApplication rec { pname = "music-assistant"; - version = "2.5.5"; + version = "2.5.8"; pyproject = true; src = fetchFromGitHub { owner = "music-assistant"; repo = "server"; tag = version; - hash = "sha256-v9xFUjjk7KHsUtuZjQWLtc1m3f6VOUPlQtSBtUR6Pcg="; + hash = "sha256-7Q+BYw7wnT7QdqrDjagaxupzD0iKTc26z4TfxNtugdA="; }; patches = [ @@ -63,9 +62,9 @@ python.pkgs.buildPythonApplication rec { ffmpeg = "${lib.getBin ffmpeg-headless}/bin/ffmpeg"; ffprobe = "${lib.getBin ffmpeg-headless}/bin/ffprobe"; }) - (replaceVars ./librespot.patch { - librespot = lib.getExe librespot; - }) + + # Look up librespot from PATH at runtime + ./librespot.patch # Disable interactive dependency resolution, which clashes with the immutable Python environment ./dont-install-deps.patch @@ -95,6 +94,11 @@ python.pkgs.buildPythonApplication rec { "zeroconf" ]; + pythonRemoveDeps = [ + # no runtime dependency resolution + "uv" + ]; + dependencies = with python.pkgs; [ diff --git a/pkgs/by-name/mu/music-assistant/providers.nix b/pkgs/by-name/mu/music-assistant/providers.nix index b86f4de81aca..ef0d35c06fde 100644 --- a/pkgs/by-name/mu/music-assistant/providers.nix +++ b/pkgs/by-name/mu/music-assistant/providers.nix @@ -1,7 +1,7 @@ # Do not edit manually, run ./update-providers.py { - version = "2.5.5"; + version = "2.5.8"; providers = { airplay = ps: [ ]; @@ -131,9 +131,10 @@ ]; ytmusic = ps: with ps; [ + bgutil-ytdlp-pot-provider duration-parser yt-dlp ytmusicapi - ]; # missing bgutil-ytdlp-pot-provider + ]; }; } diff --git a/pkgs/by-name/mu/music-assistant/update-providers.py b/pkgs/by-name/mu/music-assistant/update-providers.py index fb1825fdc233..981ca38cd40d 100755 --- a/pkgs/by-name/mu/music-assistant/update-providers.py +++ b/pkgs/by-name/mu/music-assistant/update-providers.py @@ -56,6 +56,15 @@ PACKAGE_MAP = { } +EXTRA_DEPS = { + "ytmusic": [ + # https://github.com/music-assistant/server/blob/2.5.8/music_assistant/providers/ytmusic/__init__.py#L120 + "bgutil-ytdlp-pot-provider", + "yt-dlp", + ], +} + + def run_sync(cmd: List[str]) -> None: print(f"$ {' '.join(cmd)}") process = run(cmd) @@ -191,7 +200,8 @@ async def resolve_providers(manifests) -> Set: providers = set() for manifest in manifests: provider = Provider(manifest.domain) - for requirement in manifest.requirements: + requirements = manifest.requirements + EXTRA_DEPS.get(manifest.domain, []) + for requirement in requirements: # allow substituting requirement specifications that packaging cannot parse if requirement in PACKAGE_MAP: requirement = PACKAGE_MAP[requirement] diff --git a/pkgs/development/python-modules/bgutil-ytdlp-pot-provider/default.nix b/pkgs/development/python-modules/bgutil-ytdlp-pot-provider/default.nix new file mode 100644 index 000000000000..2b067e2d7b40 --- /dev/null +++ b/pkgs/development/python-modules/bgutil-ytdlp-pot-provider/default.nix @@ -0,0 +1,35 @@ +{ + lib, + buildPythonPackage, + fetchFromGitHub, + hatchling, + yt-dlp, +}: + +buildPythonPackage rec { + pname = "bgutil-ytdlp-pot-provider"; + version = "1.2.2"; + pyproject = true; + + src = fetchFromGitHub { + owner = "Brainicism"; + repo = "bgutil-ytdlp-pot-provider"; + tag = version; + hash = "sha256-KKImGxFGjClM2wAk/L8nwauOkM/gEwRVMZhTP62ETqY="; + }; + + sourceRoot = "${src.name}/plugin"; + + build-system = [ hatchling ]; + + dependencies = [ yt-dlp ]; + + doCheck = false; # no tests + + meta = { + description = "Proof-of-origin token provider plugin for yt-dlp"; + homepage = "https://github.com/Brainicism/bgutil-ytdlp-pot-provider"; + license = lib.licenses.gpl3Only; + maintainers = with lib.maintainers; [ hexa ]; + }; +} diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix index c2df3ddcf44b..e8d8f29b5767 100644 --- a/pkgs/top-level/python-packages.nix +++ b/pkgs/top-level/python-packages.nix @@ -1845,6 +1845,8 @@ self: super: with self; { beziers = callPackage ../development/python-modules/beziers { }; + bgutil-ytdlp-pot-provider = callPackage ../development/python-modules/bgutil-ytdlp-pot-provider { }; + bibtexparser = callPackage ../development/python-modules/bibtexparser { }; bibtexparser_2 = callPackage ../development/python-modules/bibtexparser/2.nix { };