I do not like beets
, but I like
everything else less, especially manual Music metadata
management. Because I must, my setup is a bit non-standard, I do some
hackery with paths and tags in beets to organize my music so that Syncthing can more easily sync important
parts to my devices rather than fucking around with generating
file-exclusion lists and such.
I install it in home-manager with
the copyArtifacts
plugin.
{ pkgs, config, ... }:
let
myBeets = with pkgs; beets.override {
pluginOverrides = {
beetcamp = {
propagatedBuildInputs = [ pkgs.beetcamp ];
};
copyartifacts = {
propagatedBuildInputs = [ beetsPackages.copyartifacts ];
};
};
};
in {
home.activation.beets-config =
pkgs.lib.mkActivationLocalLink config"~/Music/beets/config.yaml"
".config/beets/config.yaml";
# home.packages = [ myBeets ];
home.packages = [ myBeets pkgs.beetcamp ];
}
DONE check up on nixpkgs comment about copyartifacts plugin
INPROGRESS add beetcamp to package external plugins
{ lib,
fetchFromGitHub,
python3Packages,
callPackage,
beets,
propagateBeets ? false
}:
{
python3Packages.buildPythonPackage pname = "beets-beetcamp";
version = lib.pkgVersions.beetcamp.version;
src = callPackage lib.pkgVersions.beetcamp.src {};
format = "pyproject";
propagatedBuildInputs = with python3Packages; [ setuptools poetry-core requests cached-property pycountry python-dateutil ordered-set ]
++ (lib.optional propagateBeets [ beets ]);
postInstall = ''
rm $out/lib/python*/site-packages/LICENSE
mkdir -p $out/share/doc/beetcamp
mv $out/lib/python*/site-packages/README.md $out/share/doc/beetcamp/README.md
'';
checkInputs = with python3Packages; [
# pytestCheckHook
pytest-cov
pytest-randomly
pytest-lazy-fixture
rich
tox
types-setuptools
types-requests] ++ [
beets];
meta = {
homepage = "https://github.com/snejus/beetcamp";
description = "Bandcamp autotagger plugin for beets.";
license = lib.licenses.gpl2;
inherit (beets.meta) platforms;
maintainers = with lib.maintainers; [ rrix ];
};
}
File Organization
I keep my music in ~/Music
and a few
subdirectories underneath it so that i can use Syncthing's filtering to narrow the Music i sync to
devices with less storage like the Cosmo
Communicator or whatever.
- Albums I download go in to New, usually just unzipped and kept outside of beets.
- High contains music I want anywhere
- Middle contains music that is interesting but I do not listen to often
- Low is where music I don't listen to or purchased without fully intending to listen to live
- Archive is where music goes to die
- Archive Old contains low quality rips and music I listened to when I was much younger.
To import an album in a particular class collection:
~/.nix-profile/bin/beet -c ~/Music/beets/config.yaml import --set=class=high --write --resume $IMPORT_PATH
to modify existing and move them:
~/.nix-profile/bin/beet -c ~/Music/beets/config.yaml modify class=low low:1
interactive command for importing a zip file or glob of them in to beets from Dired File Manager
I hate using unzip
, such terrible
ergonomics. I have this tiny helper shell-script to unzip to /tmp
and then I wrap it in an interactive
command elisp:(cce/import-to-beets) to
make it most easy:
defvar beet-command "~/.nix-profile/bin/beet -c ~/Music/beets/config.yaml import --write --resume")
(defun cce/import-to-beets (file class)
("Import a Bandcamp zip FILE in to a CLASS in my Music folder."
"ffile or directory to import? \nMimport-class? ")
(interactive let ((directory
(if (s-ends-with? "zip" file)
(progn
(format "bash ~/org/cce/beets_unzip.sh \"%s\"" (expand-file-name file)) "*beets_unzip*" "*beets_unzip_error*")
(shell-command (if (and (get-buffer "*beets_unzip_error*") (buffer-size (get-buffer "*beets_unzip_error*")))
(error "zip script failed")
("*beets_unzip*")
(with-current-buffer (get-buffer
(buffer-string))))
file)))
(async-shell-commandformat "%s --set=class=%s \"%s\""
(class
beet-command directory)
"*beets-import*")))
TMPD=$(mktemp -d /tmp/bandcampXXXX)
cd $TMPD
unzip "$1" &>/tmp/bandcamp_unzip.log
echo $TMPD
NEXT tag out "night music", ambient sleep stuff and code drone, music to sing along
Beets Configuration
plugins: fetchart embedart convert scrub replaygain lastgenre web fetchart smartplaylist copyartifacts discogs bandcamp
directory: /home/rrix/Music
library: /home/rrix/Music/beets/musiclibrary.blb
art_filename: albumart
threaded: yes
original_date: no
per_disc_numbering: no
The class field describes the file organization hierarchy and is used in the path selection per the "Advanced Awesomeness" documentation to achieve this.
paths:
default: New/%asciify{$albumartist}/%asciify{$album}%aunique{}/$track - %asciify{$title}
class:high: High/%asciify{$albumartist}/%asciify{$album}%aunique{}/$track - %asciify{$title}
class:middle: Middle/%asciify{$albumartist}/%asciify{$album}%aunique{}/$track - %asciify{$title}
class:low: Low/%asciify{$albumartist}/%asciify{$album}%aunique{}/$track - %asciify{$title}
class:archive: Archive/%asciify{$albumartist}/%asciify{$album}%aunique{}/$track - %asciify{$title}
I use copyartifacts
to copy album art,
pdfs, etc
copyartifacts:
extensions: .*
import configuration is pretty simple, geared towards assuming the files are fungible, and indeed they are since i'll probably be running imports on my laptop from bandcamp downloads:
import:
languages: en ja
write: yes
copy: no
move: yes
resume: ask
incremental: yes
quiet_fallback: skip
timid: no
log: /home/rrix/Music/beets/beet.log
I use beetcamp
:
bandcamp:
search_max: 5
art: yes
genre:
capitalize: no
mode: progressive
Fetch genres from Last.FM:
lastgenre:
auto: yes
source: album
Embed album art in the files
embedart:
auto: yes
remove_art_file: yes
Fetch art where possible
fetchart:
auto: yes
sources: "*"
store_source: yes
Enable and disable various other plugins:
chrome:
auto: yes
replaygain:
auto: no
scrub:
auto: yes
replace:
'^\.': _
'[\x00-\x1f]': _
'[<>:"\?\*\|]': _
'[\xE8-\xEB]': e
'[\xEC-\xEF]': i
'[\xE2-\xE6]': a
'[\xF2-\xF6]': o
'[\xF8]': o
'\.$': _
'\s+$': ''
web:
host: 0.0.0.0
port: 8337
reverse_proxy: true
acousticbrainz:
auto: yes
Smart Playlists
run shell:beet -c ~/Music/beets/config.yaml splupdate & (make sure to tangle this if you change it!):
smartplaylist:
relative_to: /home/rrix/Music
playlist_dir: /home/rrix/Music/playlists
playlists:
- name: upbeat.m3u
query:
- mood_happy::^0.99 ^genre::Rock ^artist::Vanilla ^artist:Bananarama ^album::Evangelion ^album::3.0 ^genre:Grunge ^class:archive
- artist:"Electric Six"
- danceable::^0.99999 ^genre::Rock ^artist::Vanilla ^artist:Bananarama ^album::Evangelion ^album::3.0 ^genre:Grunge ^class:archive
- name: chiptunes.m3u
query:
- artist:Anamanaguchi
- artist:"Bit Shifter"
- artist:Chipzel
- artist:chipzel
- artist::Demoscene
- album::Fez
- album::"Hyper Light Drifter"
- album::"Toffelskater"
- artist:Fantomenk
- artist:"Fighter X"
- artist:"PDF Format"
- artist:Sabrepulse
- artist:Spamtron
- artist:spamtron
- genre:8-bit
- genre:Chiptunes
- name: j-pop.m3u
query:
- album::[Gg][Uu][Nn][Dd][Aa][Mm]
- album::ガンダムユニコ
- artist::まりや
- artist:"Maria Asahina"
- artist:"松原みき"
- album::NieR
- genre:J-pop
- genre:J-Rock
- name: various-and-sundry.m3u
query:
- album:"Birdy Nam Nam"
- artist::Flashbulb
- genre:vaporwave
- genre:Ambient
- artist:'Janel & Anthony'
- artist:'Anthony Pirog'
- name: music-for-sleep.m3u
query:
- album:CATCH‐Wave
- album::Disassembly
- artist:"Music For Sleep"
- album:"Glider 10"
- album:Liminal
- album:"Solar Fields"
- artist:"Matt Borghi"
- artist:"Umber"
- artist:"the volume settings folder"
- artist:"Brian Eno"