From c21b2e13582aba0e6b569ea1ba7189746200ad00 Mon Sep 17 00:00:00 2001 From: Jonas Rabenstein Date: Fri, 28 Nov 2025 14:43:57 +0100 Subject: [PATCH 1/3] dlna: version bump --- flake.lock | 6 +- flake.nix | 2 +- package/jellyfin/plugin.nix | 40 ++++++++--- plugin/dlna/default.nix | 4 ++ plugin/dlna/xml-sanitize.patch | 125 --------------------------------- 5 files changed, 38 insertions(+), 139 deletions(-) delete mode 100644 plugin/dlna/xml-sanitize.patch diff --git a/flake.lock b/flake.lock index e94c767..ee50fc6 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1758198701, - "narHash": "sha256-7To75JlpekfUmdkUZewnT6MoBANS0XVypW6kjUOXQwc=", + "lastModified": 1764242076, + "narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=", "owner": "nixos", "repo": "nixpkgs", - "rev": "0147c2f1d54b30b5dd6d4a8c8542e8d7edf93b5d", + "rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index d222f21..c9a5657 100644 --- a/flake.nix +++ b/flake.nix @@ -97,7 +97,7 @@ module = name: defs: { ... }: - builtins.trace "module: jellyfin.${name}" { + { imports = lib.toList defs; config.nixpkgs.overlays = [ overlay ]; }; diff --git a/package/jellyfin/plugin.nix b/package/jellyfin/plugin.nix index f3094f9..2bc5f0c 100644 --- a/package/jellyfin/plugin.nix +++ b/package/jellyfin/plugin.nix @@ -39,7 +39,9 @@ let mkPlugin = info: result: buildDotnetModule result; inherit jellyfin; ignore = builtins.attrNames helper ++ [ - "override" "overrideAttrs" "overrideDerivation" + "override" + "overrideAttrs" + "overrideDerivation" ]; }; @@ -57,8 +59,8 @@ let acc: name: value: acc ++ lib.optional (value == "directory") name ) [ ] (builtins.readDir ("${info.src}/src")); - dotnet-sdk = dotnetCorePackages.sdk_8_0; - dotnet-runtime = dotnetCorePackages.aspnetcore_8_0; + dotnet-sdk = dotnetCorePackages.sdk_9_0; + dotnet-runtime = jellyfin.dotnet-runtime; dontDotnetBuild = true; dontDotnetInstall = true; project = "Jellyfin.Plugin.${capitalize lib.strings.toUpper info.name}"; @@ -142,12 +144,30 @@ let meta = meta jellyfin.meta // meta info; }; info = helper // defaults // callPackage plugin ({ inherit info; } // args); - in info.mkPlugin info (lib.attrsets.removeAttrs info info.ignore); + in + info.mkPlugin info (lib.attrsets.removeAttrs info info.ignore); - functor = args: (drv args).overrideAttrs (final: prev: { - passthru = builtins.trace "override passthru" (prev.passthru or {}) // { - in-version = { version, rev ? null, hash ? null }@args': - callPackage functor (builtins.removeAttrs args [ "version" "rev" "hash" ] // args'); + functor = + args: + (drv args).overrideAttrs ( + final: prev: { + passthru = (prev.passthru or { }) // { + in-version = + { + version, + rev ? null, + hash ? null, + }@args': + callPackage functor ( + builtins.removeAttrs args [ + "version" + "rev" + "hash" + ] + // args' + ); }; - }); -in lib.trivial.mirrorFunctionArgs plugin functor + } + ); +in +lib.trivial.mirrorFunctionArgs plugin functor diff --git a/plugin/dlna/default.nix b/plugin/dlna/default.nix index 69dcd40..49e4842 100644 --- a/plugin/dlna/default.nix +++ b/plugin/dlna/default.nix @@ -3,6 +3,10 @@ let hash = "sha256-5YUX+w4n3nBhAkdgjF9D5yY/jzRKxpW+mTQCBluzsVI="; rev = "v8"; }; + v."10.0.0.0" = { + hash = "sha256-pPhMmH17RKktIX16ozSxsigxo6tU8tlST4IAm3vpjrw="; + rev = "v10"; + }; current = lib: lib.lists.fold (acc: v: if lib.strings.versionOlder acc v then v else acc) "0.0.0" ( diff --git a/plugin/dlna/xml-sanitize.patch b/plugin/dlna/xml-sanitize.patch deleted file mode 100644 index cc8db5f..0000000 --- a/plugin/dlna/xml-sanitize.patch +++ /dev/null @@ -1,125 +0,0 @@ -diff --git a/src/Jellyfin.Plugin.Dlna/Didl/DidlBuilder.cs b/src/Jellyfin.Plugin.Dlna/Didl/DidlBuilder.cs -index 150fb49..9160d8a 100644 ---- a/src/Jellyfin.Plugin.Dlna/Didl/DidlBuilder.cs -+++ b/src/Jellyfin.Plugin.Dlna/Didl/DidlBuilder.cs -@@ -239,6 +239,17 @@ public class DidlBuilder - writer.WriteFullEndElement(); - } - -+ private string sanitize(string input) -+ { -+ var valid = input.Where(ch => System.Xml.XmlConvert.IsXmlChar(ch)).ToArray(); -+ if (valid.Length == input.Length) -+ { -+ return input; -+ } -+ _logger.LogInformation("Sanitized: {0} (was: {1})", valid, input.Where(ch => true).ToArray()); -+ return new string(valid); -+ } -+ - private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo? streamInfo = null) - { - if (streamInfo is null) -@@ -1009,7 +1020,7 @@ public class DidlBuilder - writer.WriteStartElement("upnp", "artist", NsUpnp); - writer.WriteAttributeString("role", "AlbumArtist"); - -- writer.WriteString(name); -+ writer.WriteString(sanitize(name)); - - writer.WriteFullEndElement(); - } -@@ -1023,7 +1034,7 @@ public class DidlBuilder - { - try - { -- writer.WriteElementString(prefix, name, namespaceUri, value); -+ writer.WriteElementString(prefix, name, namespaceUri, sanitize(value)); - } - catch (XmlException ex) - { -diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToController.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToController.cs -index 4d822e2..9105d46 100644 ---- a/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToController.cs -+++ b/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToController.cs -@@ -386,34 +386,38 @@ public class PlayToController : ISessionController, IDisposable - ? null : - _userManager.GetUserById(command.ControllingUserId); - -- var items = new List(); -- foreach (var id in command.ItemIds) -- { -- AddItemFromId(id, items); -- } -- - var startIndex = command.StartIndex ?? 0; -- int len = items.Count - startIndex; -- if (startIndex > 0) -- { -- items = items.GetRange(startIndex, len); -- } -- -- var playlist = new PlaylistItem[len]; -- -- // Not nullable enabled - so this is required. -- playlist[0] = CreatePlaylistItem( -- items[0], -- user, -- command.StartPositionTicks ?? 0, -- command.MediaSourceId ?? string.Empty, -- command.AudioStreamIndex, -- command.SubtitleStreamIndex); -+ var playlist = new List(); -+ var first = true; -+ foreach (var id in command.ItemIds) { -+ var item = _libraryManager.GetItemById(id); -+ if (item?.MediaType != MediaType.Audio && item?.MediaType != MediaType.Video) -+ { -+ continue; -+ } - -- for (int i = 1; i < len; i++) -- { -- playlist[i] = CreatePlaylistItem(items[i], user, 0, string.Empty, null, null); -- } -+ if (startIndex > 0) { -+ startIndex -= 1; -+ continue; -+ } -+ -+ PlaylistItem? playlistItem = null; -+ try { -+ playlistItem = CreatePlaylistItem(item, user, -+ first ? (command.StartPositionTicks ?? 0) : 0, -+ first ? (command.MediaSourceId ?? string.Empty) : string.Empty, -+ first ? command.AudioStreamIndex : null, -+ first ? command.SubtitleStreamIndex : null); -+ } catch (System.NullReferenceException) { -+ _logger.LogError("{0}: could not create playlist item.", item.Path ?? ""); -+ } -+ first = false; -+ -+ if (playlistItem != null) -+ { -+ playlist.Add(playlistItem); -+ } -+ } - - _logger.LogDebug("{0} - Playlist created", _session.DeviceName); - -@@ -509,15 +513,6 @@ public class PlayToController : ISessionController, IDisposable - return info.IsDirectStream; - } - -- private void AddItemFromId(Guid id, List list) -- { -- var item = _libraryManager.GetItemById(id); -- if (item?.MediaType == MediaType.Audio || item?.MediaType == MediaType.Video) -- { -- list.Add(item); -- } -- } -- - private PlaylistItem CreatePlaylistItem( - BaseItem item, - User? user, From 4c2f85ad62e1e5fdec7d55f28f7b21e4840b8ffa Mon Sep 17 00:00:00 2001 From: Jonas Rabenstein Date: Fri, 28 Nov 2025 23:14:08 +0100 Subject: [PATCH 2/3] wip --- flake.nix | 4 +- package/jellyfin/build-plugin.nix | 110 ++++++++++++++++++++++++ package/jellyfin/default.nix | 31 +++++-- package/jellyfin/fetch-plugin.nix | 16 ++++ package/jellyfin/plugin.nix | 12 ++- package/jprm/default.nix | 2 +- plugin/dlna/default.nix | 39 +++++---- plugin/dlna/deps.json | 136 ++++++++++++++++++++++-------- 8 files changed, 285 insertions(+), 65 deletions(-) create mode 100644 package/jellyfin/build-plugin.nix create mode 100644 package/jellyfin/fetch-plugin.nix diff --git a/flake.nix b/flake.nix index c9a5657..1556e14 100644 --- a/flake.nix +++ b/flake.nix @@ -51,9 +51,7 @@ package = name: path: let - pkg = (pkgs' pkgs).callPackage path { - original = pkgs.${name} or null; - }; + pkg = pkgs.lib.callPackageWith ((pkgs' pkgs) // { original = pkgs.${name} or null; }) path {}; patches = scan (name: type: if lib.strings.removeSuffix ".patch" name != name then name else null) ( name: path: path ) path; diff --git a/package/jellyfin/build-plugin.nix b/package/jellyfin/build-plugin.nix new file mode 100644 index 0000000..6e76c88 --- /dev/null +++ b/package/jellyfin/build-plugin.nix @@ -0,0 +1,110 @@ +let + capitalize = + upper: str: + let + length = builtins.stringLength str; + head = builtins.substring 0 1 str; + tail = builtins.substring 1 length str; + in "${upper head}${tail}"; + + buildJellyfinPlugin = { + lib, + name, + version, + + dotnetCorePackages, + buildDotnetModule, + fetchJellyfinPlugin, + jellyfin, + jprm, + unzip, + + ... + }@params: let + project = params.project or "Jellyfin.Plugin.${capitalize lib.strings.toUpper name}"; + src = params.src or (fetchJellyfinPlugin { name = name; tag = "v${builtins.head (builtins.splitVersion version)}"; hash = params.hash or ""; }); + + extraArgs = lib.attrsets.removeAttrs params (builtins.attrNames (lib.functionArgs buildJellyfinPlugin)); + + pluginLibraries = extraArgs.pluginLibraries or lib.attrsets.foldlAttrs ( + acc: name: value: + acc ++ lib.optional (value == "directory") name + ) [ ] (builtins.readDir "${src}/src"); + + args = { + pname = "jellyfin-plugin-${name}"; + version = version; + description = "${name} plugin for jellyfin"; + projectFile = "src/${project}/${project}.csproj"; + dotnet-sdk = dotnetCorePackages.sdk_9_0; + dotnet-runtime = jellyfin.dotnet-runtime; + dontDotnetBuild = true; + dontDontnetInstall = true; + } // extraArgs // { + inherit src; + meta = { inherit (jellyfin.meta) license homepage; + maintainer = [ "nonapode@fbs42.ddnss.de" ]; + } // (extraArgs.meta or {}); + + nativeBuildInputs = (extraArgs.nativeBuildInputs or []) ++ [ + jprm + unzip + ]; + outputs = (extraArgs.outputs or []) ++ [ "out" "zip" ]; + prePatch = '' + sed --sandbox --separate \ + -e 's:\(PackageReference Include="Jellyfin\..*" Version="\)[^"]\+":\1${jellyfin.version}":' \ + -e 's:<\(enerateDocumentationFile\|TreatWarningsAsErrors\)>true:<\1>false:' \ + -i ${ + lib.strings.escapeShellArgs (builtins.map (lib: "src/${lib}/${lib}.csproj") pluginLibraries) + } + + success=true + for x in ${ + lib.strings.escapeShellArgs (builtins.map (lib: "src/${lib}/${lib}.csproj") pluginLibraries) + } + do + diff -q $src/$x $x 2>/dev/null || continue + printf >&2 'no change: %s\n' $x + success=false + done + $success || exit 1 + '' + (extraArgs.prePatch or ""); + + postInstall = + let + dlls = builtins.map (name: "${name}.dll") pluginLibraries; + in + '' + tmp_output_dir="$(mktemp -d)" + jprm plugin build . --output="''${tmp_output_dir}" --version="${version}" --dotnet-configuration="''${dotnetBuildType-Release}" + mv "''${tmp_output_dir}/${name}_${version}.zip" $zip + mkdir -p $out + unzip $zip -d $out + + success=true + for file in $out/*; + do + case "''${file##*/}" in + meta.json) + ;; + ${builtins.concatStringsSep "|" (builtins.map lib.strings.escapeShellArg dlls)}) ;; + *) + printf 'unknown file: %s\n' ''${file@Q} + success=false + ;; + esac + done + + for file in meta.json ${lib.strings.escapeShellArgs dlls} + do + [[ -f "$out/$file" ]] && continue + printf 'missing file: %s\n' ''${file@Q} + success=false + done + + $success || exit 42 + ''; + }; + in buildDotnetModule args; +in buildJellyfinPlugin diff --git a/package/jellyfin/default.nix b/package/jellyfin/default.nix index 29532a8..af74e5c 100644 --- a/package/jellyfin/default.nix +++ b/package/jellyfin/default.nix @@ -1,18 +1,33 @@ { + pkgs, + lib, original, - callPackage, + fetchFromGitHub, + + gnused, + jprm, + unzip, + dotnetCorePackages, + buildDotnetModule, + ... }: let + context = pkgs // { + inherit callPackage fetchJellyfinPlugin buildJellyfinPlugin jellyfin; + }; + callPackage = lib.callPackageWith context; + + fetchJellyfinPlugin = callPackage ./fetch-plugin.nix; + buildJellyfinPlugin = callPackage ./build-plugin.nix; + jellyfin = original.overrideAttrs ( - final: prev: + final: { passthru ? {}, ... }@prev: assert !prev ? "plugin"; { - passthru.plugin = - base: - callPackage ./plugin.nix { - plugin = base; - jellyfin = jellyfin; - }; + passthru = passthru // { + plugin = + base: callPackage base; + }; } ); in diff --git a/package/jellyfin/fetch-plugin.nix b/package/jellyfin/fetch-plugin.nix new file mode 100644 index 0000000..23e87d3 --- /dev/null +++ b/package/jellyfin/fetch-plugin.nix @@ -0,0 +1,16 @@ +let + fetchJellyfinPlugin = { + lib, + fetchFromGitHub, + runCommandLocal, + ... + }@args: let + owner = args.owner or "jellyfin"; + repo = args.repo or "jellyfin-plugin-${args.name}"; + + extraArgs = lib.attrsets.removeAttrs args (builtins.attrNames (lib.functionArgs fetchJellyfinPlugin)); + git = fetchFromGitHub ({ + inherit owner repo; + } // extraArgs); + in git; +in fetchJellyfinPlugin diff --git a/package/jellyfin/plugin.nix b/package/jellyfin/plugin.nix index 2bc5f0c..4abf045 100644 --- a/package/jellyfin/plugin.nix +++ b/package/jellyfin/plugin.nix @@ -143,15 +143,18 @@ let ''; meta = meta jellyfin.meta // meta info; }; - info = helper // defaults // callPackage plugin ({ inherit info; } // args); + info = helper // defaults // { + inherit (callPackage plugin ({ inherit info; } // args)) + version hash rev; + }; in info.mkPlugin info (lib.attrsets.removeAttrs info info.ignore); functor = args: (drv args).overrideAttrs ( - final: prev: { - passthru = (prev.passthru or { }) // { + final: { passthru ? {}, ... }@prev: { + passthru = passthru // { in-version = { version, @@ -170,4 +173,5 @@ let } ); in -lib.trivial.mirrorFunctionArgs plugin functor +#lib.trivial.mirrorFunctionArgs plugin functor + drv diff --git a/package/jprm/default.nix b/package/jprm/default.nix index 49970c9..765a28c 100644 --- a/package/jprm/default.nix +++ b/package/jprm/default.nix @@ -2,7 +2,7 @@ let v."1.1.1" = "sha256-PWgZ9K81RX+AboU8/6IGEQ8Fv/e8d2I1KH3+jIQOyj4="; current = lib: - lib.lists.fold (acc: v: if lib.strings.versionOlder acc v then v else acc) "0.0.0" ( + lib.lists.foldl (acc: v: if lib.strings.versionOlder acc v then v else acc) "0.0.0" ( builtins.attrNames v ); in diff --git a/plugin/dlna/default.nix b/plugin/dlna/default.nix index 49e4842..afd626a 100644 --- a/plugin/dlna/default.nix +++ b/plugin/dlna/default.nix @@ -7,19 +7,26 @@ let hash = "sha256-pPhMmH17RKktIX16ozSxsigxo6tU8tlST4IAm3vpjrw="; rev = "v10"; }; - current = - lib: - lib.lists.fold (acc: v: if lib.strings.versionOlder acc v then v else acc) "0.0.0" ( - builtins.attrNames v - ); -in -{ - lib, - version ? current lib, - hash ? v.${version}.hash, - rev ? v.${version}.rev or "v${version}", - ... -}: -{ - inherit version hash rev; -} + + dlna = { + lib, + buildJellyfinPlugin, + fetchJellyfinPlugin, + ... + }@args: let + version = args.version or "10.0.0.0"; + hash = args.hash or v.${version}.hash or ""; + rev = args.rev or v.${version}.rev or "v${lib.versions.major version}"; + extra = lib.attrsets.removeAttrs args (builtins.attrNames (lib.functionArgs dlna)); + + plain = buildJellyfinPlugin { + name = "dlna"; + inherit version; + src = fetchJellyfinPlugin { + name = "dlna"; + inherit hash rev; + }; + nugetDeps = ./deps.json; + }; + in plain.override extra; +in dlna diff --git a/plugin/dlna/deps.json b/plugin/dlna/deps.json index e6de4d7..bd23349 100644 --- a/plugin/dlna/deps.json +++ b/plugin/dlna/deps.json @@ -1,8 +1,13 @@ [ + { + "pname": "BitFaster.Caching", + "version": "2.5.4", + "hash": "sha256-PWuVT1kKjL8ulMtv9hWmg0nMChFh8skr34xUl3mQ0Y8=" + }, { "pname": "Diacritics", - "version": "3.3.29", - "hash": "sha256-sIbdJ3yMthnmJHly3WheUdYjtwPakcczTJx9ycxtgrY=" + "version": "4.0.17", + "hash": "sha256-O1pOeOV7c+dfD/EjwiOmqYhP5RDZyosVOk0OjVuK5Eg=" }, { "pname": "ICU4N", @@ -21,33 +26,43 @@ }, { "pname": "Jellyfin.Common", - "version": "10.10.7", - "hash": "sha256-9EIigrDheob4vRP+UBAoIPHH4fyz6Cl27GUpelEGpBg=" + "version": "10.11.3", + "hash": "sha256-d0rIKccKSGqKUoXGT5N+/Wfq5M6eM06WQEzL6CA5eao=" }, { "pname": "Jellyfin.Controller", - "version": "10.10.7", - "hash": "sha256-/obWAuxWpSn+NlMES+fjyrf1g+qbxmSYQUBvgXQYltQ=" + "version": "10.11.3", + "hash": "sha256-Rudd5SwnQSwTkFATD7FCXIf3cdXmiHtQUXl8G+NW1k4=" }, { "pname": "Jellyfin.Data", - "version": "10.10.7", - "hash": "sha256-lRQjg/HkFAtCN0woL1gX6j5dMfVLP/fzQTKjFTcRth4=" + "version": "10.11.3", + "hash": "sha256-e0eWPDTh1zgitbeOmg/M1c/fYL5/OLKzAtQZg48IUYg=" + }, + { + "pname": "Jellyfin.Database.Implementations", + "version": "10.11.3", + "hash": "sha256-IP1jfyE/3Orzs1ZzIvv8V1qxfkfW9z7eRIZn5CE6WZQ=" }, { "pname": "Jellyfin.Extensions", - "version": "10.10.7", - "hash": "sha256-AOGJ2IoT2v+LnlEqnb2O4FnXapcQiH0V9ny8GUysNHg=" + "version": "10.11.3", + "hash": "sha256-dipmxz4qL4qo7i+ifrLBVMroUjzW5BkkGNrNwpDCVQw=" + }, + { + "pname": "Jellyfin.MediaEncoding.Keyframes", + "version": "10.11.3", + "hash": "sha256-w9BtlQMOeNKj7B+qFtn5oVzaGfRqsGNvISjF4cE4m8k=" }, { "pname": "Jellyfin.Model", - "version": "10.10.7", - "hash": "sha256-ubsClGTLq/aFnMiBCaHVUX2b88beuL07Yn5VXNbTCjQ=" + "version": "10.11.3", + "hash": "sha256-3+ScXU/pWj+Ru1b74v5aWlqg/+XSuKAmqeFbrVYrqRg=" }, { "pname": "Jellyfin.Naming", - "version": "10.10.7", - "hash": "sha256-/jlIS0X4FuBo6Ac1cjLGzgdJ3vR6VotBspxpNrqfMzo=" + "version": "10.11.3", + "hash": "sha256-oZ0qcARGOqAyvhdWMjdvuDVSuFYjyVd5TUpDdIMYRJs=" }, { "pname": "Microsoft.AspNetCore.Authorization", @@ -59,30 +74,60 @@ "version": "8.0.10", "hash": "sha256-SxnMOWJGgUUQyKaRezJQwMUt4eMfWjnhmfk8pldYGNA=" }, + { + "pname": "Microsoft.EntityFrameworkCore", + "version": "9.0.10", + "hash": "sha256-Zm4oMVeloK2WmPskzg4l3SXjJuC+sRg3O5aiTK5rHvw=" + }, + { + "pname": "Microsoft.EntityFrameworkCore.Abstractions", + "version": "9.0.10", + "hash": "sha256-FB+8WtFYKn1PH9R3pgKw7dNJiJDCcS78UkeRkxdOuCk=" + }, + { + "pname": "Microsoft.EntityFrameworkCore.Analyzers", + "version": "9.0.10", + "hash": "sha256-q6w0uQ4qMAe2EuA65a3rk18rhGXuGVYMrdrIzD5Z+tw=" + }, + { + "pname": "Microsoft.EntityFrameworkCore.Relational", + "version": "9.0.10", + "hash": "sha256-2XHQOKvs4mAXwl8vEZpdi6ZtDFhK2hPusRMFemu3Shw=" + }, { "pname": "Microsoft.Extensions.Caching.Abstractions", "version": "2.0.0", "hash": "sha256-Eg1MES40kzkGW9tZmjaKtbWI00Kbv7fLJQmjrigjxqk=" }, + { + "pname": "Microsoft.Extensions.Caching.Abstractions", + "version": "9.0.10", + "hash": "sha256-W/9WhAG5t/hWPZxIL5+ILMsPKO/DjprHRymZUmU5YOA=" + }, { "pname": "Microsoft.Extensions.Caching.Memory", "version": "2.0.0", "hash": "sha256-1fnNvp62KrviVwYlqVl1CbdaZVpCDah9eCZeNDGDbWM=" }, + { + "pname": "Microsoft.Extensions.Caching.Memory", + "version": "9.0.10", + "hash": "sha256-HIXNiUnBJaYN+QGzpTlHzkvkBwYmcU0QUlIgQDhVG5g=" + }, { "pname": "Microsoft.Extensions.Configuration.Abstractions", - "version": "8.0.0", - "hash": "sha256-4eBpDkf7MJozTZnOwQvwcfgRKQGcNXe0K/kF+h5Rl8o=" + "version": "9.0.10", + "hash": "sha256-sRv0yS2sbyli7eejtnpmd7UIAz4PwSt5/Po5Irc1j98=" }, { "pname": "Microsoft.Extensions.Configuration.Binder", - "version": "8.0.2", - "hash": "sha256-aGB0VuoC34YadAEqrwoaXLc5qla55pswDV2xLSmR7SE=" + "version": "9.0.10", + "hash": "sha256-4NEBx28byvjjIzo0wQPIUUymk9AzSgPS4fu5IRxkIt4=" }, { "pname": "Microsoft.Extensions.DependencyInjection", - "version": "8.0.1", - "hash": "sha256-O9g0jWS+jfGoT3yqKwZYJGL+jGSIeSbwmvomKDC3hTU=" + "version": "9.0.10", + "hash": "sha256-f3r2msA/oV9gGdFn9OEr5bPAfINR17P+sS6/2/NnCuk=" }, { "pname": "Microsoft.Extensions.DependencyInjection.Abstractions", @@ -96,19 +141,24 @@ }, { "pname": "Microsoft.Extensions.DependencyInjection.Abstractions", - "version": "8.0.2", - "hash": "sha256-UfLfEQAkXxDaVPC7foE/J3FVEXd31Pu6uQIhTic3JgY=" + "version": "9.0.10", + "hash": "sha256-5rwFXG+Wjbf+TkXeWrkGVKV4wfvOryTPadEkEyPyKj4=" }, { "pname": "Microsoft.Extensions.Logging", - "version": "8.0.1", - "hash": "sha256-vkfVw4tQEg86Xg18v6QO0Qb4Ysz0Njx57d1XcNuj6IU=" + "version": "9.0.10", + "hash": "sha256-/Et36NBhpMoxQzI+p/moW7knwYDfjI7Ma7DF7KIYn+Q=" }, { "pname": "Microsoft.Extensions.Logging.Abstractions", "version": "8.0.2", "hash": "sha256-cHpe8X2BgYa5DzulZfq24rg8O2K5Lmq2OiLhoyAVgJc=" }, + { + "pname": "Microsoft.Extensions.Logging.Abstractions", + "version": "9.0.10", + "hash": "sha256-PtYXXHi+mbdQMh2QtA57NbWlt+JEpXiey36zLzbKTmo=" + }, { "pname": "Microsoft.Extensions.Options", "version": "2.0.0", @@ -119,6 +169,11 @@ "version": "8.0.2", "hash": "sha256-AjcldddddtN/9aH9pg7ClEZycWtFHLi9IPe1GGhNQys=" }, + { + "pname": "Microsoft.Extensions.Options", + "version": "9.0.10", + "hash": "sha256-QTNhi83xhjJuIQ/3QffzQs/KY7avNyBMvnkuuSr3pBo=" + }, { "pname": "Microsoft.Extensions.Primitives", "version": "2.0.0", @@ -129,6 +184,11 @@ "version": "8.0.0", "hash": "sha256-FU8qj3DR8bDdc1c+WeGZx/PCZeqqndweZM9epcpXjSo=" }, + { + "pname": "Microsoft.Extensions.Primitives", + "version": "9.0.10", + "hash": "sha256-It7NQ+Ap/hrqFX3LXDVJqVz1Xl3j8QIapYDcG2MQ/7w=" + }, { "pname": "Microsoft.NETCore.Platforms", "version": "1.1.0", @@ -139,6 +199,21 @@ "version": "1.1.0", "hash": "sha256-0AqQ2gMS8iNlYkrD+BxtIg7cXMnr9xZHtKAuN4bjfaQ=" }, + { + "pname": "NEbml", + "version": "1.1.0.5", + "hash": "sha256-MrQLekP6z5y6rfqnCbLefkYv4Fm8di4HqZ/AiYTzBQ4=" + }, + { + "pname": "Polly", + "version": "8.6.4", + "hash": "sha256-Z+ZbhnHWMu55qgQkxvw3yMiMd+zIMzzQiFhvn/PeQ3I=" + }, + { + "pname": "Polly.Core", + "version": "8.6.4", + "hash": "sha256-4Xrg/H481Y/WOHk1sGvFNEOfgaGrdKi+4U54PTXhh9I=" + }, { "pname": "runtime.any.System.Globalization", "version": "4.3.0", @@ -174,19 +249,14 @@ "version": "4.3.0", "hash": "sha256-51813WXpBIsuA6fUtE5XaRQjcWdQ2/lmEokJt97u0Rg=" }, - { - "pname": "System.Runtime.CompilerServices.Unsafe", - "version": "4.4.0", - "hash": "sha256-SeTI4+yVRO2SmAKgOrMni4070OD+Oo8L1YiEVeKDyig=" - }, { "pname": "System.Text.Json", - "version": "8.0.5", - "hash": "sha256-yKxo54w5odWT6nPruUVsaX53oPRe+gKzGvLnnxtwP68=" + "version": "9.0.10", + "hash": "sha256-wqeobpRw3PqOw21q8oGvauj5BkX1pS02Cm78E6c742w=" }, { "pname": "System.Threading.Tasks.Dataflow", - "version": "8.0.1", - "hash": "sha256-hgCfF91BDd/eOtLEd5jhjzgJdvwmVv4/b42fXRr3nvo=" + "version": "9.0.10", + "hash": "sha256-V3UjIEGn9Yrl/DQoKeEVg9pDpp4iNz8r9+WmQ09R1bg=" } ] From 794925acb2176f6257d3bc832b7d8023dd546019 Mon Sep 17 00:00:00 2001 From: Jonas Rabenstein Date: Sat, 29 Nov 2025 18:14:10 +0100 Subject: [PATCH 3/3] wip --- flake.nix | 90 ++++------- module/{plugins.nix => default.nix} | 17 ++- package/buildJellyfinPlugin/package.nix | 146 ++++++++++++++++++ package/fetchJellyfinPlugin/package.nix | 35 +++++ package/jellyfin/build-plugin.nix | 110 -------------- package/jellyfin/default.nix | 34 ----- package/jellyfin/fetch-plugin.nix | 16 -- package/jellyfin/package.nix | 23 +++ package/jellyfin/plugin.nix | 177 ---------------------- package/jprm/{default.nix => package.nix} | 2 +- plugin/dlna/default.nix | 32 ---- plugin/dlna/package.nix | 56 +++++++ plugin/dlna/xml-sanitize.patch | 125 +++++++++++++++ 13 files changed, 425 insertions(+), 438 deletions(-) rename module/{plugins.nix => default.nix} (75%) create mode 100644 package/buildJellyfinPlugin/package.nix create mode 100644 package/fetchJellyfinPlugin/package.nix delete mode 100644 package/jellyfin/build-plugin.nix delete mode 100644 package/jellyfin/default.nix delete mode 100644 package/jellyfin/fetch-plugin.nix create mode 100644 package/jellyfin/package.nix delete mode 100644 package/jellyfin/plugin.nix rename package/jprm/{default.nix => package.nix} (96%) delete mode 100644 plugin/dlna/default.nix create mode 100644 plugin/dlna/package.nix create mode 100644 plugin/dlna/xml-sanitize.patch diff --git a/flake.nix b/flake.nix index 1556e14..aecd201 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "A very basic flake"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; }; outputs = @@ -10,16 +10,6 @@ let inherit (nixpkgs) lib; - overlay = - final: pkgs: - (packages pkgs) - // lib.attrsets.mapAttrs' (name: value: { - name = "jellyfin-plugin-${name}"; - inherit value; - }) (plugins final); - - pkgs' = pkgs: pkgs.extend overlay; - scan'directory = scan (name: type: if type == "directory" then name else null); scan'regular = scan (name: type: if type == "regular" then name else null); scan'nix = @@ -45,62 +35,42 @@ in lib.attrsets.foldlAttrs fold empty files; - packages = - pkgs: - let - package = - name: path: - let - pkg = pkgs.lib.callPackageWith ((pkgs' pkgs) // { original = pkgs.${name} or null; }) path {}; - patches = scan (name: type: if lib.strings.removeSuffix ".patch" name != name then name else null) ( - name: path: path - ) path; - in - pkg.overrideAttrs ( - final: prev: { - patches = (prev.patches or [ ]) ++ builtins.attrValues patches; + scope = pkgs: lib.makeScope pkgs.newScope ( + self: + lib.packagesFromDirectoryRecursive { + callPackage = self.callPackage; + directory = ./package; + } + // { + jellyfin = ( + self.callPackage ./package/jellyfin/package.nix { + jellyfin = pkgs.jellyfin; } ); - - in - scan'directory package ./package; - - plugins = - pkgs: - let - plugin = name: path: (pkgs' pkgs).jellyfin.plugin path { }; - in - scan'directory plugin ./plugin; + } + // + lib.attrsets.mapAttrs' + (name: value: { + name = "jellyfin-plugin-${name}"; + value = value; + }) + ( + lib.packagesFromDirectoryRecursive { + callPackage = self.callPackage; + directory = ./plugin; + } + ) + ); in { - overlays.default = overlay; + overlay = final: scope; - packages = per-pkgs ( - pkgs: - (packages pkgs) - // lib.attrsets.mapAttrs' (name: value: { - name = "plugin-${name}"; - inherit value; - }) (plugins pkgs) - ); + legacyPackages = per-pkgs scope; - nixosModules = - let - directories = scan'directory (_: path: path) ./module; - files = scan'nix (_: path: path) ./module; - modules = directories // { - default.imports = builtins.attrValues files ++ (directories.default or [ ]); - }; - module = - name: defs: - { ... }: - { - imports = lib.toList defs; - config.nixpkgs.overlays = [ overlay ]; - }; - in - builtins.mapAttrs module modules; + packages = per-pkgs (pkgs: lib.filterAttrs (_: lib.isDerivation) (scope pkgs)); + + nixosModules.default = import ./module/default.nix scope; formatter = per-pkgs ({ nixfmt-tree, ... }: nixfmt-tree); }; diff --git a/module/plugins.nix b/module/default.nix similarity index 75% rename from module/plugins.nix rename to module/default.nix index 1d9f0c9..30332ab 100644 --- a/module/plugins.nix +++ b/module/default.nix @@ -1,15 +1,18 @@ +scope: { + lib, pkgs, config, - lib, ... }: let cfg = config.services.jellyfin; - plugin = name: pkgs."jellyfin-plugin-${name}"; + plugin = name: (scope pkgs)."jellyfin-plugin-${name}".override { + jellyfin = cfg.package; + }; - per-file = pkg: fn: lib.lists.foldl (acc: dll: acc ++ lib.toList (fn dll)) [ ] pkg.pluginLibraries; + libs = pkg: fn: lib.lists.foldl (acc: lib: acc ++ lib.toList (fn lib)) [ ] pkg.pluginLibraries; serviceConfig = pkg: @@ -22,15 +25,13 @@ let ''; in { - BindReadOnlyPaths = per-file pkg ( - name: "${pkg}/${name}.dll:${cfg.dataDir}/plugins/${pkg.name}/${name}.dll" - ); + BindReadOnlyPaths = map (name: "${pkg}/${name}:${cfg.dataDir}/plugins/${pkg.name}/${name}") pkg.jellyfinPluginFiles; BindPaths = [ "${cfg.dataDir}/plugins/${pkg.name}/meta.json" ]; }; - type.plugin = lib.types.addCheck lib.types.package (builtins.hasAttr "pluginLibraries"); + type.plugin = lib.types.addCheck lib.types.package (builtins.hasAttr "jellyfinPluginFiles"); in { options.services.jellyfin.plugins = lib.mkOption { @@ -41,7 +42,7 @@ in config.systemd = lib.mkIf (cfg.plugins != { }) { services.jellyfin.serviceConfig = lib.mkMerge ( [ - { TemporaryFileSystem = [ "${cfg.dataDir}/plugins:ro" ]; } + { BindPaths = [ "${cfg.dataDir}/plugins" ]; } { BindPaths = [ "${cfg.dataDir}/plugins/configurations/" ]; } ] ++ builtins.map serviceConfig cfg.plugins diff --git a/package/buildJellyfinPlugin/package.nix b/package/buildJellyfinPlugin/package.nix new file mode 100644 index 0000000..e2e6563 --- /dev/null +++ b/package/buildJellyfinPlugin/package.nix @@ -0,0 +1,146 @@ +{ + lib, + newScope, + + dotnetCorePackages, + buildDotnetModule, + fetchJellyfinPlugin, + jellyfin, + jprm, + unzip, +}@args: +let + buildJellyfinPlugin = (lib.makeScope newScope (_: args)).callPackage package; + + capitalize = + str: + let + length = builtins.stringLength str; + head = builtins.substring 0 1 str; + tail = builtins.substring 1 length str; + in + "${lib.strings.toUpper head}${tail}"; + + package = + { + lib, + name, + version, + + dotnetCorePackages, + buildDotnetModule, + fetchJellyfinPlugin, + jellyfin, + jprm, + unzip, + + ... + }@params: + let + self = buildJellyfinPlugin params; + + project = params.project or "Jellyfin.Plugin.${capitalize name}"; + + pluginLibraries = params.pluginLibraries or lib.attrsets.foldlAttrs ( + acc: name: value: + acc ++ lib.optional (value == "directory") name + ) [ ] (builtins.readDir "${self.src}/src"); + dlls = builtins.map (name: "${name}.dll") pluginLibraries; + + defaults = { + pname = "jellyfin-plugin-${name}"; + version = version; + description = "${name} plugin for ${jellyfin.name}"; + projectFile = "src/${project}/${project}.csproj"; + dotnet-sdk = dotnetCorePackages.sdk_9_0; + dotnet-runtime = jellyfin.dotnet-runtime; + dontDotnetBuild = true; + dontDotnetInstall = true; + src = fetchJellyfinPlugin { + inherit name version; + tag = "v${builtins.head (builtins.splitVersion version)}"; + hash = params.hash or ""; + }; + }; + + override = + (lib.attrsets.removeAttrs params ( + builtins.attrNames (lib.functionArgs package) + ++ [ + "project" + "pluginLibraries" + ] + )) + // { + inherit jellyfin; + jellyfinPluginFiles = (params.jellyfinPluginFiles or []) ++ dlls; + meta = { + inherit (jellyfin.meta) license homepage; + maintaner = [ "nonapode@fbs42.ddnss.de" ]; + } + // (params.meta or { }); + + nativeBuildInputs = (params.nativeBuildInputs or [ ]) ++ [ + jprm + unzip + ]; + outputs = (params.outputs or [ ]) ++ [ + "out" + "zip" + ]; + prePatch = '' + sed --sandbox --separate \ + -e 's:\(PackageReference Include="Jellyfin\..*" Version="\)[^"]\+":\1${jellyfin.version}":' \ + -e 's:<\(enerateDocumentationFile\|TreatWarningsAsErrors\)>true:<\1>false:' \ + -i ${lib.strings.escapeShellArgs (builtins.map (lib: "src/${lib}/${lib}.csproj") pluginLibraries)} + + success=true + for x in ${ + lib.strings.escapeShellArgs (builtins.map (lib: "src/${lib}/${lib}.csproj") pluginLibraries) + } + do + diff -q $src/$x $x 2>/dev/null || continue + printf >&2 'no change: %s\n' $x + success=false + done + $success || exit 1 + '' + + (params.prePatch or ""); + + postInstall = (params.postInstall or "") + '' + tmp_output_dir="$(mktemp -d)" + jprm plugin build . --output="''${tmp_output_dir}" --version="${version}" --dotnet-configuration="''${dotnetBuildType-Release}" + env + mv "''${tmp_output_dir}/${name}_${version}.zip" $zip + mkdir -p $out + unzip $zip -d $out + + success=true + for file in $out/*; + do + case "''${file##*/}" in + meta.json) + ;; + ${builtins.concatStringsSep "|" (builtins.map lib.strings.escapeShellArg dlls)}) ;; + *) + printf 'unknown file: %s\n' ''${file@Q} + success=false + ;; + esac + done + + for file in meta.json ${lib.strings.escapeShellArgs dlls} + do + [[ -f "$out/$file" ]] && continue + printf 'missing file: %s\n' ''${file@Q} + success=false + done + + $success || exit 42 + ''; + }; + + in + buildDotnetModule (defaults // override); +in +buildJellyfinPlugin diff --git a/package/fetchJellyfinPlugin/package.nix b/package/fetchJellyfinPlugin/package.nix new file mode 100644 index 0000000..aa1d68f --- /dev/null +++ b/package/fetchJellyfinPlugin/package.nix @@ -0,0 +1,35 @@ +{ + lib, + fetchFromGitHub, + newScope, +}@args: +let + fetchJellyfinPlugin = (lib.makeScope newScope (_: args)).callPackage package; + + package = + { + name, + fetchFromGitHub, + ... + }@args: + let + self = fetchJellyfinPlugin args; + + extraArgs = lib.attrsets.removeAttrs args (builtins.attrNames (lib.functionArgs package)); + owner = args.owner or "jellyfin"; + repo = args.repo or "jellyfin-plugin-${args.name}"; + name = builtins.concatStringsSep "-" ( + [ repo ] ++ lib.optional (args ? version) args.version ++ [ "source" ] + ); + + in + fetchFromGitHub ( + { + inherit owner repo name; + passthru.override = self.override; + } + // extraArgs + ); + +in +fetchJellyfinPlugin diff --git a/package/jellyfin/build-plugin.nix b/package/jellyfin/build-plugin.nix deleted file mode 100644 index 6e76c88..0000000 --- a/package/jellyfin/build-plugin.nix +++ /dev/null @@ -1,110 +0,0 @@ -let - capitalize = - upper: str: - let - length = builtins.stringLength str; - head = builtins.substring 0 1 str; - tail = builtins.substring 1 length str; - in "${upper head}${tail}"; - - buildJellyfinPlugin = { - lib, - name, - version, - - dotnetCorePackages, - buildDotnetModule, - fetchJellyfinPlugin, - jellyfin, - jprm, - unzip, - - ... - }@params: let - project = params.project or "Jellyfin.Plugin.${capitalize lib.strings.toUpper name}"; - src = params.src or (fetchJellyfinPlugin { name = name; tag = "v${builtins.head (builtins.splitVersion version)}"; hash = params.hash or ""; }); - - extraArgs = lib.attrsets.removeAttrs params (builtins.attrNames (lib.functionArgs buildJellyfinPlugin)); - - pluginLibraries = extraArgs.pluginLibraries or lib.attrsets.foldlAttrs ( - acc: name: value: - acc ++ lib.optional (value == "directory") name - ) [ ] (builtins.readDir "${src}/src"); - - args = { - pname = "jellyfin-plugin-${name}"; - version = version; - description = "${name} plugin for jellyfin"; - projectFile = "src/${project}/${project}.csproj"; - dotnet-sdk = dotnetCorePackages.sdk_9_0; - dotnet-runtime = jellyfin.dotnet-runtime; - dontDotnetBuild = true; - dontDontnetInstall = true; - } // extraArgs // { - inherit src; - meta = { inherit (jellyfin.meta) license homepage; - maintainer = [ "nonapode@fbs42.ddnss.de" ]; - } // (extraArgs.meta or {}); - - nativeBuildInputs = (extraArgs.nativeBuildInputs or []) ++ [ - jprm - unzip - ]; - outputs = (extraArgs.outputs or []) ++ [ "out" "zip" ]; - prePatch = '' - sed --sandbox --separate \ - -e 's:\(PackageReference Include="Jellyfin\..*" Version="\)[^"]\+":\1${jellyfin.version}":' \ - -e 's:<\(enerateDocumentationFile\|TreatWarningsAsErrors\)>true:<\1>false:' \ - -i ${ - lib.strings.escapeShellArgs (builtins.map (lib: "src/${lib}/${lib}.csproj") pluginLibraries) - } - - success=true - for x in ${ - lib.strings.escapeShellArgs (builtins.map (lib: "src/${lib}/${lib}.csproj") pluginLibraries) - } - do - diff -q $src/$x $x 2>/dev/null || continue - printf >&2 'no change: %s\n' $x - success=false - done - $success || exit 1 - '' + (extraArgs.prePatch or ""); - - postInstall = - let - dlls = builtins.map (name: "${name}.dll") pluginLibraries; - in - '' - tmp_output_dir="$(mktemp -d)" - jprm plugin build . --output="''${tmp_output_dir}" --version="${version}" --dotnet-configuration="''${dotnetBuildType-Release}" - mv "''${tmp_output_dir}/${name}_${version}.zip" $zip - mkdir -p $out - unzip $zip -d $out - - success=true - for file in $out/*; - do - case "''${file##*/}" in - meta.json) - ;; - ${builtins.concatStringsSep "|" (builtins.map lib.strings.escapeShellArg dlls)}) ;; - *) - printf 'unknown file: %s\n' ''${file@Q} - success=false - ;; - esac - done - - for file in meta.json ${lib.strings.escapeShellArgs dlls} - do - [[ -f "$out/$file" ]] && continue - printf 'missing file: %s\n' ''${file@Q} - success=false - done - - $success || exit 42 - ''; - }; - in buildDotnetModule args; -in buildJellyfinPlugin diff --git a/package/jellyfin/default.nix b/package/jellyfin/default.nix deleted file mode 100644 index af74e5c..0000000 --- a/package/jellyfin/default.nix +++ /dev/null @@ -1,34 +0,0 @@ -{ - pkgs, - lib, - original, - fetchFromGitHub, - - gnused, - jprm, - unzip, - dotnetCorePackages, - buildDotnetModule, - ... -}: -let - context = pkgs // { - inherit callPackage fetchJellyfinPlugin buildJellyfinPlugin jellyfin; - }; - callPackage = lib.callPackageWith context; - - fetchJellyfinPlugin = callPackage ./fetch-plugin.nix; - buildJellyfinPlugin = callPackage ./build-plugin.nix; - - jellyfin = original.overrideAttrs ( - final: { passthru ? {}, ... }@prev: - assert !prev ? "plugin"; - { - passthru = passthru // { - plugin = - base: callPackage base; - }; - } - ); -in -jellyfin diff --git a/package/jellyfin/fetch-plugin.nix b/package/jellyfin/fetch-plugin.nix deleted file mode 100644 index 23e87d3..0000000 --- a/package/jellyfin/fetch-plugin.nix +++ /dev/null @@ -1,16 +0,0 @@ -let - fetchJellyfinPlugin = { - lib, - fetchFromGitHub, - runCommandLocal, - ... - }@args: let - owner = args.owner or "jellyfin"; - repo = args.repo or "jellyfin-plugin-${args.name}"; - - extraArgs = lib.attrsets.removeAttrs args (builtins.attrNames (lib.functionArgs fetchJellyfinPlugin)); - git = fetchFromGitHub ({ - inherit owner repo; - } // extraArgs); - in git; -in fetchJellyfinPlugin diff --git a/package/jellyfin/package.nix b/package/jellyfin/package.nix new file mode 100644 index 0000000..68baa73 --- /dev/null +++ b/package/jellyfin/package.nix @@ -0,0 +1,23 @@ +{ + pkgs, + lib, + jellyfin, + fetchFromGitHub, + + gnused, + jprm, + unzip, + dotnetCorePackages, + buildDotnetModule, + ... +}: jellyfin.overrideAttrs ( + final: { passthru ? {}, ...}@prev: { + passthru = passthru // { + plugins = lib.attrsets.foldlAttrs + (acc: name: value: assert (builtins.trace name true); if lib.strings.hasPrefix "jellyfin-plugin-" name + then acc // { + ${lib.strings.substring (builtins.stringLength "jellyfin-plugin-") (builtins.stringLength name) name} = value; + } else acc) {} pkgs; + }; + } + ) diff --git a/package/jellyfin/plugin.nix b/package/jellyfin/plugin.nix deleted file mode 100644 index 4abf045..0000000 --- a/package/jellyfin/plugin.nix +++ /dev/null @@ -1,177 +0,0 @@ -{ - lib, - jellyfin, - plugin, - fetchFromGitHub, - buildDotnetModule, - gnused, - jprm, - unzip, - dotnetCorePackages, - callPackage, -}: -let - drv = - args: - let - meta = from: { inherit (from) license homepage description; }; - - capitalize = - upper: str: - let - length = builtins.stringLength str; - head = builtins.substring 0 1 str; - tail = builtins.substring 1 length str; - in - "${upper head}${tail}"; - - helper = { - base = plugin; - name = builtins.baseNameOf info.base; - self = info; - owner = "jellyfin"; - repo = "jellyfin-plugin-${info.name}"; - hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; - rev = "v${info.version}"; - description = "${info.name} plugin for jellyfin"; - homepage = jellyfin.meta.homepage; - license = jellyfin.meta.license; - mkPlugin = info: result: buildDotnetModule result; - inherit jellyfin; - ignore = builtins.attrNames helper ++ [ - "override" - "overrideAttrs" - "overrideDerivation" - ]; - }; - - defaults = { - pname = "jellyfin-plugin-${info.name}"; - nugetDeps = - let - options = builtins.filter builtins.pathExists [ - "${info.base}/deps.json" - ]; - in - assert options != [ ]; - builtins.head options; - pluginLibraries = lib.attrsets.foldlAttrs ( - acc: name: value: - acc ++ lib.optional (value == "directory") name - ) [ ] (builtins.readDir ("${info.src}/src")); - dotnet-sdk = dotnetCorePackages.sdk_9_0; - dotnet-runtime = jellyfin.dotnet-runtime; - dontDotnetBuild = true; - dontDotnetInstall = true; - project = "Jellyfin.Plugin.${capitalize lib.strings.toUpper info.name}"; - prePatch = '' - sed --sandbox --separate \ - -e 's:\(PackageReference Include="Jellyfin\..*" Version="\)[^"]\+":\1${info.jellyfin.version}":' \ - -e 's:<\(enerateDocumentationFile\|TreatWarningsAsErrors\)>true:<\1>false:' \ - -i ${ - lib.strings.escapeShellArgs (builtins.map (lib: "src/${lib}/${lib}.csproj") info.pluginLibraries) - } - - success=true - for x in ${ - lib.strings.escapeShellArgs (builtins.map (lib: "src/${lib}/${lib}.csproj") info.pluginLibraries) - } - do - diff -q $src/$x $x 2>/dev/null || continue - printf >&2 'no change: %s\n' $x - success=false - done - $success || exit 1 - ''; - projectFile = "src/${info.project}/${info.project}.csproj"; - src = fetchFromGitHub { - owner = info.owner; - repo = info.repo; - inherit (info) rev hash; - }; - nativeBuildInputs = [ - gnused - jprm - unzip - ]; - patches = - lib.optional (builtins.pathExists "${info.base}.patch") "${info.base}.patch" - ++ lib.optionals (builtins.pathExists info.base) ( - lib.attrsets.foldlAttrs ( - acc: name: type: - acc - ++ lib.optional (type == "regular" && lib.strings.hasSuffix ".patch" name) "${info.base}/${name}" - ) [ ] (builtins.readDir info.base) - ); - outputs = [ - "out" - "zip" - ]; - postInstall = - let - dlls = builtins.map (name: "${name}.dll") info.pluginLibraries; - in - '' - tmp_output_dir="$(mktemp -d)" - jprm plugin build . --output="''${tmp_output_dir}" --version="${info.version}" --dotnet-configuration="''${dotnetBuildType-Release}" - mv "''${tmp_output_dir}/${info.name}_${info.version}.zip" $zip - mkdir -p $out - unzip $zip -d $out - - success=true - for file in $out/*; - do - case "''${file##*/}" in - meta.json) - ;; - ${builtins.concatStringsSep "|" (builtins.map lib.strings.escapeShellArg dlls)}) ;; - *) - printf 'unknown file: %s\n' ''${file@Q} - success=false - ;; - esac - done - - for file in meta.json ${lib.strings.escapeShellArgs dlls} - do - [[ -f "$out/$file" ]] && continue - printf 'missing file: %s\n' ''${file@Q} - success=false - done - - $success || exit 42 - ''; - meta = meta jellyfin.meta // meta info; - }; - info = helper // defaults // { - inherit (callPackage plugin ({ inherit info; } // args)) - version hash rev; - }; - in - info.mkPlugin info (lib.attrsets.removeAttrs info info.ignore); - - functor = - args: - (drv args).overrideAttrs ( - final: { passthru ? {}, ... }@prev: { - passthru = passthru // { - in-version = - { - version, - rev ? null, - hash ? null, - }@args': - callPackage functor ( - builtins.removeAttrs args [ - "version" - "rev" - "hash" - ] - // args' - ); - }; - } - ); -in -#lib.trivial.mirrorFunctionArgs plugin functor - drv diff --git a/package/jprm/default.nix b/package/jprm/package.nix similarity index 96% rename from package/jprm/default.nix rename to package/jprm/package.nix index 765a28c..4d14569 100644 --- a/package/jprm/default.nix +++ b/package/jprm/package.nix @@ -21,7 +21,7 @@ python3Packages.buildPythonApplication { src = fetchFromGitHub { owner = "oddstr13"; repo = "jellyfin-plugin-repository-manager"; - tag = "v${version}"; + rev = "v${version}"; hash = hash; }; diff --git a/plugin/dlna/default.nix b/plugin/dlna/default.nix deleted file mode 100644 index afd626a..0000000 --- a/plugin/dlna/default.nix +++ /dev/null @@ -1,32 +0,0 @@ -let - v."8.0.0.0" = { - hash = "sha256-5YUX+w4n3nBhAkdgjF9D5yY/jzRKxpW+mTQCBluzsVI="; - rev = "v8"; - }; - v."10.0.0.0" = { - hash = "sha256-pPhMmH17RKktIX16ozSxsigxo6tU8tlST4IAm3vpjrw="; - rev = "v10"; - }; - - dlna = { - lib, - buildJellyfinPlugin, - fetchJellyfinPlugin, - ... - }@args: let - version = args.version or "10.0.0.0"; - hash = args.hash or v.${version}.hash or ""; - rev = args.rev or v.${version}.rev or "v${lib.versions.major version}"; - extra = lib.attrsets.removeAttrs args (builtins.attrNames (lib.functionArgs dlna)); - - plain = buildJellyfinPlugin { - name = "dlna"; - inherit version; - src = fetchJellyfinPlugin { - name = "dlna"; - inherit hash rev; - }; - nugetDeps = ./deps.json; - }; - in plain.override extra; -in dlna diff --git a/plugin/dlna/package.nix b/plugin/dlna/package.nix new file mode 100644 index 0000000..a848024 --- /dev/null +++ b/plugin/dlna/package.nix @@ -0,0 +1,56 @@ +let + v."8.0.0.0" = { + hash = "sha256-5YUX+w4n3nBhAkdgjF9D5yY/jzRKxpW+mTQCBluzsVI="; + rev = "v8"; + }; + v."10.0.0.0" = { + hash = "sha256-pPhMmH17RKktIX16ozSxsigxo6tU8tlST4IAm3vpjrw="; + rev = "v10"; + }; + + latest = + lib: + builtins.foldl' (acc: next: if lib.versionOlder acc next then next else acc) "0" ( + builtins.attrNames v + ); + + plugin = + { + lib, + buildJellyfinPlugin, + fetchJellyfinPlugin, + ... + }@params: + let + argNames = builtins.attrNames (lib.trivial.functionArgs plugin) ++ [ + "name" + "version" + "hash" + ]; + extraArgs = lib.attrsets.removeAttrs params argNames; + + self = plugin params; + + name = params.name or "dlna"; + version = params.version or (latest lib); # "10.0.0.0"; # TODO latest + defaultRev = { + tag = "v${lib.versions.major version}"; + hash = params.hash or ""; + }; + + args = { + name = name; + version = args.src.version or version; + nugetDeps = ./deps.json; + src = fetchJellyfinPlugin ( + { + inherit name version; + } + // (v.${version} or defaultRev) + ); + } + // extraArgs; + in + buildJellyfinPlugin args; +in +plugin diff --git a/plugin/dlna/xml-sanitize.patch b/plugin/dlna/xml-sanitize.patch new file mode 100644 index 0000000..cc8db5f --- /dev/null +++ b/plugin/dlna/xml-sanitize.patch @@ -0,0 +1,125 @@ +diff --git a/src/Jellyfin.Plugin.Dlna/Didl/DidlBuilder.cs b/src/Jellyfin.Plugin.Dlna/Didl/DidlBuilder.cs +index 150fb49..9160d8a 100644 +--- a/src/Jellyfin.Plugin.Dlna/Didl/DidlBuilder.cs ++++ b/src/Jellyfin.Plugin.Dlna/Didl/DidlBuilder.cs +@@ -239,6 +239,17 @@ public class DidlBuilder + writer.WriteFullEndElement(); + } + ++ private string sanitize(string input) ++ { ++ var valid = input.Where(ch => System.Xml.XmlConvert.IsXmlChar(ch)).ToArray(); ++ if (valid.Length == input.Length) ++ { ++ return input; ++ } ++ _logger.LogInformation("Sanitized: {0} (was: {1})", valid, input.Where(ch => true).ToArray()); ++ return new string(valid); ++ } ++ + private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo? streamInfo = null) + { + if (streamInfo is null) +@@ -1009,7 +1020,7 @@ public class DidlBuilder + writer.WriteStartElement("upnp", "artist", NsUpnp); + writer.WriteAttributeString("role", "AlbumArtist"); + +- writer.WriteString(name); ++ writer.WriteString(sanitize(name)); + + writer.WriteFullEndElement(); + } +@@ -1023,7 +1034,7 @@ public class DidlBuilder + { + try + { +- writer.WriteElementString(prefix, name, namespaceUri, value); ++ writer.WriteElementString(prefix, name, namespaceUri, sanitize(value)); + } + catch (XmlException ex) + { +diff --git a/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToController.cs b/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToController.cs +index 4d822e2..9105d46 100644 +--- a/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToController.cs ++++ b/src/Jellyfin.Plugin.Dlna/PlayTo/PlayToController.cs +@@ -386,34 +386,38 @@ public class PlayToController : ISessionController, IDisposable + ? null : + _userManager.GetUserById(command.ControllingUserId); + +- var items = new List(); +- foreach (var id in command.ItemIds) +- { +- AddItemFromId(id, items); +- } +- + var startIndex = command.StartIndex ?? 0; +- int len = items.Count - startIndex; +- if (startIndex > 0) +- { +- items = items.GetRange(startIndex, len); +- } +- +- var playlist = new PlaylistItem[len]; +- +- // Not nullable enabled - so this is required. +- playlist[0] = CreatePlaylistItem( +- items[0], +- user, +- command.StartPositionTicks ?? 0, +- command.MediaSourceId ?? string.Empty, +- command.AudioStreamIndex, +- command.SubtitleStreamIndex); ++ var playlist = new List(); ++ var first = true; ++ foreach (var id in command.ItemIds) { ++ var item = _libraryManager.GetItemById(id); ++ if (item?.MediaType != MediaType.Audio && item?.MediaType != MediaType.Video) ++ { ++ continue; ++ } + +- for (int i = 1; i < len; i++) +- { +- playlist[i] = CreatePlaylistItem(items[i], user, 0, string.Empty, null, null); +- } ++ if (startIndex > 0) { ++ startIndex -= 1; ++ continue; ++ } ++ ++ PlaylistItem? playlistItem = null; ++ try { ++ playlistItem = CreatePlaylistItem(item, user, ++ first ? (command.StartPositionTicks ?? 0) : 0, ++ first ? (command.MediaSourceId ?? string.Empty) : string.Empty, ++ first ? command.AudioStreamIndex : null, ++ first ? command.SubtitleStreamIndex : null); ++ } catch (System.NullReferenceException) { ++ _logger.LogError("{0}: could not create playlist item.", item.Path ?? ""); ++ } ++ first = false; ++ ++ if (playlistItem != null) ++ { ++ playlist.Add(playlistItem); ++ } ++ } + + _logger.LogDebug("{0} - Playlist created", _session.DeviceName); + +@@ -509,15 +513,6 @@ public class PlayToController : ISessionController, IDisposable + return info.IsDirectStream; + } + +- private void AddItemFromId(Guid id, List list) +- { +- var item = _libraryManager.GetItemById(id); +- if (item?.MediaType == MediaType.Audio || item?.MediaType == MediaType.Video) +- { +- list.Add(item); +- } +- } +- + private PlaylistItem CreatePlaylistItem( + BaseItem item, + User? user,