mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-07-12 05:16:25 +03:00
Merge pull request #311927 from mweinelt/music-assistant
music-assistant: init at 2.0.7
This commit is contained in:
commit
bf8439efae
16 changed files with 781 additions and 0 deletions
|
@ -33,6 +33,8 @@
|
||||||
|
|
||||||
- [Renovate](https://github.com/renovatebot/renovate), a dependency updating tool for various git forges and language ecosystems. Available as [services.renovate](#opt-services.renovate.enable).
|
- [Renovate](https://github.com/renovatebot/renovate), a dependency updating tool for various git forges and language ecosystems. Available as [services.renovate](#opt-services.renovate.enable).
|
||||||
|
|
||||||
|
- [Music Assistant](https://music-assistant.io/), a music library manager for your offline and online music sources which can easily stream your favourite music to a wide range of supported players. Available as [services.music-assistant](#opt-services.music-assistant.enable).
|
||||||
|
|
||||||
- [wg-access-server](https://github.com/freifunkMUC/wg-access-server/), an all-in-one WireGuard VPN solution with a web ui for connecting devices. Available at [services.wg-access-server](#opt-services.wg-access-server.enable).
|
- [wg-access-server](https://github.com/freifunkMUC/wg-access-server/), an all-in-one WireGuard VPN solution with a web ui for connecting devices. Available at [services.wg-access-server](#opt-services.wg-access-server.enable).
|
||||||
|
|
||||||
- [Envision](https://gitlab.com/gabmus/envision), a UI for building, configuring and running Monado, the open source OpenXR runtime. Available as [programs.envision](#opt-programs.envision.enable).
|
- [Envision](https://gitlab.com/gabmus/envision), a UI for building, configuring and running Monado, the open source OpenXR runtime. Available as [programs.envision](#opt-programs.envision.enable).
|
||||||
|
|
|
@ -376,6 +376,7 @@
|
||||||
./services/audio/mopidy.nix
|
./services/audio/mopidy.nix
|
||||||
./services/audio/mpd.nix
|
./services/audio/mpd.nix
|
||||||
./services/audio/mpdscribble.nix
|
./services/audio/mpdscribble.nix
|
||||||
|
./services/audio/music-assistant.nix
|
||||||
./services/audio/mympd.nix
|
./services/audio/mympd.nix
|
||||||
./services/audio/navidrome.nix
|
./services/audio/navidrome.nix
|
||||||
./services/audio/networkaudiod.nix
|
./services/audio/networkaudiod.nix
|
||||||
|
|
113
nixos/modules/services/audio/music-assistant.nix
Normal file
113
nixos/modules/services/audio/music-assistant.nix
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
utils,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
mkIf
|
||||||
|
mkEnableOption
|
||||||
|
mkOption
|
||||||
|
mkPackageOption
|
||||||
|
types
|
||||||
|
;
|
||||||
|
|
||||||
|
inherit (types)
|
||||||
|
listOf
|
||||||
|
enum
|
||||||
|
str
|
||||||
|
;
|
||||||
|
|
||||||
|
cfg = config.services.music-assistant;
|
||||||
|
|
||||||
|
finalPackage = cfg.package.override {
|
||||||
|
inherit (cfg) providers;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
meta.buildDocsInSandbox = false;
|
||||||
|
|
||||||
|
options.services.music-assistant = {
|
||||||
|
enable = mkEnableOption "Music Assistant";
|
||||||
|
|
||||||
|
package = mkPackageOption pkgs "music-assistant" { };
|
||||||
|
|
||||||
|
extraOptions = mkOption {
|
||||||
|
type = listOf str;
|
||||||
|
default = [ "--config" "/var/lib/music-assistant" ];
|
||||||
|
example = [
|
||||||
|
"--log-level"
|
||||||
|
"DEBUG"
|
||||||
|
];
|
||||||
|
description = ''
|
||||||
|
List of extra options to pass to the music-assistant executable.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
providers = mkOption {
|
||||||
|
type = listOf (enum cfg.package.providerNames);
|
||||||
|
default = [];
|
||||||
|
example = [
|
||||||
|
"opensubsonic"
|
||||||
|
"snapcast"
|
||||||
|
];
|
||||||
|
description = ''
|
||||||
|
List of provider names for which dependencies will be installed.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
systemd.services.music-assistant = {
|
||||||
|
description = "Music Assistant";
|
||||||
|
documentation = [ "https://music-assistant.io" ];
|
||||||
|
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
|
environment = {
|
||||||
|
HOME = "/var/lib/music-assistant";
|
||||||
|
PYTHONPATH = finalPackage.pythonPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = utils.escapeSystemdExecArgs ([
|
||||||
|
(lib.getExe cfg.package)
|
||||||
|
] ++ cfg.extraOptions);
|
||||||
|
DynamicUser = true;
|
||||||
|
StateDirectory = "music-assistant";
|
||||||
|
AmbientCapabilities = "";
|
||||||
|
CapabilityBoundingSet = [ "" ];
|
||||||
|
DevicePolicy = "closed";
|
||||||
|
LockPersonality = true;
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
|
ProcSubset = "pid";
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
ProtectHome = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectProc = "invisible";
|
||||||
|
RestrictAddressFamilies = [
|
||||||
|
"AF_INET"
|
||||||
|
"AF_INET6"
|
||||||
|
"AF_NETLINK"
|
||||||
|
];
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
SystemCallArchitectures = "native";
|
||||||
|
SystemCallFilter = [
|
||||||
|
"@system-service"
|
||||||
|
"~@privileged @resources"
|
||||||
|
];
|
||||||
|
RestrictSUIDSGID = true;
|
||||||
|
UMask = "0077";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -598,6 +598,7 @@ in {
|
||||||
# Fails on aarch64-linux at the PDF creation step - need to debug this on an
|
# Fails on aarch64-linux at the PDF creation step - need to debug this on an
|
||||||
# aarch64 machine..
|
# aarch64 machine..
|
||||||
musescore = handleTestOn ["x86_64-linux"] ./musescore.nix {};
|
musescore = handleTestOn ["x86_64-linux"] ./musescore.nix {};
|
||||||
|
music-assistant = runTest ./music-assistant.nix;
|
||||||
munin = handleTest ./munin.nix {};
|
munin = handleTest ./munin.nix {};
|
||||||
mutableUsers = handleTest ./mutable-users.nix {};
|
mutableUsers = handleTest ./mutable-users.nix {};
|
||||||
mycelium = handleTest ./mycelium {};
|
mycelium = handleTest ./mycelium {};
|
||||||
|
|
21
nixos/tests/music-assistant.nix
Normal file
21
nixos/tests/music-assistant.nix
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "music-assistant";
|
||||||
|
meta.maintainers = with lib.maintainers; [ hexa ];
|
||||||
|
|
||||||
|
nodes.machine = {
|
||||||
|
services.music-assistant = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
machine.wait_for_unit("music-assistant.service")
|
||||||
|
machine.wait_until_succeeds("curl --fail http://localhost:8095")
|
||||||
|
machine.log(machine.succeed("systemd-analyze security music-assistant.service | grep -v ✓"))
|
||||||
|
'';
|
||||||
|
}
|
71
pkgs/by-name/mu/music-assistant/ffmpeg.patch
Normal file
71
pkgs/by-name/mu/music-assistant/ffmpeg.patch
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
diff --git a/music_assistant/server/helpers/audio.py b/music_assistant/server/helpers/audio.py
|
||||||
|
index 42011923..1e5dc112 100644
|
||||||
|
--- a/music_assistant/server/helpers/audio.py
|
||||||
|
+++ b/music_assistant/server/helpers/audio.py
|
||||||
|
@@ -218,7 +218,7 @@ async def crossfade_pcm_parts(
|
||||||
|
await outfile.write(fade_out_part)
|
||||||
|
args = [
|
||||||
|
# generic args
|
||||||
|
- "ffmpeg",
|
||||||
|
+ "@ffmpeg@",
|
||||||
|
"-hide_banner",
|
||||||
|
"-loglevel",
|
||||||
|
"quiet",
|
||||||
|
@@ -281,7 +281,7 @@ async def strip_silence(
|
||||||
|
) -> bytes:
|
||||||
|
"""Strip silence from begin or end of pcm audio using ffmpeg."""
|
||||||
|
fmt = ContentType.from_bit_depth(bit_depth)
|
||||||
|
- args = ["ffmpeg", "-hide_banner", "-loglevel", "quiet"]
|
||||||
|
+ args = ["@ffmpeg@", "-hide_banner", "-loglevel", "quiet"]
|
||||||
|
args += [
|
||||||
|
"-acodec",
|
||||||
|
fmt.name.lower(),
|
||||||
|
@@ -823,7 +823,7 @@ async def get_ffmpeg_stream(
|
||||||
|
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
|
||||||
|
@@ -877,7 +877,7 @@ async def get_silence(
|
||||||
|
return
|
||||||
|
# use ffmpeg for all other encodings
|
||||||
|
args = [
|
||||||
|
- "ffmpeg",
|
||||||
|
+ "@ffmpeg@",
|
||||||
|
"-hide_banner",
|
||||||
|
"-loglevel",
|
||||||
|
"quiet",
|
||||||
|
@@ -971,7 +971,7 @@ def get_ffmpeg_args(
|
||||||
|
|
||||||
|
# generic args
|
||||||
|
generic_args = [
|
||||||
|
- "ffmpeg",
|
||||||
|
+ "@ffmpeg@",
|
||||||
|
"-hide_banner",
|
||||||
|
"-loglevel",
|
||||||
|
loglevel,
|
||||||
|
diff --git a/music_assistant/server/helpers/tags.py b/music_assistant/server/helpers/tags.py
|
||||||
|
index dc38e4c0..f4f3e2fe 100644
|
||||||
|
--- a/music_assistant/server/helpers/tags.py
|
||||||
|
+++ b/music_assistant/server/helpers/tags.py
|
||||||
|
@@ -368,7 +368,7 @@ async def parse_tags(
|
||||||
|
file_path = input_file if isinstance(input_file, str) else "-"
|
||||||
|
|
||||||
|
args = (
|
||||||
|
- "ffprobe",
|
||||||
|
+ "@ffprobe@",
|
||||||
|
"-hide_banner",
|
||||||
|
"-loglevel",
|
||||||
|
"fatal",
|
||||||
|
@@ -440,7 +440,7 @@ async def get_embedded_image(input_file: str | AsyncGenerator[bytes, None]) -> b
|
||||||
|
"""
|
||||||
|
file_path = input_file if isinstance(input_file, str) else "-"
|
||||||
|
args = (
|
||||||
|
- "ffmpeg",
|
||||||
|
+ "@ffmpeg@",
|
||||||
|
"-hide_banner",
|
||||||
|
"-loglevel",
|
||||||
|
"error",
|
35
pkgs/by-name/mu/music-assistant/frontend.nix
Normal file
35
pkgs/by-name/mu/music-assistant/frontend.nix
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{ lib
|
||||||
|
, buildPythonPackage
|
||||||
|
, fetchPypi
|
||||||
|
, setuptools
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildPythonPackage rec {
|
||||||
|
pname = "music-assistant-frontend";
|
||||||
|
version = "2.5.15";
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
src = fetchPypi {
|
||||||
|
inherit pname version;
|
||||||
|
hash = "sha256-D8VFdXgaVXSxk7c24kvb9TflFztS1zLwW4qGqV32nLo=";
|
||||||
|
};
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
substituteInPlace pyproject.toml \
|
||||||
|
--replace-fail "~=" ">="
|
||||||
|
'';
|
||||||
|
|
||||||
|
build-system = [ setuptools ];
|
||||||
|
|
||||||
|
doCheck = false;
|
||||||
|
|
||||||
|
pythonImportsCheck = [ "music_assistant_frontend" ];
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
changelog = "https://github.com/music-assistant/frontend/releases/tag/${version}";
|
||||||
|
description = "The Music Assistant frontend";
|
||||||
|
homepage = "https://github.com/music-assistant/frontend";
|
||||||
|
license = licenses.asl20;
|
||||||
|
maintainers = with maintainers; [ hexa ];
|
||||||
|
};
|
||||||
|
}
|
119
pkgs/by-name/mu/music-assistant/package.nix
Normal file
119
pkgs/by-name/mu/music-assistant/package.nix
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
{ lib
|
||||||
|
, python3
|
||||||
|
, fetchFromGitHub
|
||||||
|
, ffmpeg-headless
|
||||||
|
, nixosTests
|
||||||
|
, substituteAll
|
||||||
|
, providers ? [ ]
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
python = python3.override {
|
||||||
|
packageOverrides = self: super: {
|
||||||
|
music-assistant-frontend = self.callPackage ./frontend.nix { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
providerPackages = (import ./providers.nix).providers;
|
||||||
|
providerNames = lib.attrNames providerPackages;
|
||||||
|
providerDependencies = lib.concatMap (provider: (providerPackages.${provider} python.pkgs)) providers;
|
||||||
|
|
||||||
|
pythonPath = python.pkgs.makePythonPath providerDependencies;
|
||||||
|
in
|
||||||
|
|
||||||
|
python.pkgs.buildPythonApplication rec {
|
||||||
|
pname = "music-assistant";
|
||||||
|
version = "2.0.7";
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "music-assistant";
|
||||||
|
repo = "server";
|
||||||
|
rev = version;
|
||||||
|
hash = "sha256-JtdlZ3hH4fRU5TjmMUlrdSSCnLrIGCuSwSSrnLgjYEs=";
|
||||||
|
};
|
||||||
|
|
||||||
|
patches = [
|
||||||
|
(substituteAll {
|
||||||
|
src = ./ffmpeg.patch;
|
||||||
|
ffmpeg = "${lib.getBin ffmpeg-headless}/bin/ffmpeg";
|
||||||
|
ffprobe = "${lib.getBin ffmpeg-headless}/bin/ffprobe";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
sed -i "/--cov/d" pyproject.toml
|
||||||
|
|
||||||
|
substituteInPlace pyproject.toml \
|
||||||
|
--replace-fail "0.0.0" "${version}"
|
||||||
|
'';
|
||||||
|
|
||||||
|
build-system = with python.pkgs; [
|
||||||
|
setuptools
|
||||||
|
];
|
||||||
|
|
||||||
|
dependencies = with python.pkgs; [
|
||||||
|
aiohttp
|
||||||
|
mashumaro
|
||||||
|
orjson
|
||||||
|
] ++ optional-dependencies.server;
|
||||||
|
|
||||||
|
optional-dependencies = with python.pkgs; {
|
||||||
|
server = [
|
||||||
|
aiodns
|
||||||
|
aiofiles
|
||||||
|
aiohttp
|
||||||
|
aiorun
|
||||||
|
aiosqlite
|
||||||
|
asyncio-throttle
|
||||||
|
brotli
|
||||||
|
certifi
|
||||||
|
colorlog
|
||||||
|
cryptography
|
||||||
|
faust-cchardet
|
||||||
|
ifaddr
|
||||||
|
mashumaro
|
||||||
|
memory-tempfile
|
||||||
|
music-assistant-frontend
|
||||||
|
orjson
|
||||||
|
pillow
|
||||||
|
python-slugify
|
||||||
|
shortuuid
|
||||||
|
unidecode
|
||||||
|
xmltodict
|
||||||
|
zeroconf
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeCheckInputs = with python.pkgs; [
|
||||||
|
ffmpeg-headless
|
||||||
|
pytest-aiohttp
|
||||||
|
pytestCheckHook
|
||||||
|
] ++ lib.flatten (lib.attrValues optional-dependencies);
|
||||||
|
|
||||||
|
pythonImportsCheck = [ "music_assistant" ];
|
||||||
|
|
||||||
|
passthru = {
|
||||||
|
inherit
|
||||||
|
python
|
||||||
|
pythonPath
|
||||||
|
providerPackages
|
||||||
|
providerNames
|
||||||
|
;
|
||||||
|
tests = nixosTests.music-assistant;
|
||||||
|
};
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
changelog = "https://github.com/music-assistant/server/releases/tag/${version}";
|
||||||
|
description = "Music Assistant is a music library manager for various music sources which can easily stream to a wide range of supported players";
|
||||||
|
longDescription = ''
|
||||||
|
Music Assistant is a free, opensource Media library manager that connects to your streaming services and a wide
|
||||||
|
range of connected speakers. The server is the beating heart, the core of Music Assistant and must run on an
|
||||||
|
always-on device like a Raspberry Pi, a NAS or an Intel NUC or alike.
|
||||||
|
'';
|
||||||
|
homepage = "https://github.com/music-assistant/server";
|
||||||
|
license = licenses.asl20;
|
||||||
|
maintainers = with maintainers; [ hexa ];
|
||||||
|
mainProgram = "mass";
|
||||||
|
};
|
||||||
|
}
|
78
pkgs/by-name/mu/music-assistant/providers.nix
Normal file
78
pkgs/by-name/mu/music-assistant/providers.nix
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
# Do not edit manually, run ./update-providers.py
|
||||||
|
|
||||||
|
{
|
||||||
|
version = "2.0.7";
|
||||||
|
providers = {
|
||||||
|
airplay = [
|
||||||
|
];
|
||||||
|
builtin = [
|
||||||
|
];
|
||||||
|
chromecast = ps: with ps; [
|
||||||
|
pychromecast
|
||||||
|
];
|
||||||
|
deezer = ps: with ps; [
|
||||||
|
pycryptodome
|
||||||
|
]; # missing deezer-python-async
|
||||||
|
dlna = ps: with ps; [
|
||||||
|
async-upnp-client
|
||||||
|
];
|
||||||
|
fanarttv = [
|
||||||
|
];
|
||||||
|
filesystem_local = [
|
||||||
|
];
|
||||||
|
filesystem_smb = [
|
||||||
|
];
|
||||||
|
fully_kiosk = ps: with ps; [
|
||||||
|
python-fullykiosk
|
||||||
|
];
|
||||||
|
hass = [
|
||||||
|
]; # missing hass-client
|
||||||
|
hass_players = [
|
||||||
|
];
|
||||||
|
jellyfin = [
|
||||||
|
]; # missing jellyfin_apiclient_python
|
||||||
|
musicbrainz = [
|
||||||
|
];
|
||||||
|
opensubsonic = ps: with ps; [
|
||||||
|
py-opensonic
|
||||||
|
];
|
||||||
|
plex = ps: with ps; [
|
||||||
|
plexapi
|
||||||
|
];
|
||||||
|
qobuz = [
|
||||||
|
];
|
||||||
|
radiobrowser = ps: with ps; [
|
||||||
|
radios
|
||||||
|
];
|
||||||
|
slimproto = ps: with ps; [
|
||||||
|
aioslimproto
|
||||||
|
];
|
||||||
|
snapcast = ps: with ps; [
|
||||||
|
snapcast
|
||||||
|
];
|
||||||
|
sonos = ps: with ps; [
|
||||||
|
defusedxml
|
||||||
|
soco
|
||||||
|
sonos-websocket
|
||||||
|
];
|
||||||
|
soundcloud = [
|
||||||
|
]; # missing soundcloudpy
|
||||||
|
spotify = [
|
||||||
|
];
|
||||||
|
test = [
|
||||||
|
];
|
||||||
|
theaudiodb = [
|
||||||
|
];
|
||||||
|
tidal = ps: with ps; [
|
||||||
|
tidalapi
|
||||||
|
];
|
||||||
|
tunein = [
|
||||||
|
];
|
||||||
|
ugp = [
|
||||||
|
];
|
||||||
|
ytmusic = ps: with ps; [
|
||||||
|
pytube
|
||||||
|
ytmusicapi
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
218
pkgs/by-name/mu/music-assistant/update-providers.py
Executable file
218
pkgs/by-name/mu/music-assistant/update-providers.py
Executable file
|
@ -0,0 +1,218 @@
|
||||||
|
#!/usr/bin/env nix-shell
|
||||||
|
#!nix-shell -i python3 -p "python3.withPackages (ps: with ps; [ jinja2 mashumaro orjson aiofiles packaging ])" -p pyright ruff isort
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
import tempfile
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from functools import cache
|
||||||
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
from subprocess import check_output, run
|
||||||
|
from typing import Dict, Final, List, Optional, Set, Union, cast
|
||||||
|
from urllib.request import urlopen
|
||||||
|
|
||||||
|
from jinja2 import Environment
|
||||||
|
from packaging.requirements import Requirement
|
||||||
|
|
||||||
|
TEMPLATE = """# Do not edit manually, run ./update-providers.py
|
||||||
|
|
||||||
|
{
|
||||||
|
version = "{{ version }}";
|
||||||
|
providers = {
|
||||||
|
{%- for provider in providers | sort(attribute='domain') %}
|
||||||
|
{{ provider.domain }} = {% if provider.available %}ps: with ps; {% endif %}[
|
||||||
|
{%- for requirement in provider.available | sort %}
|
||||||
|
{{ requirement }}
|
||||||
|
{%- endfor %}
|
||||||
|
];{% if provider.missing %} # missing {{ ", ".join(provider.missing) }}{% endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
ROOT: Final = (
|
||||||
|
check_output(
|
||||||
|
[
|
||||||
|
"git",
|
||||||
|
"rev-parse",
|
||||||
|
"--show-toplevel",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
.decode()
|
||||||
|
.strip()
|
||||||
|
)
|
||||||
|
|
||||||
|
PACKAGE_MAP = {
|
||||||
|
"git+https://github.com/MarvinSchenkel/pytube.git": "pytube",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def run_sync(cmd: List[str]) -> None:
|
||||||
|
print(f"$ {' '.join(cmd)}")
|
||||||
|
process = run(cmd)
|
||||||
|
|
||||||
|
if process.returncode != 0:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
async def check_async(cmd: List[str]) -> str:
|
||||||
|
print(f"$ {' '.join(cmd)}")
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||||
|
)
|
||||||
|
stdout, stderr = await process.communicate()
|
||||||
|
|
||||||
|
if process.returncode != 0:
|
||||||
|
error = stderr.decode()
|
||||||
|
raise RuntimeError(f"{cmd[0]} failed: {error}")
|
||||||
|
|
||||||
|
return stdout.decode().strip()
|
||||||
|
|
||||||
|
|
||||||
|
class Nix:
|
||||||
|
base_cmd: Final = [
|
||||||
|
"nix",
|
||||||
|
"--show-trace",
|
||||||
|
"--extra-experimental-features",
|
||||||
|
"nix-command",
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def _run(cls, args: List[str]) -> Optional[str]:
|
||||||
|
return await check_async(cls.base_cmd + args)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def eval(cls, expr: str) -> Union[List, Dict, int, float, str, bool]:
|
||||||
|
response = await cls._run(["eval", "-f", f"{ROOT}/default.nix", "--json", expr])
|
||||||
|
if response is None:
|
||||||
|
raise RuntimeError("Nix eval expression returned no response")
|
||||||
|
try:
|
||||||
|
return json.loads(response)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
raise RuntimeError("Nix eval response could not be parsed from JSON")
|
||||||
|
|
||||||
|
|
||||||
|
async def get_provider_manifests(version: str = "master") -> List:
|
||||||
|
manifests = []
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
with urlopen(
|
||||||
|
f"https://github.com/music-assistant/music-assistant/archive/{version}.tar.gz"
|
||||||
|
) as response:
|
||||||
|
tarfile.open(fileobj=BytesIO(response.read())).extractall(
|
||||||
|
tmp, filter="data"
|
||||||
|
)
|
||||||
|
|
||||||
|
basedir = Path(os.path.join(tmp, f"server-{version}"))
|
||||||
|
sys.path.append(str(basedir))
|
||||||
|
from music_assistant.common.models.provider import ProviderManifest # type: ignore
|
||||||
|
|
||||||
|
for fn in basedir.glob("**/manifest.json"):
|
||||||
|
manifests.append(await ProviderManifest.parse(fn))
|
||||||
|
|
||||||
|
return manifests
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
|
def packageset_attributes():
|
||||||
|
output = check_output(
|
||||||
|
[
|
||||||
|
"nix-env",
|
||||||
|
"-f",
|
||||||
|
ROOT,
|
||||||
|
"-qa",
|
||||||
|
"-A",
|
||||||
|
"music-assistant.python.pkgs",
|
||||||
|
"--arg",
|
||||||
|
"config",
|
||||||
|
"{ allowAliases = false; }",
|
||||||
|
"--json",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return json.loads(output)
|
||||||
|
|
||||||
|
|
||||||
|
class TooManyMatches(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoMatch(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_package_attribute(package: str) -> str:
|
||||||
|
pattern = re.compile(rf"^music-assistant\.python\.pkgs\.{package}$", re.I)
|
||||||
|
packages = packageset_attributes()
|
||||||
|
matches = []
|
||||||
|
for attr in packages.keys():
|
||||||
|
if pattern.match(attr):
|
||||||
|
matches.append(attr.split(".")[-1])
|
||||||
|
|
||||||
|
if len(matches) > 1:
|
||||||
|
raise TooManyMatches(
|
||||||
|
f"Too many matching attributes for {package}: {' '.join(matches)}"
|
||||||
|
)
|
||||||
|
if not matches:
|
||||||
|
raise NoMatch(f"No matching attribute for {package}")
|
||||||
|
|
||||||
|
return matches.pop()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Provider:
|
||||||
|
domain: str
|
||||||
|
available: list[str] = field(default_factory=list)
|
||||||
|
missing: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.domain == other.domain
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.domain)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_providers(manifests) -> Set:
|
||||||
|
providers = set()
|
||||||
|
for manifest in manifests:
|
||||||
|
provider = Provider(manifest.domain)
|
||||||
|
for requirement in manifest.requirements:
|
||||||
|
# allow substituting requirement specifications that packaging cannot parse
|
||||||
|
if requirement in PACKAGE_MAP:
|
||||||
|
requirement = PACKAGE_MAP[requirement]
|
||||||
|
requirement = Requirement(requirement)
|
||||||
|
try:
|
||||||
|
provider.available.append(resolve_package_attribute(requirement.name))
|
||||||
|
except TooManyMatches as ex:
|
||||||
|
print(ex, file=sys.stderr)
|
||||||
|
provider.missing.append(requirement.name)
|
||||||
|
except NoMatch:
|
||||||
|
provider.missing.append(requirement.name)
|
||||||
|
providers.add(provider)
|
||||||
|
return providers
|
||||||
|
|
||||||
|
|
||||||
|
def render(version: str, providers: Set):
|
||||||
|
path = os.path.join(ROOT, "pkgs/by-name/mu/music-assistant/providers.nix")
|
||||||
|
env = Environment()
|
||||||
|
template = env.from_string(TEMPLATE)
|
||||||
|
template.stream(version=version, providers=providers).dump(path)
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
version: str = cast(str, await Nix.eval("music-assistant.version"))
|
||||||
|
manifests = await get_provider_manifests(version)
|
||||||
|
providers = resolve_providers(manifests)
|
||||||
|
render(version, providers)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run_sync(["pyright", __file__])
|
||||||
|
run_sync(["ruff", "check", "--ignore=E501", __file__])
|
||||||
|
run_sync(["isort", __file__])
|
||||||
|
run_sync(["ruff", "format", __file__])
|
||||||
|
asyncio.run(main())
|
42
pkgs/development/python-modules/memory-tempfile/default.nix
Normal file
42
pkgs/development/python-modules/memory-tempfile/default.nix
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{ lib
|
||||||
|
, buildPythonPackage
|
||||||
|
, fetchFromGitHub
|
||||||
|
, fetchpatch2
|
||||||
|
, poetry-core
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildPythonPackage rec {
|
||||||
|
pname = "memory-tempfile";
|
||||||
|
version = "2.2.3";
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "mbello";
|
||||||
|
repo = "memory-tempfile";
|
||||||
|
rev = "v${version}";
|
||||||
|
hash = "sha256-4fz2CLkZdy2e1GwGw/afG54LkUVJ4cza70jcbX3rVlQ=";
|
||||||
|
};
|
||||||
|
|
||||||
|
patches = [
|
||||||
|
(fetchpatch2 {
|
||||||
|
# Migrate to poetry-core build backend
|
||||||
|
# https://github.com/mbello/memory-tempfile/pull/13
|
||||||
|
name = "poetry-core.patch";
|
||||||
|
url = "https://github.com/mbello/memory-tempfile/commit/938a3a3abf01756b1629eca6c69e970021bbc7c0.patch";
|
||||||
|
hash = "sha256-q3027MwKXtX09MH7T2UrX19BImK1FJo+YxADfxcdTME=";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
build-system = [ poetry-core ];
|
||||||
|
|
||||||
|
doCheck = false; # constrained selection of memory backed filesystems due to build sandbox
|
||||||
|
|
||||||
|
pythonImportsCheck = [ "memory_tempfile" ];
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Create temporary files and temporary dirs in memory-based filesystems on Linux";
|
||||||
|
homepage = "https://github.com/mbello/memory-tempfile";
|
||||||
|
license = licenses.mit;
|
||||||
|
maintainers = with maintainers; [ hexa ];
|
||||||
|
};
|
||||||
|
}
|
37
pkgs/development/python-modules/py-opensonic/default.nix
Normal file
37
pkgs/development/python-modules/py-opensonic/default.nix
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{ lib
|
||||||
|
, buildPythonPackage
|
||||||
|
, fetchFromGitHub
|
||||||
|
, setuptools
|
||||||
|
, requests
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildPythonPackage rec {
|
||||||
|
pname = "py-opensonic";
|
||||||
|
version = "5.1.1";
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "khers";
|
||||||
|
repo = "py-opensonic";
|
||||||
|
rev = "v${version}";
|
||||||
|
hash = "sha256-wXTXuX+iIMEoALxsciopucmvBxAyEeiOgjJPrbD63gM=";
|
||||||
|
};
|
||||||
|
|
||||||
|
build-system = [ setuptools ];
|
||||||
|
|
||||||
|
dependencies = [ requests ];
|
||||||
|
|
||||||
|
doCheck = false; # no tests
|
||||||
|
|
||||||
|
pythonImportsCheck = [
|
||||||
|
"libopensonic"
|
||||||
|
];
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Python library to wrap the Open Subsonic REST API";
|
||||||
|
homepage = "https://github.com/khers/py-opensonic";
|
||||||
|
changelog = "https://github.com/khers/py-opensonic/blob/${src.rev}/CHANGELOG.md";
|
||||||
|
license = licenses.gpl3Only;
|
||||||
|
maintainers = with maintainers; [ hexa ];
|
||||||
|
};
|
||||||
|
}
|
|
@ -27,6 +27,9 @@ home-assistant.python.pkgs.buildPythonPackage (
|
||||||
mkdir $out
|
mkdir $out
|
||||||
cp -r ./custom_components/ $out/
|
cp -r ./custom_components/ $out/
|
||||||
|
|
||||||
|
# optionally copy sentences, if they exist
|
||||||
|
cp -r ./custom_sentences/ $out/ || true
|
||||||
|
|
||||||
runHook postInstall
|
runHook postInstall
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
|
|
||||||
localtuya = callPackage ./localtuya {};
|
localtuya = callPackage ./localtuya {};
|
||||||
|
|
||||||
|
mass = callPackage ./mass { };
|
||||||
|
|
||||||
midea_ac_lan = callPackage ./midea_ac_lan {};
|
midea_ac_lan = callPackage ./midea_ac_lan {};
|
||||||
|
|
||||||
midea-air-appliances-lan = callPackage ./midea-air-appliances-lan {};
|
midea-air-appliances-lan = callPackage ./midea-air-appliances-lan {};
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
{ lib
|
||||||
|
, buildHomeAssistantComponent
|
||||||
|
, fetchFromGitHub
|
||||||
|
, toPythonModule
|
||||||
|
, async-timeout
|
||||||
|
, music-assistant
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildHomeAssistantComponent rec {
|
||||||
|
owner = "music-assistant";
|
||||||
|
domain = "mass";
|
||||||
|
version = "2024.6.2";
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "music-assistant";
|
||||||
|
repo = "hass-music-assistant";
|
||||||
|
rev = version;
|
||||||
|
hash = "sha256-Wvc+vUYkUJmS4U34Sh/sDCVXmQA0AtEqIT8MNXd++3M=";
|
||||||
|
};
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
async-timeout
|
||||||
|
(toPythonModule music-assistant)
|
||||||
|
];
|
||||||
|
|
||||||
|
dontCheckManifest = true; # expects music-assistant 2.0.6, we have 2.0.7
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Turn your Home Assistant instance into a jukebox, hassle free streaming of your favorite media to Home Assistant media players";
|
||||||
|
homepage = "https://github.com/music-assistant/hass-music-assistant";
|
||||||
|
license = licenses.asl20;
|
||||||
|
maintainers = with maintainers; [ hexa ];
|
||||||
|
};
|
||||||
|
}
|
|
@ -7524,6 +7524,8 @@ self: super: with self; {
|
||||||
|
|
||||||
memory-profiler = callPackage ../development/python-modules/memory-profiler { };
|
memory-profiler = callPackage ../development/python-modules/memory-profiler { };
|
||||||
|
|
||||||
|
memory-tempfile = callPackage ../development/python-modules/memory-tempfile { };
|
||||||
|
|
||||||
meraki = callPackage ../development/python-modules/meraki { };
|
meraki = callPackage ../development/python-modules/meraki { };
|
||||||
|
|
||||||
mercadopago = callPackage ../development/python-modules/mercadopago { };
|
mercadopago = callPackage ../development/python-modules/mercadopago { };
|
||||||
|
@ -9195,6 +9197,8 @@ self: super: with self; {
|
||||||
|
|
||||||
py-expression-eval = callPackage ../development/python-modules/py-expression-eval { };
|
py-expression-eval = callPackage ../development/python-modules/py-expression-eval { };
|
||||||
|
|
||||||
|
py-opensonic = callPackage ../development/python-modules/py-opensonic { };
|
||||||
|
|
||||||
py-radix-sr = callPackage ../development/python-modules/py-radix-sr { };
|
py-radix-sr = callPackage ../development/python-modules/py-radix-sr { };
|
||||||
|
|
||||||
nwdiag = callPackage ../development/python-modules/nwdiag { };
|
nwdiag = callPackage ../development/python-modules/nwdiag { };
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue