(λ (x) (create x) '(knowledge))

Random Script: mkplaylist.sh Part 2

Genre playlists thanks to recutils ยท January 20th, 2023

13:46 <~mio> neat music playlist script by the way :)
13:46 <~mio> on your blog post
13:46 <~mio> I hadn't thought of generating playlists on the fly like that

Thanks for the praise mio! And in return for it, I've extended mkplaylist to generate genre based playlists based off of a rec file! Like much of my music organization this is a semi manual, semi automated process, which seems to work well. I'm way too lazy to actually curate my playlists out side of very large overarching themes (ie: arists, or genre), hence all of the generation.

When I add a new artist to the mix, or an artist release a new album, I typically regenerate these playlists to include that content. So this is a really simple, almost natural extension of what I was already doing. And I really appreciate the idea!

Lets jump right into things, here's v2 of the wonderful funderful mkplaylist script, I've sort of lazily broken it into functions so I could entirely avoid implementing some sort of flag parsing system a la case. Instead I've tried to preserve the no argument invocation for most functionality.

#!/bin/bash
#Generate a playlist of all songs from the CWD
genre=$1
cwd=$(pwd)

mkplaylist(){
	#If we're in the root music directory, make a mix tape!
	if ["$cwd" == "/home/durrendal/Documents/Music"]; then
		find $cwd/* -name '*.opus' > mixtape.m3u
	else
		artist=$(basename $cwd)
		
		#Delete conflicting playlists before generating
		if [-f "$artist.m3u"]; then
			rm $artist.m3u
		fi
		
		touch $artists.m3u
		
		#For each album the artists has
		for album in $(ls -d */); do
			#Gather up all the opus tracks, and create a playlist
			for track in $album*.opus; do
				printf "$cwd/$track\n" >> $artist.m3u
			done
		done
	fi
}

mkgenreplaylist() {
	if [-z $genre]; then
		echo "Cannot generate a genre playlist without a genre!"
		exit 1
	fi
		
	if ["$cwd" != "/home/durrendal/Documents/Music"]; then
		echo "Cannot generate genre playlist outside of the root music directory!"
		exit 1
	fi

	if [-z "1_Playlists/Genres.rec"]; then
		printf "Where the hell did the database go?!
Fix it by creating a file name Genres.rec and adding records like:
Artist: Someone
Genre: Something
Genre: AnotherSomething
"
		exit 1
	fi		

	#Strip new lines from recsel output
	artists=$(recsel -P Artist -e 'Genre = "'$genre'"' 1_Playlists/Genres.rec | sed 's\/n\\g')

	#Delete conflicting playlists before generating
	if [-f 1_Playlists/$genre.m3u]; then
		rm 1_Playlists/$genre.m3u
	fi

	#For each artist of a given genre
	for artist in $artists; do
		#For each album the artists has
		for album in $(ls -d $artist/*/); do
			#Gather up all the opus tracks, and create a playlist
			for track in $album/*.opus; do
				printf "$cwd/$track\n" >> 1_Playlists/$genre.m3u
			done
		done
	done
}

if [-z $1]; then
	mkplaylist
elif ["$1" == "All"]; then
	all_genres=$(recsel -U -C -P Genre 1_Playlists/Genres.rec | sort | uniq)
	for genre in $all_genres; do
		mkgenreplaylist $genre
	done
else
	mkgenreplaylist
fi

If it's not obvious from the script, what I mean by that is that these are the sorts of invocations you'd expect with mkplaylist.

  • mkplaylist
    • If I'm in ~/Documents/Music, this will make a mixtape.m3u of all songs.
  • cd $artist && mkplaylist
    • Jumping into an artists directory will make an $artist.m3u of all albums I have for that artist.
  • mkplaylist $genre
    • Now into the new stuff! If I pass a $genre, like Metal, it'll make a Metal.m3u of all songs by any artists I tag as that genre
  • mkplaylist All
    • And because I'm lazy, this will generate playlists for all unique genres in my recfile!

I didn't want to remember too much with this thing honestly, so the simpler the interface the more I'll remember to use it. Honestly, these sorts of things don't need to be as complicated as the interface for tkts where you're switching context and such what. It just needs to make text files!

Anyways, I mentioned recutils, that's where all of the magic happens here. I initially tried to see if I could embed genre info inside of each opus file, and you can if you're willing to re-encode all of your files, which I really just don't want to do. So I scratched that. Especially after realizing that even after I added genre tags, I would need to iterate over the files, recollect that information & cache it in a database somewhere after I query some API to figure out what song by what artist is what genre. Too much effort for this script. Just thinking about it makes me want to rewrite the entire utility in nim, it would be a great tool, but that's now what this is for.

Right, yes recutils, that's what we're here for. Recfiles are wonderful little plain text database files, they are dead simple to write and query. They have awesome command line tools, and a super powered emacs mode. Plus I maintain the package for Alpine, so I may as well use the toys I have. The contents of my Genres.rec file looks just like this.

%rec: genre

Artist: 21_Savage
Genre: Rap

Artist: Blaqk_Audio
Genre: Punk

Artist: Caamp
Genre: Indie
Genre: Chill

It's dead simple, right? Even if you've never used recfiles before you know exactly what's going on here. The rec file sets up a single table (genre). That table has two fields, Artist and Genre. The Artist field is the same as the directory name inside of ~/Documents/Music. And the Genre field is the genre I want to sort that artists music into, there can be multiple Genre fields.

Seriously, I love how simple this setup is. I can edit the recfile by hand when I add a new artist. I don't really care if my Genres are 100% accurate because I just want big semi sorted lists for when I want to exclusively listen to Metal or something at work. And at the end of the day, all of this took maybe 45 minutes to figure out including writing my new recfile database!

And here's the result with the current batch of music I've got. Seriously, I love the results and can't wait to throw on my Metal playlist next time I need to seriously focus on a technical issue!

/home/durrendal/Documents/Music/1_Playlists:
  total used in directory 220 available 79.4 GiB
  drwxr-sr-x 2 durrendal durrendal 4096 Jan 20 15:47 .
  drwxr-sr-x 45 durrendal durrendal 4096 Jan 20 15:22 ..
  -rw-r--r-- 1 durrendal durrendal 5979 Jan 20 15:47 British.m3u
  -rw-r--r-- 1 durrendal durrendal 23832 Jan 20 15:47 Chill.m3u
  -rw-r--r-- 1 durrendal durrendal 14805 Jan 20 15:47 EDM.m3u
  -rw-r--r-- 1 durrendal durrendal 887 Jan 20 15:47 French.m3u
  -rw-r--r-- 1 durrendal durrendal 1380 Jan 20 15:45 Genres.rec
  -rw-r--r-- 1 durrendal durrendal 24524 Jan 20 15:47 German.m3u
  -rw-r--r-- 1 durrendal durrendal 10004 Jan 20 15:47 Indie.m3u
  -rw-r--r-- 1 durrendal durrendal 34765 Jan 20 15:47 Metal.m3u
  -rw-r--r-- 1 durrendal durrendal 3370 Jan 20 15:47 Ponk.m3u
  -rw-r--r-- 1 durrendal durrendal 13223 Jan 20 15:47 Pop.m3u
  -rw-r--r-- 1 durrendal durrendal 13710 Jan 20 15:47 Punk.m3u
  -rw-r--r-- 1 durrendal durrendal 30657 Jan 20 15:47 Rap.m3u
  -rw-r--r-- 1 durrendal durrendal 14150 Jan 20 15:47 Rock.m3u

Bio

(defparameter *Will_Sinatra* '((Age . 31) (Occupation . DevOps Engineer) (FOSS-Dev . true) (Locale . Maine) (Languages . ("Lisp" "Fennel" "Lua" "Go" "Nim")) (Certs . ("LFCS"))))

"Very little indeed is needed to live a happy life." - Aurelius