Multiple Beets libraries on one machine?

This article is about Beets - the best music library manager out there.

the Beets beetroot logo

If you are a music collector who uses command-line tools but haven’t discovered it yet, watch its demo video and read the Getting Started Guide.

Soon after I started contributing to the Beets project, I wanted to manage multiple Beets libraries installed within a single user account on my macOS and Linux machines. The initial goal was to find a way to quickly switch between testing new code or config settings and working in my main library. Later on I realized the setup’s potential to also make life more comfortable when splitting different content types to separate libraries.

 

This is not a beginners guide. It describes an opinionated setup for Beets developers and advanced users.

Why more than one?

Currently I run 3 Beets installations:

  • prod -> my main Beets library
  • dev -> a development setup I use to play around with new features, test unmerged pull requests, get crazy with the config
  • book -> a separate library I use for audio books about language training, music education practice tracks and actual audio books. The reason I keep this separate is to prevent mixing up music search results (including smartplaylists) with any non-music content.

Prerequisites

This tutorial assumes the following tools installed and set up according to their original documentation:

Beets is assumed to be installed from Git and Python running within a pyenv controlled virtual environment,

and these Zsh related things should be set up:

Where to put configuration files?

The default location for the Beets config.yaml file is ~/.config/beets. We are following this idea and use subdirectories of ~/.config/ for all of our three libraries:

  • ~/.config/beets/config.yaml for prod,
  • ~/.config/devbeets/config.yaml for dev,
  • and ~/.config/bookbeets/config.yaml for the book library.

Custom Zsh prompt

All of the following setup happens within the Zsh configuration file, usually ~/.zshrc

$BEETSDIR

As an alternative to specifiying a Beets config file via the -c option the $BEETSDIR environment variable comes in handy.

pyenv

Your pyenv initialization lines might be slightly different, but generally look similar to this:

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

It’s important that the Oh My Zsh pyenv plugin is loaded after pyenv has been initialized, so let’s put this line right below:

plugins+=(pyenv)

Agnoster theme prompt functions

Ok, so our first goal is to see at all times which Beets library and Python environment is currently active:

To stick together the parts (called “segments”) of the Zsh prompt, the Agnoster theme uses the function build_prompt which is defined in ~/.oh-my-zsh/themes/agnoster.zsh-theme. The original version looks like this. We simply redefine the function near the end of our ~/.zshrc while adding a new line to it:

build_prompt () {
        RETVAL=$?
        prompt_status
        prompt_beets  # This is what we add
        prompt_virtualenv
        prompt_context
        prompt_dir
        prompt_git
        prompt_bzr
        prompt_hg
        prompt_end
}

We also define a new function named prompt_beets:

prompt_beets () {
        local beetsdir="$BEETSDIR"
        if [[ "$beetsdir" =~ ".*/beets$" ]]; then
                prompt_segment 0 1 "prod"
        elif [[ "$beetsdir" =~ ".*/devbeets$" ]]; then
                prompt_segment 0 2 "dev"
        elif [[ "$beetsdir" =~ ".*/bookbeets$" ]]; then
                prompt_segment 0 2 "books"
        elif [[ -z $beetsdir ]]  && \
             [[ -z "$PYENV_VERSION" ]] && \
             [[ -z "$VIRTUAL_ENV" ]]; then
                prompt_segment 1 0 "sys-py"
        fi
}

What this does is look up what config file is currently active according to $BEETSDIR and return a snippet defining how the prompt segment should look like. Play around with the two digits after prompt_segment to set fore- and background colors of the string displayed!

So for example if $BEETSDIR is set to ~/.config/devbeets we would get a prompt like this:

As a nice additional feature, which is not only Beets-related, I personally like to have a fallback that tells me that I’m not in any virtual environment and the system’s Python installation is active. 1 0 here means an alerting red background and white as the foreground color. We’ll find out how that looks like further below. No spoilers now :-P

Customizing how the virtualenv plugin alters the prompt

Additionally we are changing the original format of the segment generated by the Oh My Zsh virtualenv plugin. As we saw in the screenshot above it uses parentheses around the environment name and the same background color as the prompt. We want to remove parentheses and change colors:

We achieve this by overwriting the original prompt_virtualenv() function which is defined here in the original code of the Agnoster theme. We can use literal color names too:

prompt_virtualenv() {
  if [[ -n "$VIRTUAL_ENV" && -n "$VIRTUAL_ENV_DISABLE_PROMPT" ]]; then
      prompt_segment white black "${VIRTUAL_ENV:t:gs/%/%%}"
  fi
}

Shell aliases for switching the active library

We add an alias for each library in our .zshrc. If desired at this point we could also activate a specific virtual Python environment simultaneously using the pyenv activate command. Another convenience feature I like is to directly jump to Beets source code.

bdev="export BEETSDIR=~/.config/devbeets; pyenv activate beets311; cd ~/git/beet"
bprod="export BEETSDIR=~/.config/beets; pyenv activate beets310; cd ~/git/beets"
bbook="export BEETSDIR=~/.config/bookbeets; pyenv activate beets311; cd ~/git/beet"

We saw the dev environment prompt above already - this is what we get if we now switch with bprod or bbook:

To quit working/using Beets I also define a quit alias - actually I use it to quit any pyenv controlled environment and the b-prefix is not perfectly appropriate - but who cares?

bquit="unset BEETSDIR; pyenv deactivate"

So since bquit deactivates any Python virtual environment we’ll fall back to alerting sys-py prompt we talked above:

I hope this is of use to fellow Beeters. Please tell me what you think! I’m very interested in your own ideas of handling multiple Beets environments and how this setup could be advanced further!

Article published Oct 20, 2024 , Project time around May 2022. jump to top