diff --git a/pkgs/tools/audio/beets/badfiles-plugin-nix-paths.patch b/pkgs/tools/audio/beets/badfiles-plugin-nix-paths.patch deleted file mode 100644 index 6956183344c..00000000000 --- a/pkgs/tools/audio/beets/badfiles-plugin-nix-paths.patch +++ /dev/null @@ -1,21 +0,0 @@ -diff --git i/beetsplug/badfiles.py w/beetsplug/badfiles.py -index 36b45de3..5208b696 100644 ---- i/beetsplug/badfiles.py -+++ w/beetsplug/badfiles.py -@@ -71,14 +71,14 @@ class BadFiles(BeetsPlugin): - return status, errors, [line for line in output.split("\n") if line] - - def check_mp3val(self, path): -- status, errors, output = self.run_command(["mp3val", path]) -+ status, errors, output = self.run_command(["@mp3val@/bin/mp3val", path]) - if status == 0: - output = [line for line in output if line.startswith("WARNING:")] - errors = len(output) - return status, errors, output - - def check_flac(self, path): -- return self.run_command(["flac", "-wst", path]) -+ return self.run_command(["@flac@/bin/flac", "-wst", path]) - - def check_custom(self, command): - def checker(path): diff --git a/pkgs/tools/audio/beets/builtin-plugins.nix b/pkgs/tools/audio/beets/builtin-plugins.nix new file mode 100644 index 00000000000..ee7da8d2033 --- /dev/null +++ b/pkgs/tools/audio/beets/builtin-plugins.nix @@ -0,0 +1,94 @@ +{ stdenv, lib +, aacgain ? null +, essentia-extractor ? null +, ffmpeg ? null +, flac ? null +, imagemagick ? null +, keyfinder-cli ? null +, mp3gain ? null +, mp3val ? null +, python3Packages +, ... +}: { + absubmit = { + enable = lib.elem stdenv.hostPlatform essentia-extractor.meta.platforms; + wrapperBins = [ essentia-extractor ]; + }; + acousticbrainz.propagatedBuildInputs = [ python3Packages.requests ]; + albumtypes = { }; + aura.propagatedBuildInputs = with python3Packages; [ flask pillow ]; + badfiles.wrapperBins = [ mp3val flac ]; + bareasc = { }; + beatport.propagatedBuildInputs = [ python3Packages.requests-oauthlib ]; + bench = { }; + bpd = { }; + bpm = { }; + bpsync = { }; + bucket = { }; + chroma.propagatedBuildInputs = [ python3Packages.pyacoustid ]; + convert.wrapperBins = [ ffmpeg ]; + deezer.propagatedBuildInputs = [ python3Packages.requests ]; + discogs.propagatedBuildInputs = with python3Packages; [ discogs-client requests ]; + duplicates = { }; + edit = { }; + embedart = { + propagatedBuildInputs = with python3Packages; [ pillow ]; + wrapperBins = [ imagemagick ]; + }; + embyupdate.propagatedBuildInputs = [ python3Packages.requests ]; + export = { }; + fetchart = { + propagatedBuildInputs = with python3Packages; [ requests pillow ]; + wrapperBins = [ imagemagick ]; + }; + filefilter = { }; + fish = { }; + freedesktop = { }; + fromfilename = { }; + ftintitle = { }; + fuzzy = { }; + gmusic = { }; + hook = { }; + ihate = { }; + importadded = { }; + importfeeds = { }; + info = { }; + inline = { }; + ipfs = { }; + keyfinder.wrapperBins = [ keyfinder-cli ]; + kodiupdate.propagatedBuildInputs = [ python3Packages.requests ]; + lastgenre.propagatedBuildInputs = [ python3Packages.pylast ]; + lastimport.propagatedBuildInputs = [ python3Packages.pylast ]; + loadext.propagatedBuildInputs = [ python3Packages.requests ]; + lyrics.propagatedBuildInputs = [ python3Packages.beautifulsoup4 ]; + mbcollection = { }; + mbsubmit = { }; + mbsync = { }; + metasync = { }; + missing = { }; + mpdstats.propagatedBuildInputs = [ python3Packages.mpd2 ]; + mpdupdate.propagatedBuildInputs = [ python3Packages.mpd2 ]; + parentwork = { }; + permissions = { }; + play = { }; + playlist.propagatedBuildInputs = [ python3Packages.requests ]; + plexupdate = { }; + random = { }; + replaygain.wrapperBins = [ aacgain ffmpeg mp3gain ]; + rewrite = { }; + scrub = { }; + smartplaylist = { }; + sonosupdate.propagatedBuildInputs = [ python3Packages.soco ]; + spotify = { }; + subsonicplaylist.propagatedBuildInputs = [ python3Packages.requests ]; + subsonicupdate.propagatedBuildInputs = [ python3Packages.requests ]; + the = { }; + thumbnails = { + propagatedBuildInputs = with python3Packages; [ pillow pyxdg ]; + wrapperBins = [ imagemagick ]; + }; + types.testPaths = [ "test/test_types_plugin.py" ]; + unimported = { }; + web.propagatedBuildInputs = [ python3Packages.flask ]; + zero = { }; +} diff --git a/pkgs/tools/audio/beets/common.nix b/pkgs/tools/audio/beets/common.nix new file mode 100644 index 00000000000..51391a639cc --- /dev/null +++ b/pkgs/tools/audio/beets/common.nix @@ -0,0 +1,147 @@ +{ stdenv, lib +, bashInteractive +, diffPlugins +, glibcLocales +, gobject-introspection +, gst_all_1 +, python3Packages +, runtimeShell +, writeScript + + # plugin deps +, aacgain ? null +, essentia-extractor ? null +, ffmpeg ? null +, flac ? null +, imagemagick ? null +, keyfinder-cli ? null +, mp3gain ? null +, mp3val ? null + +, src +, version +, pluginOverrides ? { } +, disableAllPlugins ? false +}@inputs: +let + inherit (lib) attrNames attrValues concatMap; + + builtinPlugins = import ./builtin-plugins.nix inputs; + + mkPlugin = { enable ? !disableAllPlugins, propagatedBuildInputs ? [ ], testPaths ? [ ], wrapperBins ? [ ] }: { + inherit enable propagatedBuildInputs testPaths wrapperBins; + }; + + allPlugins = lib.mapAttrs (_: mkPlugin) (lib.recursiveUpdate builtinPlugins pluginOverrides); + enabledPlugins = lib.filterAttrs (_: p: p.enable) allPlugins; + disabledPlugins = lib.filterAttrs (_: p: !p.enable) allPlugins; + + pluginWrapperBins = concatMap (p: p.wrapperBins) (attrValues enabledPlugins); +in +python3Packages.buildPythonApplication rec { + pname = "beets"; + inherit src version; + + patches = [ + # Bash completion fix for Nix + ./patches/bash-completion-always-print.patch + ]; + + propagatedBuildInputs = with python3Packages; [ + confuse + enum34 + gobject-introspection + gst-python + jellyfish + mediafile + munkres + musicbrainzngs + mutagen + pygobject3 + pyyaml + reflink + unidecode + ] ++ (concatMap (p: p.propagatedBuildInputs) (attrValues enabledPlugins)); + + buildInputs = [ + ] ++ (with gst_all_1; [ + gst-plugins-base + gst-plugins-good + gst-plugins-ugly + ]); + + postInstall = '' + mkdir -p $out/share/zsh/site-functions + cp extra/_beet $out/share/zsh/site-functions/ + ''; + + doInstallCheck = true; + + installCheckPhase = '' + runHook preInstallCheck + + tmphome="$(mktemp -d)" + + EDITOR="${writeScript "beetconfig.sh" '' + #!${runtimeShell} + cat > "$1" < plugins_available + ${diffPlugins (attrNames allPlugins) "plugins_available"} + + export BEETS_TEST_SHELL="${bashInteractive}/bin/bash --norc" + export HOME="$(mktemp -d)" + + args=" -m pytest -r fEs" + eval "disabledTestPaths=($disabledTestPaths)" + for path in ''${disabledTestPaths[@]}; do + if [ -e "$path" ]; then + args+=" --ignore \"$path\"" + else + echo "Skipping non-existent test path '$path'" + fi + done + + eval "python $args" + + runHook postCheck + ''; + + meta = with lib; { + description = "Music tagger and library organizer"; + homepage = "https://beets.io"; + license = licenses.mit; + maintainers = with maintainers; [ aszlig doronbehar lovesegfault pjones ]; + platforms = platforms.linux; + }; +} diff --git a/pkgs/tools/audio/beets/convert-plugin-ffmpeg-path.patch b/pkgs/tools/audio/beets/convert-plugin-ffmpeg-path.patch deleted file mode 100644 index 1bc17893448..00000000000 --- a/pkgs/tools/audio/beets/convert-plugin-ffmpeg-path.patch +++ /dev/null @@ -1,34 +0,0 @@ -diff --git i/beetsplug/convert.py w/beetsplug/convert.py -index 6bc07c28..039fb452 100644 ---- i/beetsplug/convert.py -+++ w/beetsplug/convert.py -@@ -118,22 +118,22 @@ class ConvertPlugin(BeetsPlugin): - 'id3v23': 'inherit', - 'formats': { - 'aac': { -- 'command': 'ffmpeg -i $source -y -vn -acodec aac ' -+ 'command': '@ffmpeg@/bin/ffmpeg -i $source -y -vn -acodec aac ' - '-aq 1 $dest', - 'extension': 'm4a', - }, - 'alac': { -- 'command': 'ffmpeg -i $source -y -vn -acodec alac $dest', -+ 'command': '@ffmpeg@/bin/ffmpeg -i $source -y -vn -acodec alac $dest', - 'extension': 'm4a', - }, -- 'flac': 'ffmpeg -i $source -y -vn -acodec flac $dest', -- 'mp3': 'ffmpeg -i $source -y -vn -aq 2 $dest', -+ 'flac': '@ffmpeg@/bin/ffmpeg -i $source -y -vn -acodec flac $dest', -+ 'mp3': '@ffmpeg@/bin/ffmpeg -i $source -y -vn -aq 2 $dest', - 'opus': -- 'ffmpeg -i $source -y -vn -acodec libopus -ab 96k $dest', -+ '@ffmpeg@/bin/ffmpeg -i $source -y -vn -acodec libopus -ab 96k $dest', - 'ogg': -- 'ffmpeg -i $source -y -vn -acodec libvorbis -aq 3 $dest', -+ '@ffmpeg@/bin/ffmpeg -i $source -y -vn -acodec libvorbis -aq 3 $dest', - 'wma': -- 'ffmpeg -i $source -y -vn -acodec wmav2 -vn $dest', -+ '@ffmpeg@/bin/ffmpeg -i $source -y -vn -acodec wmav2 -vn $dest', - }, - 'max_bitrate': 500, - 'auto': False, diff --git a/pkgs/tools/audio/beets/default.nix b/pkgs/tools/audio/beets/default.nix index 0bdbe9d345d..d312ec9b627 100644 --- a/pkgs/tools/audio/beets/default.nix +++ b/pkgs/tools/audio/beets/default.nix @@ -1,279 +1,37 @@ -{ stdenv -, lib +{ lib +, callPackage , fetchFromGitHub -, writeScript -, glibcLocales -, diffPlugins -, substituteAll -, pythonPackages -# can be null, if you wish to disable a reference to it. It's needed for the -# artresizer, see: -# https://beets.readthedocs.io/en/v1.6.0/plugins/fetchart.html#image-resizing -, imagemagick -, gobject-introspection -, gst_all_1 -, runtimeShell - -# external plugins package set -, beetsExternalPlugins - -, enableAbsubmit ? lib.elem stdenv.hostPlatform.system essentia-extractor.meta.platforms, essentia-extractor -, enableAcousticbrainz ? true -, enableAcoustid ? true -, enableAura ? true -, enableBadfiles ? true, flac, mp3val -, enableBeatport ? true -, enableBpsync ? true -, enableConvert ? true, ffmpeg -, enableDeezer ? true -, enableDiscogs ? true -, enableEmbyupdate ? true -, enableFetchart ? true -, enableKeyfinder ? true, keyfinder-cli -, enableKodiupdate ? true -, enableLastfm ? true -, enableLoadext ? true -, enableLyrics ? true -, enableMpd ? true -, enablePlaylist ? true -, enableReplaygain ? true -, enableSonosUpdate ? true -, enableSubsonicplaylist ? true -, enableSubsonicupdate ? true -, enableThumbnails ? true -, enableWeb ? true - -# External plugins -, enableAlternatives ? false -, enableCopyArtifacts ? false -, enableExtraFiles ? false - -, bashInteractive, bash-completion }: -assert enableBpsync -> enableBeatport; - -let - optionalPlugins = { - absubmit = enableAbsubmit; - acousticbrainz = enableAcousticbrainz; - aura = enableAura; - badfiles = enableBadfiles; - beatport = enableBeatport; - bpsync = enableBpsync; - chroma = enableAcoustid; - convert = enableConvert; - deezer = enableDeezer; - discogs = enableDiscogs; - embyupdate = enableEmbyupdate; - fetchart = enableFetchart; - keyfinder = enableKeyfinder; - kodiupdate = enableKodiupdate; - lastgenre = enableLastfm; - lastimport = enableLastfm; - loadext = enableLoadext; - lyrics = enableLyrics; - mpdstats = enableMpd; - mpdupdate = enableMpd; - playlist = enablePlaylist; - replaygain = enableReplaygain; - sonosupdate = enableSonosUpdate; - subsonicplaylist = enableSubsonicplaylist; - subsonicupdate = enableSubsonicupdate; - thumbnails = enableThumbnails; - web = enableWeb; - }; - - pluginsWithoutDeps = [ - "albumtypes" "bareasc" "bench" "bpd" "bpm" "bucket" "duplicates" "edit" "embedart" - "export" "filefilter" "fish" "freedesktop" "fromfilename" "ftintitle" "fuzzy" - "hook" "ihate" "importadded" "importfeeds" "info" "inline" "ipfs" "gmusic" - "mbcollection" "mbsubmit" "mbsync" "metasync" "missing" "parentwork" "permissions" "play" - "plexupdate" "random" "rewrite" "scrub" "smartplaylist" "spotify" "the" - "types" "unimported" "zero" - ]; - - enabledOptionalPlugins = lib.attrNames (lib.filterAttrs (_: lib.id) optionalPlugins); - - allPlugins = pluginsWithoutDeps ++ lib.attrNames optionalPlugins; - allEnabledPlugins = pluginsWithoutDeps ++ enabledOptionalPlugins; - - testShell = "${bashInteractive}/bin/bash --norc"; - completion = "${bash-completion}/share/bash-completion/bash_completion"; - -in pythonPackages.buildPythonApplication rec { - pname = "beets"; - version = "1.6.0"; - - src = fetchFromGitHub { - owner = "beetbox"; - repo = "beets"; - rev = "v${version}"; - sha256 = "sha256-fT+rCJJQR7bdfAcmeFRaknmh4ZOP4RCx8MXpq7/D8tM="; +lib.makeExtensible (self: { + beets = self.beets-stable; + + beets-stable = callPackage ./common.nix rec { + version = "1.6.0"; + src = fetchFromGitHub { + owner = "beetbox"; + repo = "beets"; + rev = "v${version}"; + hash = "sha256-fT+rCJJQR7bdfAcmeFRaknmh4ZOP4RCx8MXpq7/D8tM="; + }; }; - propagatedBuildInputs = [ - pythonPackages.six - pythonPackages.enum34 - pythonPackages.jellyfish - pythonPackages.munkres - pythonPackages.musicbrainzngs - pythonPackages.mutagen - pythonPackages.pyyaml - pythonPackages.unidecode - pythonPackages.gst-python - pythonPackages.pygobject3 - pythonPackages.reflink - pythonPackages.confuse - pythonPackages.mediafile - gobject-introspection - ] ++ lib.optional enableAbsubmit essentia-extractor - ++ lib.optional enableAcoustid pythonPackages.pyacoustid - ++ lib.optional enableBeatport pythonPackages.requests-oauthlib - ++ lib.optional enableConvert ffmpeg - ++ lib.optional enableDiscogs pythonPackages.discogs-client - ++ lib.optional (enableFetchart - || enableDeezer - || enableEmbyupdate - || enableKodiupdate - || enableLoadext - || enablePlaylist - || enableSubsonicplaylist - || enableSubsonicupdate - || enableAcousticbrainz) pythonPackages.requests - ++ lib.optional enableKeyfinder keyfinder-cli - ++ lib.optional enableLastfm pythonPackages.pylast - ++ lib.optional enableLyrics pythonPackages.beautifulsoup4 - ++ lib.optional enableMpd pythonPackages.mpd2 - ++ lib.optional enableSonosUpdate pythonPackages.soco - ++ lib.optional enableThumbnails pythonPackages.pyxdg - ++ lib.optional (enableAura - || enableWeb) pythonPackages.flask - ++ lib.optional enableAlternatives beetsExternalPlugins.alternatives - ++ lib.optional enableCopyArtifacts beetsExternalPlugins.copyartifacts - ++ lib.optional enableExtraFiles beetsExternalPlugins.extrafiles - ; - - buildInputs = [ - ] ++ (with gst_all_1; [ - gst-plugins-base - gst-plugins-good - gst-plugins-ugly - ]); - - checkInputs = with pythonPackages; [ - beautifulsoup4 - mock - nose - rarfile - responses - # Although considered as plugin dependencies, they are needed for the - # tests, for disabling them via an override makes the build fail. see: - # https://github.com/beetbox/beets/blob/v1.6.0/setup.py - pylast - mpd2 - discogs-client - pyxdg - ]; - - patches = [ - # Bash completion fix for Nix - ./bash-completion-always-print.patch - ] - # Fix path to imagemagick, used for the artresizer.py file. This reference - # to imagemagick might be expensive for some people, so the patch can be - # disabled if imagemagick is set to null - ++ lib.optional (imagemagick != null) (substituteAll { - src = ./imagemagick-nix-path.patch; - inherit imagemagick; - }) - # We need to force ffmpeg as the default, since we do not package - # bs1770gain, and set the absolute path there, to avoid impurities. - ++ lib.optional enableReplaygain (substituteAll { - src = ./replaygain-default-ffmpeg.patch; - ffmpeg = lib.getBin ffmpeg; - }) - # Put absolute Nix paths in place - ++ lib.optional enableConvert (substituteAll { - src = ./convert-plugin-ffmpeg-path.patch; - ffmpeg = lib.getBin ffmpeg; - }) - ++ lib.optional enableBadfiles (substituteAll { - src = ./badfiles-plugin-nix-paths.patch; - inherit mp3val flac; - }) - ; - - # Disable failing tests - postPatch = '' - echo echo completion tests passed > test/rsrc/test_completion.sh - - # https://github.com/beetbox/beets/issues/1187 - sed -i -e 's/len(mf.images)/0/' test/test_zero.py - ''; - - postInstall = '' - mkdir -p $out/share/zsh/site-functions - cp extra/_beet $out/share/zsh/site-functions/ - ''; - - doCheck = true; - - preCheck = '' - 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 allPlugins "plugins_available"} - ''; - - checkPhase = '' - runHook preCheck - - LANG=en_US.UTF-8 \ - LOCALE_ARCHIVE=${assert stdenv.isLinux; glibcLocales}/lib/locale/locale-archive \ - BEETS_TEST_SHELL="${testShell}" \ - BASH_COMPLETION_SCRIPT="${completion}" \ - HOME="$(mktemp -d)" nosetests -v - - runHook postCheck - ''; - - doInstallCheck = true; - - installCheckPhase = '' - runHook preInstallCheck - - tmphome="$(mktemp -d)" - - EDITOR="${writeScript "beetconfig.sh" '' - #!${runtimeShell} - cat > "$1" <