354 lines
10 KiB
Org Mode
354 lines
10 KiB
Org Mode
:PROPERTIES:
|
||
:ID: cce/beets
|
||
:ROAM_ALIASES: beets
|
||
:END:
|
||
#+TITLE: Music Library Management with beets
|
||
#+ARCOLOGY_KEY: cce/beets
|
||
#+AUTO_TANGLE: t
|
||
#+ARROYO_MODULE_WANTS: cce/configure_packaging.org
|
||
#+ARCOLOGY_ALLOW_CRAWL: t
|
||
|
||
I do not like =beets=, but I like everything else less, especially manual [[id:3182614c-9122-4b01-ac3f-9f99e56bfdbd][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 [[id:cce/syncthing][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 [[id:cce/home-manager][home-manager]] with the =copyArtifacts= plugin.
|
||
|
||
#+begin_src nix :tangle ~/arroyo-nix/hm/beets.nix
|
||
{ 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 ];
|
||
}
|
||
#+end_src
|
||
|
||
#+ARROYO_HOME_MODULE: hm/beets.nix
|
||
#+ARROYO_SYSTEM_EXCLUDE: droid
|
||
|
||
* DONE check up on [[https://github.com/NixOS/nixpkgs/pull/172032#issuecomment-1137987507][nixpkgs]] comment about copyartifacts plugin
|
||
SCHEDULED: <2022-05-27 Fri>
|
||
:LOGBOOK:
|
||
- State "DONE" from "NEXT" [2022-06-11 Sat 18:23]
|
||
:END:
|
||
* INPROGRESS add [[https://github.com/snejus/beetcamp][beetcamp]] to package external plugins
|
||
:PROPERTIES:
|
||
:ID: 20220611T182415.660603
|
||
:ROAM_REFS: https://github.com/NixOS/nixpkgs/pull/176978
|
||
:ROAM_ALIASES: beetcamp
|
||
:END:
|
||
:LOGBOOK:
|
||
- Note taken on [2022-06-11 Sat 18:24] \\
|
||
https://github.com/NixOS/nixpkgs/pull/176978
|
||
- State "INPROGRESS" from "NEXT" [2022-06-11 Sat 18:24]
|
||
:END:
|
||
|
||
#+begin_src nix :tangle ~/arroyo-nix/pkgs/beetcamp.nix
|
||
{ 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 ];
|
||
};
|
||
}
|
||
#+end_src
|
||
|
||
* File Organization
|
||
:PROPERTIES:
|
||
:ID: music-file-organization
|
||
:ROAM_ALIASES: "Music File Organization"
|
||
:END:
|
||
|
||
I keep my music in =~/Music= and a few subdirectories underneath it so that i can use [[id:cce/syncthing][Syncthing]]'s filtering to narrow the [[id:3182614c-9122-4b01-ac3f-9f99e56bfdbd][Music]] i sync to devices with less storage like the [[id:cosmo_communicator][Cosmo Communicator]] or whatever.
|
||
|
||
- Albums I download go in to [[file:~/Music/New][New]], usually just unzipped and kept outside of beets.
|
||
- [[file:~/Music/High/][High]] contains music I want anywhere
|
||
- [[file:~/Music/Middle][Middle]] contains music that is interesting but I do not listen to often
|
||
- [[file:~/Music/Low][Low]] is where music I don't listen to or purchased without fully intending to listen to live
|
||
- [[file:~/Music/Archive][Archive]] is where music goes to die
|
||
- [[file:~/Music/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:
|
||
|
||
#+begin_src shell
|
||
~/.nix-profile/bin/beet -c ~/Music/beets/config.yaml import --set=class=high --write --resume $IMPORT_PATH
|
||
#+end_src
|
||
|
||
to modify existing and move them:
|
||
|
||
#+begin_src shell
|
||
~/.nix-profile/bin/beet -c ~/Music/beets/config.yaml modify class=low low:1
|
||
#+end_src
|
||
|
||
** interactive command for importing a zip file or glob of them in to beets from [[id:cce/dired_file_manager][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:
|
||
|
||
#+ARROYO_EMACS_MODULE: beets
|
||
|
||
#+begin_src emacs-lisp :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*")))
|
||
#+end_src
|
||
|
||
#+begin_src shell :tangle beets_unzip.sh :shebang #!/bin/env sh
|
||
TMPD=$(mktemp -d /tmp/bandcampXXXX)
|
||
cd $TMPD
|
||
unzip "$1" &>/tmp/bandcamp_unzip.log
|
||
echo $TMPD
|
||
#+end_src
|
||
|
||
** NEXT tag out "night music", ambient sleep stuff and code drone, music to sing along
|
||
|
||
* Beets Configuration
|
||
|
||
#+begin_src yaml :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
|
||
#+end_src
|
||
|
||
The class field describes the [[id:music-file-organization][file organization hierarchy]] and is used in the path selection per the [[https://beets.readthedocs.io/en/stable/guides/advanced.html#choose-a-path-style-manually-for-some-music]["Advanced Awesomeness"]] documentation to achieve this.
|
||
|
||
#+begin_src yaml :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}
|
||
#+end_src
|
||
|
||
I use =copyartifacts= to copy album art, pdfs, etc
|
||
|
||
#+begin_src yaml :tangle ~/Music/beets/config.yaml
|
||
copyartifacts:
|
||
extensions: .*
|
||
#+end_src
|
||
|
||
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:
|
||
|
||
#+begin_src yaml :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
|
||
#+end_src
|
||
|
||
I use [[id:20220611T182415.660603][=beetcamp=]]:
|
||
|
||
#+begin_src yaml :tangle ~/Music/beets/config.yaml
|
||
bandcamp:
|
||
search_max: 5
|
||
art: yes
|
||
genre:
|
||
capitalize: no
|
||
mode: progressive
|
||
#+end_src
|
||
|
||
Fetch genres from Last.FM:
|
||
|
||
#+begin_src yaml :tangle ~/Music/beets/config.yaml
|
||
lastgenre:
|
||
auto: yes
|
||
source: album
|
||
#+end_src
|
||
|
||
Embed album art in the files
|
||
|
||
#+begin_src yaml :tangle ~/Music/beets/config.yaml
|
||
embedart:
|
||
auto: yes
|
||
remove_art_file: yes
|
||
#+end_src
|
||
|
||
Fetch art where possible
|
||
|
||
#+begin_src yaml :tangle ~/Music/beets/config.yaml
|
||
fetchart:
|
||
auto: yes
|
||
sources: "*"
|
||
store_source: yes
|
||
#+end_src
|
||
|
||
Enable and disable various other plugins:
|
||
|
||
#+begin_src yaml :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
|
||
#+end_src
|
||
|
||
** Smart Playlists
|
||
|
||
run [[shell:beet -c ~/Music/beets/config.yaml splupdate &]] (make sure to tangle this if you change it!):
|
||
|
||
#+begin_src yaml :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"
|
||
#+end_src
|