wip
This commit is contained in:
parent
4c2f85ad62
commit
794925acb2
15 changed files with 516 additions and 529 deletions
94
flake.nix
94
flake.nix
|
|
@ -2,7 +2,7 @@
|
||||||
description = "A very basic flake";
|
description = "A very basic flake";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
|
|
@ -10,16 +10,6 @@
|
||||||
let
|
let
|
||||||
inherit (nixpkgs) lib;
|
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'directory = scan (name: type: if type == "directory" then name else null);
|
||||||
scan'regular = scan (name: type: if type == "regular" then name else null);
|
scan'regular = scan (name: type: if type == "regular" then name else null);
|
||||||
scan'nix =
|
scan'nix =
|
||||||
|
|
@ -45,62 +35,42 @@
|
||||||
in
|
in
|
||||||
lib.attrsets.foldlAttrs fold empty files;
|
lib.attrsets.foldlAttrs fold empty files;
|
||||||
|
|
||||||
packages =
|
scope = pkgs: lib.makeScope pkgs.newScope (
|
||||||
pkgs:
|
self:
|
||||||
let
|
lib.packagesFromDirectoryRecursive {
|
||||||
package =
|
callPackage = self.callPackage;
|
||||||
name: path:
|
directory = ./package;
|
||||||
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) (
|
jellyfin = (
|
||||||
name: path: path
|
self.callPackage ./package/jellyfin/package.nix {
|
||||||
) path;
|
jellyfin = pkgs.jellyfin;
|
||||||
in
|
|
||||||
pkg.overrideAttrs (
|
|
||||||
final: prev: {
|
|
||||||
patches = (prev.patches or [ ]) ++ builtins.attrValues patches;
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
in
|
//
|
||||||
scan'directory package ./package;
|
lib.attrsets.mapAttrs'
|
||||||
|
(name: value: {
|
||||||
plugins =
|
name = "jellyfin-plugin-${name}";
|
||||||
pkgs:
|
value = value;
|
||||||
let
|
})
|
||||||
plugin = name: path: (pkgs' pkgs).jellyfin.plugin path { };
|
(
|
||||||
in
|
lib.packagesFromDirectoryRecursive {
|
||||||
scan'directory plugin ./plugin;
|
callPackage = self.callPackage;
|
||||||
|
directory = ./plugin;
|
||||||
in
|
}
|
||||||
{
|
)
|
||||||
overlays.default = overlay;
|
|
||||||
|
|
||||||
packages = per-pkgs (
|
|
||||||
pkgs:
|
|
||||||
(packages pkgs)
|
|
||||||
// lib.attrsets.mapAttrs' (name: value: {
|
|
||||||
name = "plugin-${name}";
|
|
||||||
inherit value;
|
|
||||||
}) (plugins pkgs)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
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
|
in
|
||||||
builtins.mapAttrs module modules;
|
{
|
||||||
|
overlay = final: scope;
|
||||||
|
|
||||||
|
legacyPackages = per-pkgs scope;
|
||||||
|
|
||||||
|
packages = per-pkgs (pkgs: lib.filterAttrs (_: lib.isDerivation) (scope pkgs));
|
||||||
|
|
||||||
|
nixosModules.default = import ./module/default.nix scope;
|
||||||
|
|
||||||
formatter = per-pkgs ({ nixfmt-tree, ... }: nixfmt-tree);
|
formatter = per-pkgs ({ nixfmt-tree, ... }: nixfmt-tree);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
|
scope:
|
||||||
{
|
{
|
||||||
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
config,
|
config,
|
||||||
lib,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.services.jellyfin;
|
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 =
|
serviceConfig =
|
||||||
pkg:
|
pkg:
|
||||||
|
|
@ -22,15 +25,13 @@ let
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
BindReadOnlyPaths = per-file pkg (
|
BindReadOnlyPaths = map (name: "${pkg}/${name}:${cfg.dataDir}/plugins/${pkg.name}/${name}") pkg.jellyfinPluginFiles;
|
||||||
name: "${pkg}/${name}.dll:${cfg.dataDir}/plugins/${pkg.name}/${name}.dll"
|
|
||||||
);
|
|
||||||
BindPaths = [
|
BindPaths = [
|
||||||
"${cfg.dataDir}/plugins/${pkg.name}/meta.json"
|
"${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
|
in
|
||||||
{
|
{
|
||||||
options.services.jellyfin.plugins = lib.mkOption {
|
options.services.jellyfin.plugins = lib.mkOption {
|
||||||
|
|
@ -41,7 +42,7 @@ in
|
||||||
config.systemd = lib.mkIf (cfg.plugins != { }) {
|
config.systemd = lib.mkIf (cfg.plugins != { }) {
|
||||||
services.jellyfin.serviceConfig = lib.mkMerge (
|
services.jellyfin.serviceConfig = lib.mkMerge (
|
||||||
[
|
[
|
||||||
{ TemporaryFileSystem = [ "${cfg.dataDir}/plugins:ro" ]; }
|
{ BindPaths = [ "${cfg.dataDir}/plugins" ]; }
|
||||||
{ BindPaths = [ "${cfg.dataDir}/plugins/configurations/" ]; }
|
{ BindPaths = [ "${cfg.dataDir}/plugins/configurations/" ]; }
|
||||||
]
|
]
|
||||||
++ builtins.map serviceConfig cfg.plugins
|
++ builtins.map serviceConfig cfg.plugins
|
||||||
146
package/buildJellyfinPlugin/package.nix
Normal file
146
package/buildJellyfinPlugin/package.nix
Normal file
|
|
@ -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>:<\1>false</\1>:' \
|
||||||
|
-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
|
||||||
35
package/fetchJellyfinPlugin/package.nix
Normal file
35
package/fetchJellyfinPlugin/package.nix
Normal file
|
|
@ -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
|
||||||
|
|
@ -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>:<\1>false</\1>:' \
|
|
||||||
-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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
23
package/jellyfin/package.nix
Normal file
23
package/jellyfin/package.nix
Normal file
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -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>:<\1>false</\1>:' \
|
|
||||||
-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
|
|
||||||
|
|
@ -21,7 +21,7 @@ python3Packages.buildPythonApplication {
|
||||||
src = fetchFromGitHub {
|
src = fetchFromGitHub {
|
||||||
owner = "oddstr13";
|
owner = "oddstr13";
|
||||||
repo = "jellyfin-plugin-repository-manager";
|
repo = "jellyfin-plugin-repository-manager";
|
||||||
tag = "v${version}";
|
rev = "v${version}";
|
||||||
hash = hash;
|
hash = hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -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
|
|
||||||
56
plugin/dlna/package.nix
Normal file
56
plugin/dlna/package.nix
Normal file
|
|
@ -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
|
||||||
125
plugin/dlna/xml-sanitize.patch
Normal file
125
plugin/dlna/xml-sanitize.patch
Normal file
|
|
@ -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<BaseItem>();
|
||||||
|
- 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<PlaylistItem>();
|
||||||
|
+ 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 ?? "<unknown>");
|
||||||
|
+ }
|
||||||
|
+ 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<BaseItem> 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,
|
||||||
Loading…
Add table
Add a link
Reference in a new issue