Working With Music As A Programmer
- Published on
- Authors
Selecting Specific Music
As a music enthusiast and a programmer, I often find myself wanting more control over how I manage and play my music. I have a large collection of songs and often there's a specific set of songs I want to play. For example, maybe I want to play all of Kendrick Lamar's songs with titles that contain the word "the," or perhaps I want to play just the songs "A Pearl" and "Jobless Monday" by Mitski. This can be done by manually selecting the songs through a graphic music player like VLC or by a terminal music player like ncmpcpp, but I wanted a more efficient way to do this. I wanted to be able to essentially query exactly what songs I wanted to play in one line, which led me to create a command line tool to do just that and much more.
In the examples above, we can query Kendrick Lamar's songs that have the word "the" by running:
music play "kendrick#lamar#the"
And we can query Mitski's two songs by running:
music play "mitski#a pearl,jobless monday"
How this program works is by looking through your music directory and finding any files with names that match the query. The actual text that is being matched is the relative path to the file from your music directory. For example if your music directory was ~/Music
and you had a song whose path is ~/Music/Kendrick Lamar/To Pimp a Butterfly/01 Wesley's Theory.mp3,
then the text that would be matched against is kendrick lamar/to pimp a butterfly/01 wesley's theory.mp3.
There are a couple of things to note now that I mention this:
- This program does not support piracy, you should only use this program with music that you own.
- This program is not a music player, after finding matching songs, it plays them with VLC
- Filenames are case-insensitive when being tested against the query.
- This program does not use the metadata of the music files, it only uses the filenames. This approach is faster and easier to query, so I recommend you keep your filenames well maintained.
The querying system is pretty simple. Every command line argument after music play
is considered a term. A song has to match at least one term and can not match any negation term. A term can have required sections and one-of sections, specified with "#" and "," respectively.
music play tonight "monday#mornings" care,bear,say "make#you,me#believe" \!joe
This command has the following five terms:
tonight
(song has to have the word "tonight" somewhere in the path)monday#mornings
(song has to have both words "monday" and "mornings" (not necessarily next to each other))care,bear,say
(song has to have one or more of "care", "bear", "say")make#you,me#believe
(song has to have "make", either "you" or "me", and "believe")\!joe
(the song can't have the term, notice the escaping of "!")
A song has to match any of the first four terms and can't match the last term. When combining these terms, the string is split by #
first, and then ,
.
Live Querying
I also wanted to be able to see the results of my query in real-time. This way I wouldn't have to waste as much time figuring out what query works best and having it fail. To activate this feature, simply run music play --live
. I personally have this command set to a keybinding in my shell (ctrl+Alt+m
).
Search: mac#miller \!lim
───────────────[11]─────────────── - Mac Miller/100 Grandkids.m4a - Mac Miller/Back In The Day.m4a - Mac Miller/Blue World.m4a - Mac Miller/Everybody.m4a - Mac Miller/Good News.m4a - Mac Miller/Jet Fuel.m4a - Mac Miller/Ladders.m4a - Mac Miller/Objects in the Mirror.m4a - Mac Miller/Perfect Circle.m4a - Mac Miller/Self Care.m4a - Mac Miller/Surf.m4a
However, having to spawn a terminal just to run this command was getting a bit annoying so I made it so whenever I pressed meta+m+x
it spawns a floating one-use terminal in the center of my screen with the live feature. This is the command that gets executed:
bindsym $mod+m mode "music"
mode "music" {
bindsym x exec alacritty --class floating -o window.padding.x=10 -o window.padding.y=10 -e music play --live; mode "default"
# ...
}
Note: in my i3 config I have it set so programs with the class of "floating" are floating.
Tags
It's pretty reasonable to want to group together a set of songs, whether it be due to the artist's intent, the genre, or your own reasons. To solve this problem this program has what are called tags (or playlists).
Each tag is stored at $MUSIC_PATH/tags/[tagName].m3u
as a m3u file. You can edit the songs in these tags through the program itself, or you can simply edit the actual file. One benefit of this approach is that adding songs to a tag (or playlist) is much easier. You can simply run your query with music play [query...]
and then use --add-to-tag | -a <tag>
to add it to that tag or --set-to-tag | -s <tag>
to set a tag to only those songs. This does have the side effect of playing the songs in VLC still, but you can stop that with --dry-run
.
Playlist Integration With Spotify
Usually if I want to create a playlist I make it on Spotify first and then import it to my local files. This way my playlist is online and can be shared with anyone. To help automate the process once I created the Spotify playlist I made another command to help import it to tags: music spotify import <tag> <spotify playlist/album url>
After running the command the program will try to match songs from your local library to the songs on the Spotify playlist. This process uses the files' metadata rather than the files' filename. It's possible that despite having the song in your local library, it will not be matched due to the difference in metadata (say a song from the playlist is not uploaded by the original artist and instead by some fan). However, it does a pretty good job, assuming your local library has metadata embedded. As you might have noticed, you can also import spotify albums.
This does require you to create a Spotify developer account for API credentials.
Last.fm Scrobbling
When you listen to a decent amount of music, it's nice to look back and see how you've changed over time or see any trends that follows. One way to record this is with Last.fm.
While VLC does have built in Last.fm scrobbling, I could not get it to work for the longest time. However, I still experience certain difficulties with VLC's integrated scrobbling so I continued to build my own. You can run a watch server to watch for playing songs every x seconds (defaulted to 10). It uses playerctl (MPRIS) under the hood, so you will need to have that installed and be on linux (in the future I could add vlc tcp support so that non-linux users could also use). It also only checks for the VLC player so other media players or something like Youtube will not be logged (there's an extension for that). An alternative would be multi-scrobbler which supports a lot more sources, and has a lot more functionality overall.
The scrobble detection follows the approach of minimizing false positives, so if you skip/seek around, it likely won't scrobble. Once VLC goes onto a new track (or the song loops over) then it will scrobble the song if these two requirements are met:
- The track must be longer than 30 seconds.
- The track has been played for at least half its duration, or for 4 minutes (whichever occurs earlier).
You will need to create a Last.fm developer account for API credentials.
Last.fm Suggestions
One downside of playing music locally rather than on a music platform like Apple Music or Spotify is that you can't explore new songs as much. Last.fm does have an API for suggestions based off your listening, but it is a bit worse than the recommendations of Spotify. Regardless, you can also get Last.fm suggestions (on any OS, without authentication) with the suggest command.
music lastfm suggest username --limit 20
Usage on Android
I also wanted to be able to query my music on my phone. Luckily, this is possible with Termux. You can install this program on Android with Termux and then use it like normal. However, I would not recommend using the program straight up. You can technically install VLC on Termux and use music play
like normal, but I would not recommend doing so because Termux will hang, and you won't have VLC in your notification tray.
Instead, I would recommend creating a helper script that uses the query feature of music play
but does not actually play the songs (music play [query...] --dry-run
) and then sets it to a tag (--set-to-tag last-query
).
You can run the helper script (let's say it's called mx
) as so: mx jaxson#tonight
. Once you open up VLC, you will see your playlist.
Other Keybindings
Apart from the keybindings I mentioned earlier, I also have a couple of other keybindings that I use often. I first need to get into the music mode by pressing meta+m
. Then I can use the following keybindings:
h: previous song
j: switch players
k: pause/play
l: next song
c: kill vlc
?: shuffle
0: go to the start of the song
-: go back 10 seconds
+: go forward 10 seconds
shift+h: previous song (but don't exit music mode)
shift+l: next song (but don't exit music mode)
Here is the i3 config for these keybindings:
bindsym $mod+m mode "music"
mode "music" {
bindsym h exec $HOME/.local/bin/usr/handle-prev-next.sh "previous"; mode "default"
bindsym j exec $HOME/.local/bin/usr/handle-play-pause.sh "force-select"; mode "default"
bindsym k exec $HOME/.local/bin/usr/handle-play-pause.sh; mode "default"
bindsym l exec $HOME/.local/bin/usr/handle-prev-next.sh "next"; mode "default"
bindsym x exec alacritty --class floating -o window.padding.x=20 -o window.padding.y=20 -e music play --live; mode "default"
bindsym shift+c exec killall vlc; mode "default"
bindsym shift+/ exec playerctl shuffle
bindsym 0 exec playerctl position 0; mode "default"
bindsym minus exec playerctl position 10-
bindsym plus exec playerctl position 10+
bindsym shift+h exec $HOME/.local/bin/usr/handle-prev-next.sh "previous"
bindsym shift+l exec $HOME/.local/bin/usr/handle-prev-next.sh "next"
bindsym Return mode "default"
bindsym Escape mode "default"
bindsym $mod+x mode "default"
bindsym ctrl+braceleft mode "default"
}
Conclusion
I've worked on this program on and off for 2 years with different languages, and it's been an interesting ride. I've learned a lot more about music production, music tagging, and programming through this experience. This post is more so just a quick introduction to the program and how I made some of the features in it. If you are interested in using it, the full instructions will be in the github repository.