mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-03-14 08:26:37 +01:00
549 lines
15 KiB
Nix
549 lines
15 KiB
Nix
/*
|
|
** To customize the enabled beets plugins, use the pluginOverrides input to the
|
|
** derivation.
|
|
** Examples:
|
|
**
|
|
** Disabling a builtin plugin:
|
|
** python3.pkgs.beets.override {
|
|
** pluginOverrides = {
|
|
** beatport.enable = false;
|
|
** };
|
|
** }
|
|
**
|
|
** Enabling an external plugin:
|
|
** python3.pkgs.beets.override {
|
|
** pluginOverrides = {
|
|
** alternatives = {
|
|
** enable = true;
|
|
** propagatedBuildInputs = [ beets-alternatives ];
|
|
** };
|
|
** };
|
|
** }
|
|
**
|
|
** For an example adding a builtin plugin, see
|
|
** passthru.tests.with-new-builtin-plugin below
|
|
*/
|
|
{
|
|
lib,
|
|
stdenv,
|
|
buildPythonPackage,
|
|
pythonAtLeast,
|
|
fetchFromGitHub,
|
|
|
|
# build-system
|
|
poetry-core,
|
|
poetry-dynamic-versioning,
|
|
|
|
# dependencies
|
|
confuse,
|
|
gst-python,
|
|
jellyfish,
|
|
mediafile,
|
|
munkres,
|
|
musicbrainzngs,
|
|
platformdirs,
|
|
pyyaml,
|
|
unidecode,
|
|
reflink,
|
|
typing-extensions,
|
|
lap,
|
|
|
|
# native
|
|
gobject-introspection,
|
|
sphinxHook,
|
|
sphinx-design,
|
|
sphinx-copybutton,
|
|
pydata-sphinx-theme,
|
|
|
|
# buildInputs
|
|
gst_all_1,
|
|
|
|
# plugin deps
|
|
aacgain,
|
|
beautifulsoup4,
|
|
chromaprint,
|
|
discogs-client,
|
|
ffmpeg,
|
|
flac,
|
|
flask,
|
|
flask-cors,
|
|
imagemagick,
|
|
keyfinder-cli,
|
|
langdetect,
|
|
librosa,
|
|
mp3gain,
|
|
mp3val,
|
|
mpd2,
|
|
pyacoustid,
|
|
pylast,
|
|
pyxdg,
|
|
requests,
|
|
requests-oauthlib,
|
|
resampy,
|
|
soco,
|
|
|
|
# configurations
|
|
extraPatches ? [ ],
|
|
pluginOverrides ? { },
|
|
disableAllPlugins ? false,
|
|
extraDisabledTests ? [ ],
|
|
extraNativeBuildInputs ? [ ],
|
|
|
|
# tests
|
|
pytestCheckHook,
|
|
pytest-cov-stub,
|
|
mock,
|
|
rarfile,
|
|
responses,
|
|
requests-mock,
|
|
pillow,
|
|
writableTmpDirAsHomeHook,
|
|
|
|
# preCheck
|
|
bashInteractive,
|
|
diffPlugins,
|
|
runtimeShell,
|
|
writeScript,
|
|
|
|
# passthru.tests
|
|
runCommand,
|
|
beets,
|
|
}:
|
|
|
|
buildPythonPackage (finalAttrs: {
|
|
pname = "beets";
|
|
version = "2.5.1";
|
|
src = fetchFromGitHub {
|
|
owner = "beetbox";
|
|
repo = "beets";
|
|
tag = "v${finalAttrs.version}";
|
|
hash = "sha256-H3jcEHyK13+RHVlV4zp+8M3LZ0Jc2FdmAbLpekGozLA=";
|
|
};
|
|
pyproject = true;
|
|
# Waiting for https://github.com/beetbox/beets/pull/6267
|
|
disabled = pythonAtLeast "3.14";
|
|
|
|
patches = [
|
|
# Bash completion fix for Nix
|
|
./bash-completion-always-print.patch
|
|
]
|
|
++ extraPatches;
|
|
|
|
build-system = [
|
|
poetry-core
|
|
poetry-dynamic-versioning
|
|
];
|
|
|
|
dependencies = [
|
|
confuse
|
|
gst-python
|
|
jellyfish
|
|
mediafile
|
|
munkres
|
|
musicbrainzngs
|
|
platformdirs
|
|
pyyaml
|
|
unidecode
|
|
# Can be built without it, but is useful on btrfs systems, and doesn't
|
|
# add too much to the closure. See:
|
|
# https://github.com/NixOS/nixpkgs/issues/437308
|
|
reflink
|
|
typing-extensions
|
|
lap
|
|
]
|
|
++ (lib.concatMap (p: p.propagatedBuildInputs) (
|
|
lib.attrValues finalAttrs.finalPackage.passthru.plugins.enabled
|
|
));
|
|
|
|
nativeBuildInputs = [
|
|
gobject-introspection
|
|
sphinxHook
|
|
sphinx-design
|
|
sphinx-copybutton
|
|
pydata-sphinx-theme
|
|
]
|
|
++ extraNativeBuildInputs;
|
|
|
|
buildInputs = with gst_all_1; [
|
|
gst-plugins-base
|
|
gst-plugins-good
|
|
gst-plugins-ugly
|
|
];
|
|
|
|
outputs = [
|
|
"out"
|
|
"doc"
|
|
"man"
|
|
];
|
|
sphinxBuilders = [
|
|
"html"
|
|
"man"
|
|
];
|
|
# Causes an installManPage error. Not clear why this directory gets generated
|
|
# with the manpages. The same directory is observed correctly in
|
|
# $doc/share/doc/beets-${version}/html
|
|
preInstallSphinx = ''
|
|
rm -r .sphinx/man/man/_sphinx_design_static
|
|
'';
|
|
|
|
postInstall = ''
|
|
mkdir -p $out/share/zsh/site-functions
|
|
cp extra/_beet $out/share/zsh/site-functions/
|
|
'';
|
|
|
|
makeWrapperArgs = [
|
|
"--set GI_TYPELIB_PATH \"$GI_TYPELIB_PATH\""
|
|
"--set GST_PLUGIN_SYSTEM_PATH_1_0 \"$GST_PLUGIN_SYSTEM_PATH_1_0\""
|
|
"--prefix PATH : ${lib.makeBinPath finalAttrs.finalPackage.passthru.plugins.wrapperBins}"
|
|
];
|
|
|
|
nativeCheckInputs = [
|
|
pytestCheckHook
|
|
pytest-cov-stub
|
|
mock
|
|
rarfile
|
|
responses
|
|
requests-mock
|
|
pillow
|
|
writableTmpDirAsHomeHook
|
|
]
|
|
++ finalAttrs.finalPackage.passthru.plugins.wrapperBins;
|
|
|
|
__darwinAllowLocalNetworking = true;
|
|
|
|
disabledTestPaths =
|
|
finalAttrs.finalPackage.passthru.plugins.disabledTestPaths
|
|
++ [
|
|
# touches network
|
|
"test/plugins/test_aura.py"
|
|
]
|
|
++ lib.optionals stdenv.hostPlatform.isDarwin [
|
|
# Flaky: several tests fail randomly with:
|
|
# if not self._poll(timeout):
|
|
# raise Empty
|
|
# _queue.Empty
|
|
"test/plugins/test_bpd.py"
|
|
]
|
|
++ lib.optionals stdenv.hostPlatform.isLinux [
|
|
# fail on Hydra with `RuntimeError: image cannot be obtained without artresizer backend`
|
|
"test/plugins/test_art.py::AlbumArtOperationConfigurationTest::test_enforce_ratio"
|
|
"test/plugins/test_art.py::AlbumArtOperationConfigurationTest::test_enforce_ratio_with_percent_margin"
|
|
"test/plugins/test_art.py::AlbumArtOperationConfigurationTest::test_enforce_ratio_with_px_margin"
|
|
"test/plugins/test_art.py::AlbumArtOperationConfigurationTest::test_minwidth"
|
|
"test/plugins/test_art.py::AlbumArtPerformOperationTest::test_deinterlaced"
|
|
"test/plugins/test_art.py::AlbumArtPerformOperationTest::test_deinterlaced_and_resized"
|
|
"test/plugins/test_art.py::AlbumArtPerformOperationTest::test_file_not_resized"
|
|
"test/plugins/test_art.py::AlbumArtPerformOperationTest::test_file_resized"
|
|
"test/plugins/test_art.py::AlbumArtPerformOperationTest::test_file_resized_and_scaled"
|
|
"test/plugins/test_art.py::AlbumArtPerformOperationTest::test_file_resized_but_not_scaled"
|
|
"test/plugins/test_art.py::AlbumArtPerformOperationTest::test_resize"
|
|
];
|
|
disabledTests = extraDisabledTests ++ [
|
|
# touches network
|
|
"test_merge_duplicate_album"
|
|
# The existence of the dependency reflink (see comment above), causes this
|
|
# test to be run, and it fails in the sandbox.
|
|
"test_successful_reflink"
|
|
];
|
|
|
|
# Perform extra "sanity checks", before running pytest tests.
|
|
preCheck = ''
|
|
# Check for undefined plugins
|
|
find beetsplug -mindepth 1 \
|
|
\! -path 'beetsplug/__init__.py' -a \
|
|
\( -name '*.py' -o -path 'beetsplug/*/__init__.py' \) -print \
|
|
| sed -n -re 's|^beetsplug/([^/.]+).*|\1|p' \
|
|
| sort -u > plugins_available
|
|
${diffPlugins (lib.attrNames finalAttrs.finalPackage.passthru.plugins.builtins) "plugins_available"}
|
|
|
|
export BEETS_TEST_SHELL="${lib.getExe bashInteractive} --norc"
|
|
|
|
env EDITOR="${writeScript "beetconfig.sh" ''
|
|
#!${runtimeShell}
|
|
cat > "$1" <<CFG
|
|
plugins: ${lib.concatStringsSep " " (lib.attrNames finalAttrs.finalPackage.passthru.plugins.enabled)}
|
|
CFG
|
|
''}" "$out/bin/beet" config -e
|
|
env EDITOR=true "$out/bin/beet" config -e
|
|
'';
|
|
|
|
passthru = {
|
|
plugins = {
|
|
builtins = {
|
|
absubmit = {
|
|
deprecated = true;
|
|
testPaths = [ ];
|
|
};
|
|
advancedrewrite = {
|
|
testPaths = [ ];
|
|
};
|
|
acousticbrainz = {
|
|
deprecated = true;
|
|
propagatedBuildInputs = [ requests ];
|
|
};
|
|
albumtypes = { };
|
|
aura = {
|
|
propagatedBuildInputs = [
|
|
flask
|
|
flask-cors
|
|
pillow
|
|
];
|
|
};
|
|
autobpm = {
|
|
propagatedBuildInputs = [
|
|
librosa
|
|
# An optional dependency of librosa, needed for beets' autobpm
|
|
resampy
|
|
];
|
|
};
|
|
badfiles = {
|
|
testPaths = [ ];
|
|
wrapperBins = [
|
|
mp3val
|
|
flac
|
|
];
|
|
};
|
|
bareasc = { };
|
|
beatport.propagatedBuildInputs = [ requests-oauthlib ];
|
|
bench.testPaths = [ ];
|
|
bpd.testPaths = [ ];
|
|
bpm.testPaths = [ ];
|
|
bpsync.testPaths = [ ];
|
|
bucket = { };
|
|
chroma = {
|
|
propagatedBuildInputs = [ pyacoustid ];
|
|
testPaths = [ ];
|
|
wrapperBins = [
|
|
chromaprint
|
|
];
|
|
};
|
|
convert.wrapperBins = [ ffmpeg ];
|
|
deezer = {
|
|
propagatedBuildInputs = [ requests ];
|
|
testPaths = [ ];
|
|
};
|
|
discogs.propagatedBuildInputs = [
|
|
discogs-client
|
|
requests
|
|
];
|
|
duplicates.testPaths = [ ];
|
|
edit = { };
|
|
embedart = {
|
|
propagatedBuildInputs = [ pillow ];
|
|
wrapperBins = [ imagemagick ];
|
|
};
|
|
embyupdate.propagatedBuildInputs = [ requests ];
|
|
export = { };
|
|
fetchart = {
|
|
propagatedBuildInputs = [
|
|
beautifulsoup4
|
|
langdetect
|
|
pillow
|
|
requests
|
|
];
|
|
wrapperBins = [ imagemagick ];
|
|
};
|
|
filefilter = { };
|
|
fish.testPaths = [ ];
|
|
freedesktop.testPaths = [ ];
|
|
fromfilename.testPaths = [ ];
|
|
ftintitle = { };
|
|
fuzzy.testPaths = [ ];
|
|
gmusic.testPaths = [ ];
|
|
hook = { };
|
|
ihate = { };
|
|
importadded = { };
|
|
importfeeds = { };
|
|
info = { };
|
|
inline.testPaths = [ ];
|
|
ipfs = { };
|
|
keyfinder.wrapperBins = [ keyfinder-cli ];
|
|
kodiupdate = {
|
|
propagatedBuildInputs = [ requests ];
|
|
testPaths = [ ];
|
|
};
|
|
lastgenre.propagatedBuildInputs = [ pylast ];
|
|
lastimport = {
|
|
propagatedBuildInputs = [ pylast ];
|
|
testPaths = [ ];
|
|
};
|
|
limit = { };
|
|
listenbrainz = {
|
|
testPaths = [ ];
|
|
};
|
|
loadext = {
|
|
propagatedBuildInputs = [ requests ];
|
|
testPaths = [ ];
|
|
};
|
|
lyrics.propagatedBuildInputs = [
|
|
beautifulsoup4
|
|
langdetect
|
|
requests
|
|
];
|
|
mbcollection.testPaths = [ ];
|
|
mbsubmit = { };
|
|
mbsync = { };
|
|
metasync.testPaths = [ ];
|
|
missing.testPaths = [ ];
|
|
mpdstats.propagatedBuildInputs = [ mpd2 ];
|
|
mpdupdate = {
|
|
propagatedBuildInputs = [ mpd2 ];
|
|
testPaths = [ ];
|
|
};
|
|
musicbrainz = { };
|
|
parentwork = { };
|
|
permissions = { };
|
|
play = { };
|
|
playlist.propagatedBuildInputs = [ requests ];
|
|
plexupdate = { };
|
|
random = { };
|
|
replace = { };
|
|
replaygain.wrapperBins = [
|
|
aacgain
|
|
ffmpeg
|
|
mp3gain
|
|
];
|
|
rewrite.testPaths = [ ];
|
|
scrub.testPaths = [ ];
|
|
smartplaylist = { };
|
|
sonosupdate = {
|
|
propagatedBuildInputs = [ soco ];
|
|
testPaths = [ ];
|
|
};
|
|
spotify = { };
|
|
subsonicplaylist = {
|
|
propagatedBuildInputs = [ requests ];
|
|
testPaths = [ ];
|
|
};
|
|
subsonicupdate.propagatedBuildInputs = [ requests ];
|
|
substitute = {
|
|
testPaths = [ ];
|
|
};
|
|
the = { };
|
|
thumbnails = {
|
|
propagatedBuildInputs = [
|
|
pillow
|
|
pyxdg
|
|
];
|
|
wrapperBins = [ imagemagick ];
|
|
};
|
|
types.testPaths = [ "test/plugins/test_types_plugin.py" ];
|
|
unimported.testPaths = [ ];
|
|
web.propagatedBuildInputs = [
|
|
flask
|
|
flask-cors
|
|
];
|
|
zero = { };
|
|
_typing = {
|
|
testPaths = [ ];
|
|
};
|
|
_utils = {
|
|
testPaths = [ ];
|
|
};
|
|
};
|
|
base = lib.mapAttrs (
|
|
_: a: { builtin = true; } // a
|
|
) finalAttrs.finalPackage.passthru.plugins.builtins;
|
|
overrides = lib.mapAttrs (
|
|
plugName:
|
|
lib.throwIf (finalAttrs.finalPackage.passthru.plugins.builtins.${plugName}.deprecated or false)
|
|
"beets evaluation error: Plugin ${plugName} was enabled in pluginOverrides, but it has been removed. Remove the override to fix evaluation."
|
|
) pluginOverrides;
|
|
all =
|
|
lib.mapAttrs
|
|
(
|
|
n: a:
|
|
{
|
|
name = n;
|
|
enable = !disableAllPlugins;
|
|
builtin = false;
|
|
propagatedBuildInputs = [ ];
|
|
testPaths = [ "test/plugins/test_${n}.py" ];
|
|
wrapperBins = [ ];
|
|
}
|
|
// a
|
|
)
|
|
(
|
|
lib.recursiveUpdate finalAttrs.finalPackage.passthru.plugins.base finalAttrs.finalPackage.passthru.plugins.overrides
|
|
);
|
|
enabled = lib.filterAttrs (_: p: p.enable) finalAttrs.finalPackage.passthru.plugins.all;
|
|
disabled = lib.filterAttrs (_: p: !p.enable) finalAttrs.finalPackage.passthru.plugins.all;
|
|
disabledTestPaths = lib.flatten (
|
|
lib.attrValues (lib.mapAttrs (_: v: v.testPaths) finalAttrs.finalPackage.passthru.plugins.disabled)
|
|
);
|
|
wrapperBins = lib.concatMap (p: p.wrapperBins) (
|
|
lib.attrValues finalAttrs.finalPackage.passthru.plugins.enabled
|
|
);
|
|
};
|
|
tests = {
|
|
gstreamer =
|
|
runCommand "beets-gstreamer-test"
|
|
{
|
|
meta.timeout = 60;
|
|
}
|
|
''
|
|
set -euo pipefail
|
|
export HOME=$(mktemp -d)
|
|
mkdir $out
|
|
|
|
cat << EOF > $out/config.yaml
|
|
replaygain:
|
|
backend: gstreamer
|
|
EOF
|
|
|
|
${finalAttrs.finalPackage}/bin/beet -c $out/config.yaml > /dev/null
|
|
'';
|
|
with-new-builtin-plugin = finalAttrs.finalPackage.overrideAttrs (
|
|
newAttrs: oldAttrs: {
|
|
postPatch = (oldAttrs.postPatch or "") + ''
|
|
mkdir -p beetsplug/my_special_plugin
|
|
touch beetsplug/my_special_plugin/__init__.py
|
|
'';
|
|
passthru = lib.recursiveUpdate oldAttrs.passthru {
|
|
plugins.builtins.my_special_plugin = { };
|
|
};
|
|
}
|
|
);
|
|
# Test that disabling
|
|
with-mpd-plugins-disabled = beets.override {
|
|
pluginOverrides = {
|
|
# These two plugins require mpd2 Python dependency. If they are
|
|
# disabled, this dependency shouldn't be pulled, and the `runCommand`
|
|
# test below should fail with a `ModuleNotFoundError`
|
|
mpdstats.enable = false;
|
|
mpdupdate.enable = false;
|
|
};
|
|
};
|
|
mpd-plugins-really-disabled = runCommand "beets-mpd-plugins-disabled-test" { } ''
|
|
set -euo pipefail
|
|
export HOME=$(mktemp -d)
|
|
mkdir $out
|
|
|
|
cat << EOF > $out/config.yaml
|
|
plugins:
|
|
- mpdstats
|
|
EOF
|
|
${finalAttrs.finalPackage.passthru.tests.with-mpd-plugins-disabled}/bin/beet \
|
|
-c $out/config.yaml \
|
|
--help 2> $out/help-stderr || true
|
|
${finalAttrs.finalPackage.passthru.tests.with-mpd-plugins-disabled}/bin/beet \
|
|
-c $out/config.yaml \
|
|
mpdstats --help 2> $out/mpdstats-help-stderr || true
|
|
'';
|
|
};
|
|
};
|
|
|
|
meta = {
|
|
description = "Music tagger and library organizer";
|
|
homepage = "https://beets.io";
|
|
license = lib.licenses.mit;
|
|
maintainers = with lib.maintainers; [
|
|
astratagem
|
|
doronbehar
|
|
lovesegfault
|
|
pjones
|
|
];
|
|
platforms = lib.platforms.linux ++ lib.platforms.darwin;
|
|
mainProgram = "beet";
|
|
};
|
|
})
|