Replacing YouTube as a Music Player
2020-11-10

I listen to a lot of music and most of the time I'm not playing whole albums, but chain songs that fit my current mood. I started out creating those chains by clicking through suggestions on YouTube, but I disliked that mostly because the suggestions didn't always fit my expectations, I don't want Google to analyze my listening patterns and I want full access to my suggestions data. Thus I created some tools and scripts which alleviate those problems and enable me to manage my music completely from the terminal as a bonus.

A Quick Overview of My Setup

Nowadays I'm just typing sng into my terminal to get suggestions for songs to listen to and select one via fzf. It then gets downloaded from YouTube, if necessary, and starts playing in mpd. If the song played for 20 seconds, it is automatically registered that I listened to it using songmem. After listening to some songs, songmem will have learned which songs I often listen to consecutively and sng will be able to make better suggestions.

With this setup, YouTube will only see me listening to the song the first time. After that the song is stored on my computer, automatically tagged and added to my mpd music library. This not only keeps Google from seeing my listening patterns, but also saves network bandwidth, decreases latency, let's me listen to the songs offline and gives me the possibility to add those songs to my mpd queue.

Prerequisites

The following tools will be used in the sections below. Check out these links, if you are unfamiliar with them:

  • youtube-dl: Tool for downloading videos from YouTube.
  • fzf: A UNIX filter used to select one song out of many suggestions. You could also use dmenu instead.
  • ffmpeg: A video and audio conversion tool used for tagging.
  • ytools: Tools I wrote to interact with YouTube in scipts.
  • songmem: Tool I wrote to analyze listening patterns.
  • mpd and an mpd client. I use mpq.

Searching and Downloading Songs from YouTube

I search YouTube from the command line with ytools and download songs with youtube-dl. After downloading I tag the songs with their artist and title using ffmpeg. I use this script for searching, downloading and tagging a song all in one; let's call it yt-audio-backup:

#!/usr/bin/env sh
# Use like: yt-audio-backup 'Daft Punk - Face to Face'
set -e

cd ~/Music/youtube-backups
song=$@
song_slug=$(printf '%s\n' "$song" | sed 's|[ /]|_|g')
artist=$(printf '%s' "$song" | awk -F ' - ' '{print $1}')
title=$(printf '%s' "$song" | awk '{i=index($0, " - "); print substr($0, i+3)}')

ytools-search "$song" && \
echo -n 'Selection [1]: '
read selection
test -z "$selection" && selection=1
youtube-dl \
	-f "bestaudio/best" \
	-x \
	-o "${song_slug}_%(id)s.%(ext)s" \
	"$(ytools-pick "$selection")"

file=$(find . -name "${song_slug}*" | head -n1)
ffmpeg -i "$file" \
	-acodec copy \
	-metadata artist="$artist" \
	-metadata title="$title" \
	"tmp_youtube-dl_output.${file##*.}"
mv "tmp_youtube-dl_output.${file##*.}" "$file"

Now you could run mpc --wait rescan and use the newly downloaded song from your mpq client.

Automatically Recording Listening Patterns

Now that songs can easily be backed up from YouTube and added to the mpd music library, I can use songmem to automatically register every song I listen to in mpd. Simply run this script after you logged into your computer and mpd is started:

#!/usr/bin/env sh
while song=$(mpc -f '%artist% - %title%' current --wait)
do
	# Kill background processes from previous loops:
	pkill -P "$$"

	# Register a hearing of the started song,
	# if it is still playing after 20s:
	sleep 20 \
	&& mpc status | grep -q 'playing' \
	&& songmem --register "$song" &
done

Introducing sng

Now that I can easily download songs and record my listening patterns, I want to have an easy way to find suggestions for songs to listen to, download them automatically if necessary and start playing them in mpq. To do this I use a script I call "sng":

#!/usr/bin/env sh
set -e

alias fzf='fzf --no-sort --prompt "Search on YouTube: "'
last_song=$(songmem | head -n1)
selection=$(songmem --suggestions "$last_song" | fzf)

artist=$(printf '%s' "$selection" | awk -F ' - ' '{print $1}')
title=$(printf '%s' "$selection" | awk '{i=index($0, " - "); print substr($0, i+3)}')
file=$(mpc search artist "$artist" title "$title")
if [ -z "$file" ]
then
	printf 'Need to download from YouTube first:\n'
	yt-audio-backup "$selection"
	mpc --wait rescan
	file=$(mpc search artist "$artist" title "$title")
fi
mpc clear
mpc add "$file"
mpc play

You could add other features to this script, like registering you just heard a song without playing it, listing most heard songs instead of suggestions or searching for recommendations on YouTube using ytools-recommend.

Exploring New Music

When I just listened to a new song, I can add it to my collection by executing something like songmem -r 'Röyksopp - Keyboard Milk'. The next time I play this song through sng it will be downloaded and added to my music library.