Multiple Beets libraries on one machine?
This article is about Beets - the best music library manager out there.
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:
- Oh My Zsh plugins
git
andvirtualenv
active with their default settings - Oh My Zsh pyenv plugin configured,
- Zsh Agnoster theme in use
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!