The Complete Computer

Music Library Management with beets

LifeTechEmacsArcology

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.

nix source: :tangle ~/arroyo-nix/hm/beets.nix
{ pkgs, config, ... }: let myBeets = pkgs.python3.pkgs.beets.override { pluginOverrides = { beetcamp = { propagatedBuildInputs = [ pkgs.beetcamp ]; }; # copyartifacts = { # propagatedBuildInputs = [ pkgs.python3.pkgs.beets-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

nix source: :tangle ~/arroyo-nix/pkgs/beetcamp.nix
{ lib, fetchFromGitHub, callPackage, buildPythonPackage, beets, setuptools, poetry-core, requests, cached-property, pycountry, python-dateutil, ordered-set, pytest-cov, pytest-randomly, pytest-lazy-fixture, rich, tox, types-setuptools, types-requests, propagateBeets ? false }: buildPythonPackage { pname = "beets-beetcamp"; version = lib.pkgVersions.beetcamp.version; src = callPackage lib.pkgVersions.beetcamp.src {}; format = "pyproject"; propagatedBuildInputs = [ 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 = [ # 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 New, usually just unzipped and kept outside of beets.

  • High High contains music I want anywhere

  • Middle Middle contains music that is interesting but I do not listen to often

  • Low Low is where music I don't listen to or purchased without fully intending to listen to live

  • Archive Archive is where music goes to die

  • Archive Old 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:

shell source: 
~/.nix-profile/bin/beet -c ~/Music/beets/config.yaml import --set=class=high --write --resume $IMPORT_PATH

to modify existing and move them:

shell source: 
~/.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:

emacs-lisp source: :tangle beets.el
(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." (interactive "ffile or directory to import? \nMimport-class? ") (let ((directory (if (s-ends-with? "zip" file) (progn (shell-command (format "bash ~/org/cce/beets_unzip.sh \"%s\"" (expand-file-name file)) "*beets_unzip*" "*beets_unzip_error*") (if (and (get-buffer "*beets_unzip_error*") (buffer-size (get-buffer "*beets_unzip_error*"))) (error "zip script failed") (with-current-buffer (get-buffer "*beets_unzip*") (buffer-string)))) file))) (async-shell-command (format "%s --set=class=%s \"%s\"" beet-command class directory) "*beets-import*")))
shell source: :tangle beets_unzip.sh :shebang #!/bin/env sh
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

yaml source: :tangle ~/Music/beets/config.yaml
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.

yaml source: :tangle ~/Music/beets/config.yaml
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

yaml source: :tangle ~/Music/beets/config.yaml
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:

yaml source: :tangle ~/Music/beets/config.yaml
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= :

yaml source: :tangle ~/Music/beets/config.yaml
bandcamp: search_max: 5 art: yes genre: capitalize: no mode: progressive

Fetch genres from Last.FM:

yaml source: :tangle ~/Music/beets/config.yaml
lastgenre: auto: yes source: album

Embed album art in the files

yaml source: :tangle ~/Music/beets/config.yaml
embedart: auto: yes remove_art_file: yes

Fetch art where possible

yaml source: :tangle ~/Music/beets/config.yaml
fetchart: auto: yes sources: "*" store_source: yes

Enable and disable various other plugins:

yaml source: :tangle ~/Music/beets/config.yaml
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!):

yaml source: :tangle ~/Music/beets/config.yaml
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"