rework layout

This commit is contained in:
Jonas Rabenstein 2025-09-20 13:34:01 +02:00
commit 81127287c1
8 changed files with 256 additions and 197 deletions

View file

@ -20,43 +20,59 @@
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 =
let
stem = lib.strings.removeSuffix ".nix";
in
scan (name: type: if type == "regular" && name == "${stem name}.nix" then stem name else null);
per-pkgs = fn: builtins.mapAttrs (_: fn) nixpkgs.legacyPackages;
scan =
base: fn:
filter: fn: base:
let
fold =
acc: entry: kind:
let
name = lib.strings.removeSuffix ".nix" entry;
attr = lib.optionalAttrs (lib.strings.hasSuffix ".nix" entry) {
${name} = fn name "${base}/${entry}";
empty = { };
create =
name: entry:
lib.optionalAttrs (name != null) {
${name} = fn name (base + "/${entry}");
};
in
acc // attr;
fold =
acc: name: kind:
acc // create (filter name kind) name;
files = lib.optionalAttrs (builtins.pathExists base) (builtins.readDir base);
in
lib.attrsets.foldlAttrs fold { } files;
lib.attrsets.foldlAttrs fold empty files;
packages =
pkgs: scan ./package (name: def: (pkgs' pkgs).callPackage def { original = pkgs.${name} or null; });
pkgs:
let
package =
name: path:
let
pkg = (pkgs' pkgs).callPackage path {
original = pkgs.${name} or null;
};
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;
}
);
in
scan'directory package ./package;
plugins =
pkgs:
let
base = ./plugin;
result = lib.attrsets.foldlAttrs (
acc: name: kind:
acc
// lib.optionalAttrs (kind == "directory") {
${name} = (pkgs' pkgs).jellyfin.plugin (base + "/${name}") { };
}
) { } (builtins.readDir base);
plugin = name: path: (pkgs' pkgs).jellyfin.plugin path { };
in
if builtins.pathExists base then result else { };
module = defs: {
imports = lib.toList defs;
config.nixpkgs.overlays = [ overlay ];
};
scan'directory plugin ./plugin;
in
{
@ -73,14 +89,18 @@
nixosModules =
let
modules = scan ./module (
_: def: {
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: { ... }: builtins.trace "module: jellyfin.${name}" {
imports = lib.toList defs;
config.nixpkgs.overlays = [ overlay ];
imports = [ def ];
}
);
};
in
{ default.imports = builtins.attrValues modules; } // modules;
builtins.mapAttrs module modules;
formatter = per-pkgs ({ nixfmt-tree, ... }: nixfmt-tree);
};

View file

@ -7,7 +7,7 @@
let
cfg = config.services.jellyfin;
plugin = name: pks."jellyfin-plugin-${name}";
plugin = name: pkgs."jellyfin-plugin-${name}";
per-file = pkg: fn: lib.lists.foldl (acc: dll: acc ++ (fn dll)) pkg.pluginLibraries;
@ -25,9 +25,9 @@ let
BindReadOnlyPaths = per-file pkg (
name: "${pkg}/${name}:${cfg.dataDir}/plugins/${pkg.name}/${name}"
);
BindPaths = per-file pkg (
name: lib.optional (rw name) "${cfg.dataDir}/plugins/${pkg.name}/${name}"
);
BindPaths = [
"${cfg.dataDir}/plugins/${pkg.name}/meta.json"
];
};
type.plugin = lib.types.addCheck lib.types.package (builtins.hasAttr "pluginLibraries");
@ -51,7 +51,7 @@ in
acc: pkg:
acc
// {
${pkg.name}."${cfg.dataDir}/plugins/${pkgs.name}/${name}".C = {
${pkg.name}."${cfg.dataDir}/plugins/${pkgs.name}/meta.json".C = {
group = cfg.group;
user = cfg.user;
mode = "0700";

View file

@ -1,159 +0,0 @@
{
lib,
original,
callPackage,
dotnetCorePackages,
fetchFromGitHub,
buildDotnetModule,
gnused,
jprm,
unzip,
}:
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}";
jellyfin = original.overrideAttrs (
final: prev:
assert !prev ? "plugin";
{
passthru.plugin =
base: args:
let
meta = from: { inherit (from) license homepage description; };
helper = {
inherit base;
name = builtins.baseNameOf info.base;
self = info;
owner = "jellyfin";
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
rev = "v${info.version}";
description = "${info.name} plugin for jellyfin";
homepage = original.meta.homepage;
license = original.meta.license;
override =
assert false;
null;
overrideAttrs =
assert false;
null;
overrideDerivation =
assert false;
null;
mkPlugin = info: result: buildDotnetModule result;
inherit jellyfin;
};
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_8_0;
dotnet-runtime = dotnetCorePackages.aspnetcore_8_0;
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 = "jellyfin-plugin-${info.name}";
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 original.meta // meta info;
};
info = helper // defaults // callPackage base ({ inherit info; } // args);
result = lib.attrsets.removeAttrs info (builtins.attrNames helper);
in
info.mkPlugin info result;
}
);
in
jellyfin

View file

@ -0,0 +1,19 @@
{
original,
callPackage,
}:
let
jellyfin = original.overrideAttrs (
final: prev:
assert !prev ? "plugin";
{
passthru.plugin =
base:
callPackage ./plugin.nix {
plugin = base;
jellyfin = jellyfin;
};
}
);
in
jellyfin

View file

@ -0,0 +1,26 @@
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 1801db70d..9fe792b26 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -100,6 +100,11 @@ namespace MediaBrowser.Model.Dlna
}
MediaStream audioStream = item.GetDefaultAudioStream(null);
+ if (audioStream is null)
+ {
+ _logger.LogError("No audio stream for {0}", item.Path ?? "<unknown>");
+ return null;
+ }
var directPlayInfo = GetAudioDirectPlayProfile(item, audioStream, options);
@@ -434,6 +439,9 @@ namespace MediaBrowser.Model.Dlna
TranscodeReason transcodeReasons = 0;
if (directPlayProfile is null)
{
+ _logger.LogDebug("Profile: {0}", options.Profile.Name ?? "<unknown>");
+ _logger.LogDebug("Path: {0}", item.Path ?? "<unknwon>");
+ _logger.LogDebug("Codec: {0}", audioStream.Codec ?? "<unknown>");
_logger.LogDebug(
"Profile: {0}, No audio direct play profiles found for {1} with codec {2}",
options.Profile.Name ?? "Unknown Profile",

153
package/jellyfin/plugin.nix Normal file
View file

@ -0,0 +1,153 @@
{
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_8_0;
dotnet-runtime = dotnetCorePackages.aspnetcore_8_0;
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 // callPackage plugin ({ inherit info; } // args);
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');
};
});
in lib.trivial.mirrorFunctionArgs plugin functor

View file

@ -12,7 +12,7 @@ in
{
lib,
version ? current lib,
hash ? v.${version}.hash or "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
hash ? v.${version}.hash,
rev ? v.${version}.rev or "v${version}",
...
}: