fifteen eleven ninety

How I manage my music library with beets and cmus

As a self-hosted advocate I like to own music. While I also enjoy using spotify and youtube music, I usually prefer to download my fav’ music. With the help of yt-dlp my local collection already grew to an astonishing amount of tracks. Just in case you want to download all the liked music from youtube, there you go, it is as easy as…

yt-dlp -x --cookies-from-browser chrome \
       https://music.youtube.com/playlist?list=LM

That’s about the quick part. But once you’ve the files, the journey begins. All the meta data needs to be added first. Unless you’re ok with such a mess. The bigger it grows, the uglier it gets.

tmux

I use tmux as my terminal multiplexer. It allows me to create new windows when I need to. It is an essential part of my setup.

beets

More than a decade ago, I got to know about beets, the media library management system for obsessive music geeks. It felt great to use at first, but also took a lot of time to tag. That’s probably one of the reasons, why the developer also included an apology and a brief interlude:

I would like to sincerely apologize that the autotagger in beets is so fussy. It asks you a lot of complicated questions, insecurely asking that you verify nearly every assumption it makes. This means importing and correcting the tags for a large library can be an endless, tedious process. I’m sorry for this.

Several years have passed by, I already quit beets. I’m not that obsessed with my very own music collection, I never have been. Spotify and Youtube Music made everything easier. Yet I’m here to think about. The world has changed. Things have got more expensive, content just disappeared. It’s the economy, stupid!

However, what’s mine is mine. Totally. What’s more, I actually started listening to music much more than I used to. I’ve discovered a bunch of different artists I got very obsessed about. Listening to you, dear Phil Collins. And so does the developer of beets when he shares his view on managing his very own music library:

Maybe it will help to think of it as a tradeoff. By carefully examining every album you own, you get to become more familiar with your library, its extent, its variation, and its quirks. People used to spend hours lovingly sorting and resorting their shelves of LPs. In the iTunes age, many of us toss our music into a heap and forget about it. This is great for some people. But there’s value in intimate, complete familiarity with your collection. So instead of a chore, try thinking of correcting tags as quality time with your music collection. That’s what I do.

I’m with him. It definitely adds some value. While I’m listening to and spend quality time with the music I like, tagging it properly feels more of a tradeoff. The thing with beets is, I have to query the title first I’d like to edit. It always felt very cumbersome to do so. This is especially true when you’ve a lot of singleton tracks like me. The vast majority of my collection are songs, which don’t belong to any album at all.

cmus

What if I told you, there is a faster and more convenient way? Instead of doing the query on the command line, let’s just operate on the song I’m currently listening to. My music player of choice, cmus, comes with a builtin cmus-remote control. This way, I’m able to automate a lot of things. So let’s take a look at my cmus config first:

  bind -f common o shell cmus-open
  bind -f common e shell cmus-edit
  bind -f common E shell cmus-edit --all
  bind -f common D shell cmus-delete

  update-cache
  add ~/lib/music/

Just a bunch of keybindings. Every time I start cmus, it will update the cache (which removes missing tracks) and adds new music to the library. A feature that has been requested many times.

Whenever I want to add some music, I just download it with yt-dlp. I usually don’t have time to tag them immediately. Sometimes I do, sometimes I don’t.

alias bis="beet import -s"
alias bias="beet import -As"

Open the current track on youtube

Most of the time I like to tweak the meta data to my very own likings. It is helpful to see the source video then. And that’s what this script does. It looks up the comment for an url, opens it in the browser, waits a few seconds and pauses cmus (that’s the time it usually takes on my computer at home to open the video on youtube and skip the ads). What’s more, it starts the video at the same position I’m currently at. A very kind way to continue where I left off!

#!/bin/bash

metadata=$(cmus-remote --query)

position=$(echo "$metadata" | grep position | awk '{print $2}')
comment=$(echo "$metadata" | grep comment | awk '{print $3}')

if [[ $comment =~ ^https?:// ]]; then
    url="$comment&t=$((position + 5))"

    cmus-remote --raw "echo open $url"
    gio open $url

    sleep 5

    cmus-remote --pause
else
    cmus-remote --raw "echo No url in comment found"
fi

Delete the current track

Next, I also have a script to delete the current track and remove it from the current view. There is not much going on here.

#!/bin/bash

metadata=$(cmus-remote --query)
file="$(echo "$metadata" | grep file | awk '{print $1=""; print $0}')"

cmus-remote --raw "win-sel-cur"
cmus-remote --raw "echo beet rm -d {}"
cmus-remote --raw "win-remove"

kgx -e beet rm -d -f path:$file

Edit the metadata of current track

This script will pass the currently playing track to beet edit, a plugin that let’s you modify metadata using your favorite $EDITOR.

#!/bin/bash

metadata=$(cmus-remote --query)
file="$(echo "$metadata" | grep file | awk '{print $1=""; print $0}')"

cmus-remote --raw "win-sel-cur"
cmus-remote --raw "echo beet edit {}"

tmux new-window -- beet edit $@ $file

Hook into beets

Once a track has got edited, it usually also changes its path on disk. And cmus needs to get to know about it. Thanks to beets, there are hooks that will trigger, once an item has moved.

plugins:
  - hook
hook:
  hooks:
    - event: item_moved
      command: cmus-continue

Continue where we left off

It will use a filter to match the new filename, start playing it but also seek to the position where we left off. Next, it will clear the filter and select the currently playing track. Very convenient, isn’t it?

#!/bin/bash

cmus-remote --raw "update-cache"
cmus-remote -l ~/lib/music

file="$(find ~/lib/music/ -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -d' ' -f2-)"
position="$(cmus-remote --query | grep position | awk '{print $2}')"

cmus-remote --raw "filter filename=\"$file\""
cmus-remote --raw "win-activate"
cmus-remote --seek $position
cmus-remote --raw "filter"
cmus-remote --raw "win-sel-cur"

Fuzzy find artists

When it comes to editing metadata, it is quite helpful to replace the artist. I’ve a lot of songs with nearly the same artist. The emphasis is on nearly. I usually don’t want to end up with different spellings. This is what this vim function is for. It uses fzf to query all the artists in my library.

nnoremap <leader>a :call ReplaceArtists()<CR>
function! ReplaceArtists()
  let artist_lines = []
  let artist_names = []

  for lnum in range(1, line('$'))
    let line_text = getline(lnum)
    if line_text =~ '^artist: '
      call add(artist_lines, lnum)
      let artist = substitute(matchstr(line_text, '^artist: \zs.*'), '"\|''', '', 'g')
      call add(artist_names, artist)
    endif
  endfor

  if empty(artist_lines)
    echo "No artist lines found"
    return
  endif

  let result = system('beet-fzf --artist')
  let result = substitute(result, '\n\+$', '', '') " Trim trailing newlines

  for lnum in artist_lines
    call setline(lnum, result)
  endfor

  redraw!
endfunction

Query all the artists

#!/bin/bash

if [[ "$1" == "--artist" ]]; then
	echo -n "artist: '"
	beet ls -f '$artist' | sort -u | fzf --reverse --print-query --query "$2" --bind 'ctrl-k:kill-line' | tail -n1 | tr -d '\n'
	echo -n "'"
fi