lol

Merge pull request #311927 from mweinelt/music-assistant

music-assistant: init at 2.0.7

authored by

Martin Weinelt and committed by
GitHub
bf8439ef b1afff0e

+781
+2
nixos/doc/manual/release-notes/rl-2411.section.md
··· 33 33 34 34 - [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). 35 35 36 + - [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). 37 + 36 38 - [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). 37 39 38 40 - [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).
+1
nixos/modules/module-list.nix
··· 376 376 ./services/audio/mopidy.nix 377 377 ./services/audio/mpd.nix 378 378 ./services/audio/mpdscribble.nix 379 + ./services/audio/music-assistant.nix 379 380 ./services/audio/mympd.nix 380 381 ./services/audio/navidrome.nix 381 382 ./services/audio/networkaudiod.nix
+113
nixos/modules/services/audio/music-assistant.nix
··· 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + utils, 6 + ... 7 + }: 8 + 9 + let 10 + inherit (lib) 11 + mkIf 12 + mkEnableOption 13 + mkOption 14 + mkPackageOption 15 + types 16 + ; 17 + 18 + inherit (types) 19 + listOf 20 + enum 21 + str 22 + ; 23 + 24 + cfg = config.services.music-assistant; 25 + 26 + finalPackage = cfg.package.override { 27 + inherit (cfg) providers; 28 + }; 29 + in 30 + 31 + { 32 + meta.buildDocsInSandbox = false; 33 + 34 + options.services.music-assistant = { 35 + enable = mkEnableOption "Music Assistant"; 36 + 37 + package = mkPackageOption pkgs "music-assistant" { }; 38 + 39 + extraOptions = mkOption { 40 + type = listOf str; 41 + default = [ "--config" "/var/lib/music-assistant" ]; 42 + example = [ 43 + "--log-level" 44 + "DEBUG" 45 + ]; 46 + description = '' 47 + List of extra options to pass to the music-assistant executable. 48 + ''; 49 + }; 50 + 51 + providers = mkOption { 52 + type = listOf (enum cfg.package.providerNames); 53 + default = []; 54 + example = [ 55 + "opensubsonic" 56 + "snapcast" 57 + ]; 58 + description = '' 59 + List of provider names for which dependencies will be installed. 60 + ''; 61 + }; 62 + }; 63 + 64 + config = mkIf cfg.enable { 65 + systemd.services.music-assistant = { 66 + description = "Music Assistant"; 67 + documentation = [ "https://music-assistant.io" ]; 68 + 69 + wantedBy = [ "multi-user.target" ]; 70 + 71 + environment = { 72 + HOME = "/var/lib/music-assistant"; 73 + PYTHONPATH = finalPackage.pythonPath; 74 + }; 75 + 76 + serviceConfig = { 77 + ExecStart = utils.escapeSystemdExecArgs ([ 78 + (lib.getExe cfg.package) 79 + ] ++ cfg.extraOptions); 80 + DynamicUser = true; 81 + StateDirectory = "music-assistant"; 82 + AmbientCapabilities = ""; 83 + CapabilityBoundingSet = [ "" ]; 84 + DevicePolicy = "closed"; 85 + LockPersonality = true; 86 + MemoryDenyWriteExecute = true; 87 + ProcSubset = "pid"; 88 + ProtectClock = true; 89 + ProtectControlGroups = true; 90 + ProtectHome = true; 91 + ProtectHostname = true; 92 + ProtectKernelLogs = true; 93 + ProtectKernelModules = true; 94 + ProtectKernelTunables = true; 95 + ProtectProc = "invisible"; 96 + RestrictAddressFamilies = [ 97 + "AF_INET" 98 + "AF_INET6" 99 + "AF_NETLINK" 100 + ]; 101 + RestrictNamespaces = true; 102 + RestrictRealtime = true; 103 + SystemCallArchitectures = "native"; 104 + SystemCallFilter = [ 105 + "@system-service" 106 + "~@privileged @resources" 107 + ]; 108 + RestrictSUIDSGID = true; 109 + UMask = "0077"; 110 + }; 111 + }; 112 + }; 113 + }
+1
nixos/tests/all-tests.nix
··· 598 598 # Fails on aarch64-linux at the PDF creation step - need to debug this on an 599 599 # aarch64 machine.. 600 600 musescore = handleTestOn ["x86_64-linux"] ./musescore.nix {}; 601 + music-assistant = runTest ./music-assistant.nix; 601 602 munin = handleTest ./munin.nix {}; 602 603 mutableUsers = handleTest ./mutable-users.nix {}; 603 604 mycelium = handleTest ./mycelium {};
+21
nixos/tests/music-assistant.nix
··· 1 + { 2 + lib, 3 + ... 4 + }: 5 + 6 + { 7 + name = "music-assistant"; 8 + meta.maintainers = with lib.maintainers; [ hexa ]; 9 + 10 + nodes.machine = { 11 + services.music-assistant = { 12 + enable = true; 13 + }; 14 + }; 15 + 16 + testScript = '' 17 + machine.wait_for_unit("music-assistant.service") 18 + machine.wait_until_succeeds("curl --fail http://localhost:8095") 19 + machine.log(machine.succeed("systemd-analyze security music-assistant.service | grep -v ✓")) 20 + ''; 21 + }
+71
pkgs/by-name/mu/music-assistant/ffmpeg.patch
··· 1 + diff --git a/music_assistant/server/helpers/audio.py b/music_assistant/server/helpers/audio.py 2 + index 42011923..1e5dc112 100644 3 + --- a/music_assistant/server/helpers/audio.py 4 + +++ b/music_assistant/server/helpers/audio.py 5 + @@ -218,7 +218,7 @@ async def crossfade_pcm_parts( 6 + await outfile.write(fade_out_part) 7 + args = [ 8 + # generic args 9 + - "ffmpeg", 10 + + "@ffmpeg@", 11 + "-hide_banner", 12 + "-loglevel", 13 + "quiet", 14 + @@ -281,7 +281,7 @@ async def strip_silence( 15 + ) -> bytes: 16 + """Strip silence from begin or end of pcm audio using ffmpeg.""" 17 + fmt = ContentType.from_bit_depth(bit_depth) 18 + - args = ["ffmpeg", "-hide_banner", "-loglevel", "quiet"] 19 + + args = ["@ffmpeg@", "-hide_banner", "-loglevel", "quiet"] 20 + args += [ 21 + "-acodec", 22 + fmt.name.lower(), 23 + @@ -823,7 +823,7 @@ async def get_ffmpeg_stream( 24 + async def check_audio_support() -> tuple[bool, bool, str]: 25 + """Check if ffmpeg is present (with/without libsoxr support).""" 26 + # check for FFmpeg presence 27 + - returncode, output = await check_output("ffmpeg -version") 28 + + returncode, output = await check_output("@ffmpeg@ -version") 29 + ffmpeg_present = returncode == 0 and "FFmpeg" in output.decode() 30 + 31 + # use globals as in-memory cache 32 + @@ -877,7 +877,7 @@ async def get_silence( 33 + return 34 + # use ffmpeg for all other encodings 35 + args = [ 36 + - "ffmpeg", 37 + + "@ffmpeg@", 38 + "-hide_banner", 39 + "-loglevel", 40 + "quiet", 41 + @@ -971,7 +971,7 @@ def get_ffmpeg_args( 42 + 43 + # generic args 44 + generic_args = [ 45 + - "ffmpeg", 46 + + "@ffmpeg@", 47 + "-hide_banner", 48 + "-loglevel", 49 + loglevel, 50 + diff --git a/music_assistant/server/helpers/tags.py b/music_assistant/server/helpers/tags.py 51 + index dc38e4c0..f4f3e2fe 100644 52 + --- a/music_assistant/server/helpers/tags.py 53 + +++ b/music_assistant/server/helpers/tags.py 54 + @@ -368,7 +368,7 @@ async def parse_tags( 55 + file_path = input_file if isinstance(input_file, str) else "-" 56 + 57 + args = ( 58 + - "ffprobe", 59 + + "@ffprobe@", 60 + "-hide_banner", 61 + "-loglevel", 62 + "fatal", 63 + @@ -440,7 +440,7 @@ async def get_embedded_image(input_file: str | AsyncGenerator[bytes, None]) -> b 64 + """ 65 + file_path = input_file if isinstance(input_file, str) else "-" 66 + args = ( 67 + - "ffmpeg", 68 + + "@ffmpeg@", 69 + "-hide_banner", 70 + "-loglevel", 71 + "error",
+35
pkgs/by-name/mu/music-assistant/frontend.nix
··· 1 + { lib 2 + , buildPythonPackage 3 + , fetchPypi 4 + , setuptools 5 + }: 6 + 7 + buildPythonPackage rec { 8 + pname = "music-assistant-frontend"; 9 + version = "2.5.15"; 10 + pyproject = true; 11 + 12 + src = fetchPypi { 13 + inherit pname version; 14 + hash = "sha256-D8VFdXgaVXSxk7c24kvb9TflFztS1zLwW4qGqV32nLo="; 15 + }; 16 + 17 + postPatch = '' 18 + substituteInPlace pyproject.toml \ 19 + --replace-fail "~=" ">=" 20 + ''; 21 + 22 + build-system = [ setuptools ]; 23 + 24 + doCheck = false; 25 + 26 + pythonImportsCheck = [ "music_assistant_frontend" ]; 27 + 28 + meta = with lib; { 29 + changelog = "https://github.com/music-assistant/frontend/releases/tag/${version}"; 30 + description = "The Music Assistant frontend"; 31 + homepage = "https://github.com/music-assistant/frontend"; 32 + license = licenses.asl20; 33 + maintainers = with maintainers; [ hexa ]; 34 + }; 35 + }
+119
pkgs/by-name/mu/music-assistant/package.nix
··· 1 + { lib 2 + , python3 3 + , fetchFromGitHub 4 + , ffmpeg-headless 5 + , nixosTests 6 + , substituteAll 7 + , providers ? [ ] 8 + }: 9 + 10 + let 11 + python = python3.override { 12 + packageOverrides = self: super: { 13 + music-assistant-frontend = self.callPackage ./frontend.nix { }; 14 + }; 15 + }; 16 + 17 + providerPackages = (import ./providers.nix).providers; 18 + providerNames = lib.attrNames providerPackages; 19 + providerDependencies = lib.concatMap (provider: (providerPackages.${provider} python.pkgs)) providers; 20 + 21 + pythonPath = python.pkgs.makePythonPath providerDependencies; 22 + in 23 + 24 + python.pkgs.buildPythonApplication rec { 25 + pname = "music-assistant"; 26 + version = "2.0.7"; 27 + pyproject = true; 28 + 29 + src = fetchFromGitHub { 30 + owner = "music-assistant"; 31 + repo = "server"; 32 + rev = version; 33 + hash = "sha256-JtdlZ3hH4fRU5TjmMUlrdSSCnLrIGCuSwSSrnLgjYEs="; 34 + }; 35 + 36 + patches = [ 37 + (substituteAll { 38 + src = ./ffmpeg.patch; 39 + ffmpeg = "${lib.getBin ffmpeg-headless}/bin/ffmpeg"; 40 + ffprobe = "${lib.getBin ffmpeg-headless}/bin/ffprobe"; 41 + }) 42 + ]; 43 + 44 + postPatch = '' 45 + sed -i "/--cov/d" pyproject.toml 46 + 47 + substituteInPlace pyproject.toml \ 48 + --replace-fail "0.0.0" "${version}" 49 + ''; 50 + 51 + build-system = with python.pkgs; [ 52 + setuptools 53 + ]; 54 + 55 + dependencies = with python.pkgs; [ 56 + aiohttp 57 + mashumaro 58 + orjson 59 + ] ++ optional-dependencies.server; 60 + 61 + optional-dependencies = with python.pkgs; { 62 + server = [ 63 + aiodns 64 + aiofiles 65 + aiohttp 66 + aiorun 67 + aiosqlite 68 + asyncio-throttle 69 + brotli 70 + certifi 71 + colorlog 72 + cryptography 73 + faust-cchardet 74 + ifaddr 75 + mashumaro 76 + memory-tempfile 77 + music-assistant-frontend 78 + orjson 79 + pillow 80 + python-slugify 81 + shortuuid 82 + unidecode 83 + xmltodict 84 + zeroconf 85 + ]; 86 + }; 87 + 88 + nativeCheckInputs = with python.pkgs; [ 89 + ffmpeg-headless 90 + pytest-aiohttp 91 + pytestCheckHook 92 + ] ++ lib.flatten (lib.attrValues optional-dependencies); 93 + 94 + pythonImportsCheck = [ "music_assistant" ]; 95 + 96 + passthru = { 97 + inherit 98 + python 99 + pythonPath 100 + providerPackages 101 + providerNames 102 + ; 103 + tests = nixosTests.music-assistant; 104 + }; 105 + 106 + meta = with lib; { 107 + changelog = "https://github.com/music-assistant/server/releases/tag/${version}"; 108 + description = "Music Assistant is a music library manager for various music sources which can easily stream to a wide range of supported players"; 109 + longDescription = '' 110 + Music Assistant is a free, opensource Media library manager that connects to your streaming services and a wide 111 + range of connected speakers. The server is the beating heart, the core of Music Assistant and must run on an 112 + always-on device like a Raspberry Pi, a NAS or an Intel NUC or alike. 113 + ''; 114 + homepage = "https://github.com/music-assistant/server"; 115 + license = licenses.asl20; 116 + maintainers = with maintainers; [ hexa ]; 117 + mainProgram = "mass"; 118 + }; 119 + }
+78
pkgs/by-name/mu/music-assistant/providers.nix
··· 1 + # Do not edit manually, run ./update-providers.py 2 + 3 + { 4 + version = "2.0.7"; 5 + providers = { 6 + airplay = [ 7 + ]; 8 + builtin = [ 9 + ]; 10 + chromecast = ps: with ps; [ 11 + pychromecast 12 + ]; 13 + deezer = ps: with ps; [ 14 + pycryptodome 15 + ]; # missing deezer-python-async 16 + dlna = ps: with ps; [ 17 + async-upnp-client 18 + ]; 19 + fanarttv = [ 20 + ]; 21 + filesystem_local = [ 22 + ]; 23 + filesystem_smb = [ 24 + ]; 25 + fully_kiosk = ps: with ps; [ 26 + python-fullykiosk 27 + ]; 28 + hass = [ 29 + ]; # missing hass-client 30 + hass_players = [ 31 + ]; 32 + jellyfin = [ 33 + ]; # missing jellyfin_apiclient_python 34 + musicbrainz = [ 35 + ]; 36 + opensubsonic = ps: with ps; [ 37 + py-opensonic 38 + ]; 39 + plex = ps: with ps; [ 40 + plexapi 41 + ]; 42 + qobuz = [ 43 + ]; 44 + radiobrowser = ps: with ps; [ 45 + radios 46 + ]; 47 + slimproto = ps: with ps; [ 48 + aioslimproto 49 + ]; 50 + snapcast = ps: with ps; [ 51 + snapcast 52 + ]; 53 + sonos = ps: with ps; [ 54 + defusedxml 55 + soco 56 + sonos-websocket 57 + ]; 58 + soundcloud = [ 59 + ]; # missing soundcloudpy 60 + spotify = [ 61 + ]; 62 + test = [ 63 + ]; 64 + theaudiodb = [ 65 + ]; 66 + tidal = ps: with ps; [ 67 + tidalapi 68 + ]; 69 + tunein = [ 70 + ]; 71 + ugp = [ 72 + ]; 73 + ytmusic = ps: with ps; [ 74 + pytube 75 + ytmusicapi 76 + ]; 77 + }; 78 + }
+218
pkgs/by-name/mu/music-assistant/update-providers.py
··· 1 + #!/usr/bin/env nix-shell 2 + #!nix-shell -i python3 -p "python3.withPackages (ps: with ps; [ jinja2 mashumaro orjson aiofiles packaging ])" -p pyright ruff isort 3 + import asyncio 4 + import json 5 + import os.path 6 + import re 7 + import sys 8 + import tarfile 9 + import tempfile 10 + from dataclasses import dataclass, field 11 + from functools import cache 12 + from io import BytesIO 13 + from pathlib import Path 14 + from subprocess import check_output, run 15 + from typing import Dict, Final, List, Optional, Set, Union, cast 16 + from urllib.request import urlopen 17 + 18 + from jinja2 import Environment 19 + from packaging.requirements import Requirement 20 + 21 + TEMPLATE = """# Do not edit manually, run ./update-providers.py 22 + 23 + { 24 + version = "{{ version }}"; 25 + providers = { 26 + {%- for provider in providers | sort(attribute='domain') %} 27 + {{ provider.domain }} = {% if provider.available %}ps: with ps; {% endif %}[ 28 + {%- for requirement in provider.available | sort %} 29 + {{ requirement }} 30 + {%- endfor %} 31 + ];{% if provider.missing %} # missing {{ ", ".join(provider.missing) }}{% endif %} 32 + {%- endfor %} 33 + }; 34 + } 35 + 36 + """ 37 + 38 + 39 + ROOT: Final = ( 40 + check_output( 41 + [ 42 + "git", 43 + "rev-parse", 44 + "--show-toplevel", 45 + ] 46 + ) 47 + .decode() 48 + .strip() 49 + ) 50 + 51 + PACKAGE_MAP = { 52 + "git+https://github.com/MarvinSchenkel/pytube.git": "pytube", 53 + } 54 + 55 + 56 + def run_sync(cmd: List[str]) -> None: 57 + print(f"$ {' '.join(cmd)}") 58 + process = run(cmd) 59 + 60 + if process.returncode != 0: 61 + sys.exit(1) 62 + 63 + 64 + async def check_async(cmd: List[str]) -> str: 65 + print(f"$ {' '.join(cmd)}") 66 + process = await asyncio.create_subprocess_exec( 67 + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE 68 + ) 69 + stdout, stderr = await process.communicate() 70 + 71 + if process.returncode != 0: 72 + error = stderr.decode() 73 + raise RuntimeError(f"{cmd[0]} failed: {error}") 74 + 75 + return stdout.decode().strip() 76 + 77 + 78 + class Nix: 79 + base_cmd: Final = [ 80 + "nix", 81 + "--show-trace", 82 + "--extra-experimental-features", 83 + "nix-command", 84 + ] 85 + 86 + @classmethod 87 + async def _run(cls, args: List[str]) -> Optional[str]: 88 + return await check_async(cls.base_cmd + args) 89 + 90 + @classmethod 91 + async def eval(cls, expr: str) -> Union[List, Dict, int, float, str, bool]: 92 + response = await cls._run(["eval", "-f", f"{ROOT}/default.nix", "--json", expr]) 93 + if response is None: 94 + raise RuntimeError("Nix eval expression returned no response") 95 + try: 96 + return json.loads(response) 97 + except (TypeError, ValueError): 98 + raise RuntimeError("Nix eval response could not be parsed from JSON") 99 + 100 + 101 + async def get_provider_manifests(version: str = "master") -> List: 102 + manifests = [] 103 + with tempfile.TemporaryDirectory() as tmp: 104 + with urlopen( 105 + f"https://github.com/music-assistant/music-assistant/archive/{version}.tar.gz" 106 + ) as response: 107 + tarfile.open(fileobj=BytesIO(response.read())).extractall( 108 + tmp, filter="data" 109 + ) 110 + 111 + basedir = Path(os.path.join(tmp, f"server-{version}")) 112 + sys.path.append(str(basedir)) 113 + from music_assistant.common.models.provider import ProviderManifest # type: ignore 114 + 115 + for fn in basedir.glob("**/manifest.json"): 116 + manifests.append(await ProviderManifest.parse(fn)) 117 + 118 + return manifests 119 + 120 + 121 + @cache 122 + def packageset_attributes(): 123 + output = check_output( 124 + [ 125 + "nix-env", 126 + "-f", 127 + ROOT, 128 + "-qa", 129 + "-A", 130 + "music-assistant.python.pkgs", 131 + "--arg", 132 + "config", 133 + "{ allowAliases = false; }", 134 + "--json", 135 + ] 136 + ) 137 + return json.loads(output) 138 + 139 + 140 + class TooManyMatches(Exception): 141 + pass 142 + 143 + 144 + class NoMatch(Exception): 145 + pass 146 + 147 + 148 + def resolve_package_attribute(package: str) -> str: 149 + pattern = re.compile(rf"^music-assistant\.python\.pkgs\.{package}$", re.I) 150 + packages = packageset_attributes() 151 + matches = [] 152 + for attr in packages.keys(): 153 + if pattern.match(attr): 154 + matches.append(attr.split(".")[-1]) 155 + 156 + if len(matches) > 1: 157 + raise TooManyMatches( 158 + f"Too many matching attributes for {package}: {' '.join(matches)}" 159 + ) 160 + if not matches: 161 + raise NoMatch(f"No matching attribute for {package}") 162 + 163 + return matches.pop() 164 + 165 + 166 + @dataclass 167 + class Provider: 168 + domain: str 169 + available: list[str] = field(default_factory=list) 170 + missing: list[str] = field(default_factory=list) 171 + 172 + def __eq__(self, other): 173 + return self.domain == other.domain 174 + 175 + def __hash__(self): 176 + return hash(self.domain) 177 + 178 + 179 + def resolve_providers(manifests) -> Set: 180 + providers = set() 181 + for manifest in manifests: 182 + provider = Provider(manifest.domain) 183 + for requirement in manifest.requirements: 184 + # allow substituting requirement specifications that packaging cannot parse 185 + if requirement in PACKAGE_MAP: 186 + requirement = PACKAGE_MAP[requirement] 187 + requirement = Requirement(requirement) 188 + try: 189 + provider.available.append(resolve_package_attribute(requirement.name)) 190 + except TooManyMatches as ex: 191 + print(ex, file=sys.stderr) 192 + provider.missing.append(requirement.name) 193 + except NoMatch: 194 + provider.missing.append(requirement.name) 195 + providers.add(provider) 196 + return providers 197 + 198 + 199 + def render(version: str, providers: Set): 200 + path = os.path.join(ROOT, "pkgs/by-name/mu/music-assistant/providers.nix") 201 + env = Environment() 202 + template = env.from_string(TEMPLATE) 203 + template.stream(version=version, providers=providers).dump(path) 204 + 205 + 206 + async def main(): 207 + version: str = cast(str, await Nix.eval("music-assistant.version")) 208 + manifests = await get_provider_manifests(version) 209 + providers = resolve_providers(manifests) 210 + render(version, providers) 211 + 212 + 213 + if __name__ == "__main__": 214 + run_sync(["pyright", __file__]) 215 + run_sync(["ruff", "check", "--ignore=E501", __file__]) 216 + run_sync(["isort", __file__]) 217 + run_sync(["ruff", "format", __file__]) 218 + asyncio.run(main())
+42
pkgs/development/python-modules/memory-tempfile/default.nix
··· 1 + { lib 2 + , buildPythonPackage 3 + , fetchFromGitHub 4 + , fetchpatch2 5 + , poetry-core 6 + }: 7 + 8 + buildPythonPackage rec { 9 + pname = "memory-tempfile"; 10 + version = "2.2.3"; 11 + pyproject = true; 12 + 13 + src = fetchFromGitHub { 14 + owner = "mbello"; 15 + repo = "memory-tempfile"; 16 + rev = "v${version}"; 17 + hash = "sha256-4fz2CLkZdy2e1GwGw/afG54LkUVJ4cza70jcbX3rVlQ="; 18 + }; 19 + 20 + patches = [ 21 + (fetchpatch2 { 22 + # Migrate to poetry-core build backend 23 + # https://github.com/mbello/memory-tempfile/pull/13 24 + name = "poetry-core.patch"; 25 + url = "https://github.com/mbello/memory-tempfile/commit/938a3a3abf01756b1629eca6c69e970021bbc7c0.patch"; 26 + hash = "sha256-q3027MwKXtX09MH7T2UrX19BImK1FJo+YxADfxcdTME="; 27 + }) 28 + ]; 29 + 30 + build-system = [ poetry-core ]; 31 + 32 + doCheck = false; # constrained selection of memory backed filesystems due to build sandbox 33 + 34 + pythonImportsCheck = [ "memory_tempfile" ]; 35 + 36 + meta = with lib; { 37 + description = "Create temporary files and temporary dirs in memory-based filesystems on Linux"; 38 + homepage = "https://github.com/mbello/memory-tempfile"; 39 + license = licenses.mit; 40 + maintainers = with maintainers; [ hexa ]; 41 + }; 42 + }
+37
pkgs/development/python-modules/py-opensonic/default.nix
··· 1 + { lib 2 + , buildPythonPackage 3 + , fetchFromGitHub 4 + , setuptools 5 + , requests 6 + }: 7 + 8 + buildPythonPackage rec { 9 + pname = "py-opensonic"; 10 + version = "5.1.1"; 11 + pyproject = true; 12 + 13 + src = fetchFromGitHub { 14 + owner = "khers"; 15 + repo = "py-opensonic"; 16 + rev = "v${version}"; 17 + hash = "sha256-wXTXuX+iIMEoALxsciopucmvBxAyEeiOgjJPrbD63gM="; 18 + }; 19 + 20 + build-system = [ setuptools ]; 21 + 22 + dependencies = [ requests ]; 23 + 24 + doCheck = false; # no tests 25 + 26 + pythonImportsCheck = [ 27 + "libopensonic" 28 + ]; 29 + 30 + meta = with lib; { 31 + description = "Python library to wrap the Open Subsonic REST API"; 32 + homepage = "https://github.com/khers/py-opensonic"; 33 + changelog = "https://github.com/khers/py-opensonic/blob/${src.rev}/CHANGELOG.md"; 34 + license = licenses.gpl3Only; 35 + maintainers = with maintainers; [ hexa ]; 36 + }; 37 + }
+3
pkgs/servers/home-assistant/build-custom-component/default.nix
··· 27 27 mkdir $out 28 28 cp -r ./custom_components/ $out/ 29 29 30 + # optionally copy sentences, if they exist 31 + cp -r ./custom_sentences/ $out/ || true 32 + 30 33 runHook postInstall 31 34 ''; 32 35
+2
pkgs/servers/home-assistant/custom-components/default.nix
··· 32 32 33 33 localtuya = callPackage ./localtuya {}; 34 34 35 + mass = callPackage ./mass { }; 36 + 35 37 midea_ac_lan = callPackage ./midea_ac_lan {}; 36 38 37 39 midea-air-appliances-lan = callPackage ./midea-air-appliances-lan {};
+34
pkgs/servers/home-assistant/custom-components/mass/default.nix
··· 1 + { lib 2 + , buildHomeAssistantComponent 3 + , fetchFromGitHub 4 + , toPythonModule 5 + , async-timeout 6 + , music-assistant 7 + }: 8 + 9 + buildHomeAssistantComponent rec { 10 + owner = "music-assistant"; 11 + domain = "mass"; 12 + version = "2024.6.2"; 13 + 14 + src = fetchFromGitHub { 15 + owner = "music-assistant"; 16 + repo = "hass-music-assistant"; 17 + rev = version; 18 + hash = "sha256-Wvc+vUYkUJmS4U34Sh/sDCVXmQA0AtEqIT8MNXd++3M="; 19 + }; 20 + 21 + dependencies = [ 22 + async-timeout 23 + (toPythonModule music-assistant) 24 + ]; 25 + 26 + dontCheckManifest = true; # expects music-assistant 2.0.6, we have 2.0.7 27 + 28 + meta = with lib; { 29 + description = "Turn your Home Assistant instance into a jukebox, hassle free streaming of your favorite media to Home Assistant media players"; 30 + homepage = "https://github.com/music-assistant/hass-music-assistant"; 31 + license = licenses.asl20; 32 + maintainers = with maintainers; [ hexa ]; 33 + }; 34 + }
+4
pkgs/top-level/python-packages.nix
··· 7524 7524 7525 7525 memory-profiler = callPackage ../development/python-modules/memory-profiler { }; 7526 7526 7527 + memory-tempfile = callPackage ../development/python-modules/memory-tempfile { }; 7528 + 7527 7529 meraki = callPackage ../development/python-modules/meraki { }; 7528 7530 7529 7531 mercadopago = callPackage ../development/python-modules/mercadopago { }; ··· 9194 9196 py-eth-sig-utils = callPackage ../development/python-modules/py-eth-sig-utils { }; 9195 9197 9196 9198 py-expression-eval = callPackage ../development/python-modules/py-expression-eval { }; 9199 + 9200 + py-opensonic = callPackage ../development/python-modules/py-opensonic { }; 9197 9201 9198 9202 py-radix-sr = callPackage ../development/python-modules/py-radix-sr { }; 9199 9203