Martin Weinelt 2025-03-05 20:48:09 +01:00
parent fed188c351
commit 2127c8d779
No known key found for this signature in database
GPG Key ID: 87C1E9888F856759
6 changed files with 91 additions and 44 deletions

View File

@ -1,8 +1,8 @@
diff --git a/music_assistant/helpers/util.py b/music_assistant/helpers/util.py diff --git a/music_assistant/helpers/util.py b/music_assistant/helpers/util.py
index b6e5f2b4..0edead16 100644 index 8daf159d..af5a6f38 100644
--- a/music_assistant/helpers/util.py --- a/music_assistant/helpers/util.py
+++ b/music_assistant/helpers/util.py +++ b/music_assistant/helpers/util.py
@@ -424,29 +424,11 @@ async def load_provider_module(domain: str, requirements: list[str]) -> Provider @@ -429,30 +429,11 @@ async def load_provider_module(domain: str, requirements: list[str]) -> Provider
def _get_provider_module(domain: str) -> ProviderModuleType: def _get_provider_module(domain: str) -> ProviderModuleType:
return importlib.import_module(f".{domain}", "music_assistant.providers") return importlib.import_module(f".{domain}", "music_assistant.providers")
@ -29,7 +29,8 @@ index b6e5f2b4..0edead16 100644
- # try loading the provider again to be safe - # try loading the provider again to be safe
- # this will fail if something else is wrong (as it should) - # this will fail if something else is wrong (as it should)
- return await asyncio.to_thread(_get_provider_module, domain) - return await asyncio.to_thread(_get_provider_module, domain)
+ raise RuntimeError(f"Missing dependencies for provider {domain}") -
+ raise RuntimeError(f"Missing dependencies for provider {domain}.")
def create_tempfile(): def create_tempfile():
"""Return a (named) temporary file."""

View File

@ -1,8 +1,8 @@
diff --git a/music_assistant/helpers/audio.py b/music_assistant/helpers/audio.py diff --git a/music_assistant/helpers/audio.py b/music_assistant/helpers/audio.py
index dad3c5db..d1398d66 100644 index 482ab8e8..a02fc435 100644
--- a/music_assistant/helpers/audio.py --- a/music_assistant/helpers/audio.py
+++ b/music_assistant/helpers/audio.py +++ b/music_assistant/helpers/audio.py
@@ -74,7 +74,7 @@ async def crossfade_pcm_parts( @@ -80,7 +80,7 @@ async def crossfade_pcm_parts(
await outfile.write(fade_out_part) await outfile.write(fade_out_part)
args = [ args = [
# generic args # generic args
@ -11,7 +11,7 @@ index dad3c5db..d1398d66 100644
"-hide_banner", "-hide_banner",
"-loglevel", "-loglevel",
"quiet", "quiet",
@@ -135,7 +135,7 @@ async def strip_silence( @@ -141,7 +141,7 @@ async def strip_silence(
reverse: bool = False, reverse: bool = False,
) -> bytes: ) -> bytes:
"""Strip silence from begin or end of pcm audio using ffmpeg.""" """Strip silence from begin or end of pcm audio using ffmpeg."""
@ -20,16 +20,7 @@ index dad3c5db..d1398d66 100644
args += [ args += [
"-acodec", "-acodec",
pcm_format.content_type.name.lower(), pcm_format.content_type.name.lower(),
@@ -734,7 +734,7 @@ async def get_file_stream( @@ -912,7 +912,7 @@ async def get_silence(
async def check_audio_support() -> tuple[bool, bool, str]:
"""Check if ffmpeg is present (with/without libsoxr support)."""
# check for FFmpeg presence
- returncode, output = await check_output("ffmpeg", "-version")
+ returncode, output = await check_output("@ffmpeg@", "-version")
ffmpeg_present = returncode == 0 and "FFmpeg" in output.decode()
# use globals as in-memory cache
@@ -789,7 +789,7 @@ async def get_silence(
return return
# use ffmpeg for all other encodings # use ffmpeg for all other encodings
args = [ args = [
@ -39,11 +30,11 @@ index dad3c5db..d1398d66 100644
"-loglevel", "-loglevel",
"quiet", "quiet",
diff --git a/music_assistant/helpers/ffmpeg.py b/music_assistant/helpers/ffmpeg.py diff --git a/music_assistant/helpers/ffmpeg.py b/music_assistant/helpers/ffmpeg.py
index 0405fc27..570f9157 100644 index 7c1c8d83..501a7370 100644
--- a/music_assistant/helpers/ffmpeg.py --- a/music_assistant/helpers/ffmpeg.py
+++ b/music_assistant/helpers/ffmpeg.py +++ b/music_assistant/helpers/ffmpeg.py
@@ -213,7 +213,7 @@ def get_ffmpeg_args( # noqa: PLR0915 @@ -218,7 +218,7 @@ def get_ffmpeg_args(
extra_args = []
# generic args # generic args
generic_args = [ generic_args = [
- "ffmpeg", - "ffmpeg",
@ -51,11 +42,20 @@ index 0405fc27..570f9157 100644
"-hide_banner", "-hide_banner",
"-loglevel", "-loglevel",
loglevel, loglevel,
@@ -370,7 +370,7 @@ async def check_ffmpeg_version() -> None:
"""Check if ffmpeg is present (with libsoxr support)."""
# check for FFmpeg presence
try:
- returncode, output = await check_output("ffmpeg", "-version")
+ returncode, output = await check_output("@ffmpeg@", "-version")
except FileNotFoundError:
raise AudioError(
"FFmpeg binary is missing from system. "
diff --git a/music_assistant/helpers/tags.py b/music_assistant/helpers/tags.py diff --git a/music_assistant/helpers/tags.py b/music_assistant/helpers/tags.py
index 55def74b..7c26e13d 100644 index 06c8bcb5..a703aacd 100644
--- a/music_assistant/helpers/tags.py --- a/music_assistant/helpers/tags.py
+++ b/music_assistant/helpers/tags.py +++ b/music_assistant/helpers/tags.py
@@ -387,7 +387,7 @@ async def parse_tags(input_file: str, file_size: int | None = None) -> AudioTags @@ -438,7 +438,7 @@ def parse_tags(input_file: str, file_size: int | None = None) -> AudioTags:
Input_file may be a (local) filename or URL accessible by ffmpeg. Input_file may be a (local) filename or URL accessible by ffmpeg.
""" """
args = ( args = (
@ -64,7 +64,7 @@ index 55def74b..7c26e13d 100644
"-hide_banner", "-hide_banner",
"-loglevel", "-loglevel",
"fatal", "fatal",
@@ -448,7 +448,7 @@ async def get_embedded_image(input_file: str) -> bytes | None: @@ -553,7 +553,7 @@ async def get_embedded_image(input_file: str) -> bytes | None:
Input_file may be a (local) filename or URL accessible by ffmpeg. Input_file may be a (local) filename or URL accessible by ffmpeg.
""" """
args = ( args = (

View File

@ -7,12 +7,12 @@
buildPythonPackage rec { buildPythonPackage rec {
pname = "music-assistant-frontend"; pname = "music-assistant-frontend";
version = "2.9.16"; version = "2.11.11";
pyproject = true; pyproject = true;
src = fetchPypi { src = fetchPypi {
inherit pname version; inherit pname version;
hash = "sha256-bUtclj8GMq1JdC4tYNETl+l+TNF91y4yTANSpuOOm70="; hash = "sha256-fPFszZfJjdc2KTQipeb2KVbIwWwL56bqFGba1LSJXuQ=";
}; };
postPatch = '' postPatch = ''

View File

@ -0,0 +1,29 @@
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@"

View File

@ -3,6 +3,7 @@
python3, python3,
fetchFromGitHub, fetchFromGitHub,
ffmpeg-headless, ffmpeg-headless,
librespot,
nixosTests, nixosTests,
replaceVars, replaceVars,
providers ? [ ], providers ? [ ],
@ -12,27 +13,16 @@ let
python = python3.override { python = python3.override {
self = python; self = python;
packageOverrides = self: super: { packageOverrides = self: super: {
aiojellyfin = super.aiojellyfin.overridePythonAttrs (oldAttrs: rec {
version = "0.10.1";
src = fetchFromGitHub {
owner = "Jc2k";
repo = "aiojellyfin";
tag = "v${version}";
hash = "sha256-A+uvM1/7HntRMIdknfHr0TMGIjHk7BCwsZopXdVoEO8=";
};
});
music-assistant-frontend = self.callPackage ./frontend.nix { }; music-assistant-frontend = self.callPackage ./frontend.nix { };
music-assistant-models = super.music-assistant-models.overridePythonAttrs (oldAttrs: rec { music-assistant-models = super.music-assistant-models.overridePythonAttrs (oldAttrs: rec {
version = "1.1.4"; version = "1.1.30";
src = fetchFromGitHub { src = fetchFromGitHub {
owner = "music-assistant"; owner = "music-assistant";
repo = "models"; repo = "models";
tag = version; tag = version;
hash = "sha256-keig18o32X53q/QcoaPO0o9AT4XTEZ+dQ3L6u6BVkLU="; hash = "sha256-ZLTRHarjVFAk+tYPkgLm192rE+C82vNzqs8PmJhGSeg=";
}; };
postPatch = '' postPatch = ''
@ -54,14 +44,14 @@ in
python.pkgs.buildPythonApplication rec { python.pkgs.buildPythonApplication rec {
pname = "music-assistant"; pname = "music-assistant";
version = "2.3.6"; version = "2.4.2";
pyproject = true; pyproject = true;
src = fetchFromGitHub { src = fetchFromGitHub {
owner = "music-assistant"; owner = "music-assistant";
repo = "server"; repo = "server";
tag = version; tag = version;
hash = "sha256-CSGpG1E4ou1TGz/S1mXFHyk49p7dStEwxUTB+xxfNEc="; hash = "sha256-5FIIXIn4tEz6w/uAh6PGkU4tU+mz7Jpb3+bq1mRNr2Y=";
}; };
patches = [ patches = [
@ -69,6 +59,9 @@ python.pkgs.buildPythonApplication rec {
ffmpeg = "${lib.getBin ffmpeg-headless}/bin/ffmpeg"; ffmpeg = "${lib.getBin ffmpeg-headless}/bin/ffmpeg";
ffprobe = "${lib.getBin ffmpeg-headless}/bin/ffprobe"; ffprobe = "${lib.getBin ffmpeg-headless}/bin/ffprobe";
}) })
(replaceVars ./librespot.patch {
librespot = lib.getExe librespot;
})
# Disable interactive dependency resolution, which clashes with the immutable Python environment # Disable interactive dependency resolution, which clashes with the immutable Python environment
./dont-install-deps.patch ./dont-install-deps.patch
@ -77,6 +70,8 @@ python.pkgs.buildPythonApplication rec {
postPatch = '' postPatch = ''
substituteInPlace pyproject.toml \ substituteInPlace pyproject.toml \
--replace-fail "0.0.0" "${version}" --replace-fail "0.0.0" "${version}"
rm -rv music_assistant/providers/spotify/bin
''; '';
build-system = with python.pkgs; [ build-system = with python.pkgs; [
@ -85,6 +80,7 @@ python.pkgs.buildPythonApplication rec {
pythonRelaxDeps = [ pythonRelaxDeps = [
"aiohttp" "aiohttp"
"aiosqlite"
"certifi" "certifi"
"colorlog" "colorlog"
"cryptography" "cryptography"
@ -123,8 +119,10 @@ python.pkgs.buildPythonApplication rec {
memory-tempfile memory-tempfile
music-assistant-frontend music-assistant-frontend
music-assistant-models music-assistant-models
mutagen
orjson orjson
pillow pillow
podcastparser
python-slugify python-slugify
shortuuid shortuuid
unidecode unidecode
@ -136,7 +134,6 @@ python.pkgs.buildPythonApplication rec {
nativeCheckInputs = nativeCheckInputs =
with python.pkgs; with python.pkgs;
[ [
aiojellyfin
pytest-aiohttp pytest-aiohttp
pytest-cov-stub pytest-cov-stub
pytest-timeout pytest-timeout
@ -144,7 +141,9 @@ python.pkgs.buildPythonApplication rec {
syrupy syrupy
pytest-timeout pytest-timeout
] ]
++ lib.flatten (lib.attrValues optional-dependencies); ++ lib.flatten (lib.attrValues optional-dependencies)
++ (providerPackages.jellyfin python.pkgs)
++ (providerPackages.opensubsonic python.pkgs);
pytestFlagsArray = [ pytestFlagsArray = [
# blocks in poll() # blocks in poll()

View File

@ -1,12 +1,20 @@
# Do not edit manually, run ./update-providers.py # Do not edit manually, run ./update-providers.py
{ {
version = "2.3.6"; version = "2.4.2";
providers = { providers = {
airplay = ps: [ airplay = ps: [
]; ];
apple_music = ps: [ apple_music = ps: [
]; # missing pywidevine ]; # missing pywidevine
audible =
ps: with ps; [
audible
];
audiobookshelf =
ps: with ps; [
aioaudiobookshelf
];
bluesound = bluesound =
ps: with ps; [ ps: with ps; [
pyblu pyblu
@ -19,8 +27,9 @@
]; ];
deezer = deezer =
ps: with ps; [ ps: with ps; [
deezer-python-async
pycryptodome pycryptodome
]; # missing deezer-python-async ];
dlna = dlna =
ps: with ps; [ ps: with ps; [
async-upnp-client async-upnp-client
@ -41,6 +50,8 @@
]; ];
hass_players = ps: [ hass_players = ps: [
]; ];
ibroadcast = ps: [
]; # missing ibroadcastaio
jellyfin = jellyfin =
ps: with ps; [ ps: with ps; [
aiojellyfin aiojellyfin
@ -57,6 +68,10 @@
ps: with ps; [ ps: with ps; [
plexapi plexapi
]; ];
podcastfeed =
ps: with ps; [
podcastparser
];
qobuz = ps: [ qobuz = ps: [
]; ];
radiobrowser = radiobrowser =
@ -87,6 +102,8 @@
ps: with ps; [ ps: with ps; [
pkce pkce
]; ];
spotify_connect = ps: [
];
template_player_provider = ps: [ template_player_provider = ps: [
]; ];
test = ps: [ test = ps: [
@ -101,6 +118,7 @@
]; ];
ytmusic = ytmusic =
ps: with ps; [ ps: with ps; [
duration-parser
yt-dlp yt-dlp
ytmusicapi ytmusicapi
]; ];