Bart Simons

Bart Simons


Thoughts, stories and ideas.

Bart Simons
Author

Share


Tags


.net .net core Apache C# CentOS LAMP NET Framework Pretty URLs Windows Server WireGuard WireGuard.io access log add analysis android api at the same time authentication authorization automate automation azure azurerm backup bash basics batch bootstrap build capture cheat sheet chromium chroot class cli click to close code snippet command line commands compile compiling compression containers control controller controlling convert cpu usage create credentials csv csvparser curl data dd deployment desktop detect devices disable diskpart dism distributed diy docker dom changes dotnet core drivers ease of access encryption example export file transfer files fix folders generalize getting started ghost ghost.org gui guide gunicorn gzip html html tables icewarp igd imagex import inotify install installation interactive ios iphone itunes java javascript jquery json kiosk kotlin linux live load data loading screen lock screen loopback audio lxc lxd lxml macos manage manually message messages minio mirrored mod_rewrite monitor monitoring mutationobserver mysql nexmo nginx no oobe node node.js nodejs not installing notification notifications object storage on desktop one command openssl owncloud parallels parallels tools parse perfect philips hue play port forwarding portainer.io powershell processing ps-spotify python quick raspberry pi record rip ripping rsync rtmp save save data sbapplication scraping script scripting scriptingbridge scripts security send server service sharedpreferences sms songs sonos spotify spotify api spotlight ssh stack streaming streamlink studio sudo swarm swift sync sysprep system audio systemd tables terminal tracking tutorial twilio ubiquiti ubuntu ubuntu 18.04 ui code unifi unlock unsplash source upnp uptime usb tethering wallpapers wasapi website websites webview windows windows 10 without itunes without oobe workaround xaml

Ripping Spotify songs on macOS

Ever wanted to rip songs of Spotify on your Mac? Since there are no tools available that allow you to directly download songs from Spotify, I decided to create a so-called 'duct tape' solution that might help you out with ripping songs.

So, it works as follows: AppleScript bindings allow users to automate Spotify in the following way for example:

osascript -e "tell application \"Spotify\"" -e "play track \"spotify:track:21cp8L9Pei4AgysZVihjSv\"" -e "end tell"

This will play the song Child In Time by Deep Purple for example. There are so many ways to control Spotify with AppleScript: lots of examples and/or AppleScripts are available on the internet.

To summarize: we are now able to control and automate Spotify with AppleScript. But, we also want to record songs. I came across a brilliant application called Piezo. Piezo is a simple to use app that allows Mac users to record the audio of a specific application, which makes it a perfect solution since we only want to record Spotify audio, right?

We also want to update the song's ID3 tags afterwards, since Spotify also makes song information available through AppleScript.

I decided to write my script in Python to help better integrate the 3 main tasks:

I have included comments in my Python script so that you can easily understand what each part of the code does:

# recordsong.py - record a song on Spotify with the help of Piezo

# Example usage:
#
# python3 recordsong.py spotify:track:21cp8L9Pei4AgysZVihjSv

import subprocess, sys, os, time, shutil, eyed3
from urllib.request import urlopen

# Setup variables
piezoStorageLocation = '/Users/bart/Music/Piezo/'
ripStorageLocation   = '/Users/bart/Music/Ripped/'

# Clear all previous recordings if they exist
for f in os.listdir(piezoStorageLocation):
    os.remove(os.path.join(piezoStorageLocation,f))

# Tell Spotify to pause, tell Piezo to record, tell Spotify to play a specified song
subprocess.Popen('osascript -e "tell application \\"Spotify\\" to pause"', shell=True, stdout=subprocess.PIPE).stdout.read()
time.sleep(.300)
subprocess.Popen('osascript -e "activate application \\"Piezo\\"" -e "tell application \\"System Events\\"" -e "keystroke \\"r\\" using {command down}" -e "end tell"', shell=True, stdout=subprocess.PIPE).stdout.read()
subprocess.Popen('osascript -e "tell application \\"Spotify\\"" -e "play track \\"'+sys.argv[1]+'\\"" -e "end tell"', shell=True, stdout=subprocess.PIPE).stdout.read()

time.sleep(1)

# Get the artist name, track name, album name and album artwork URL from Spotify
artist  = subprocess.Popen('osascript -e "tell application \\"Spotify\\"" -e "current track\'s artist" -e "end tell"', shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8').rstrip('\r\n')
track   = subprocess.Popen('osascript -e "tell application \\"Spotify\\"" -e "current track\'s name" -e "end tell"', shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8').rstrip('\r\n')
album   = subprocess.Popen('osascript -e "tell application \\"Spotify\\"" -e "current track\'s album" -e "end tell"', shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8').rstrip('\r\n')
artwork = subprocess.Popen('osascript -e "tell application \\"Spotify\\"" -e "current track\'s artwork url" -e "end tell"', shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8').rstrip('\r\n')

# Download album artwork
artworkData = urlopen(artwork).read()

# Check every 500 milliseconds if Spotify has stopped playing
while subprocess.Popen('osascript -e "tell application \\"Spotify\\"" -e "player state" -e "end tell"', shell=True, stdout=subprocess.PIPE).stdout.read() == b"playing\n":
    time.sleep(.500)

# Spotify has stopped playing, stop the recording in Piezo
subprocess.Popen('osascript -e "activate application \\"Piezo\\"" -e "tell application \\"System Events\\"" -e "keystroke \\"r\\" using {command down}" -e "end tell"', shell=True, stdout=subprocess.PIPE).stdout.read()

time.sleep(.500)

# Create directory for the artist
if not os.path.exists(ripStorageLocation+artist):
    os.makedirs(ripStorageLocation+artist)

# Create directory for the album
if not os.path.exists(ripStorageLocation+artist+"/"+album):
    os.makedirs(ripStorageLocation+artist+"/"+album)

# Move MP3 file from Piezo folder to the folder containing rips.
for f in os.listdir(piezoStorageLocation):
        if f.endswith(".mp3"):
            shutil.move(piezoStorageLocation+f, ripStorageLocation+artist+"/"+album+"/"+track+".mp3")

# Set and/or update ID3 information
musicFile = eyed3.load(ripStorageLocation+artist+"/"+album+"/"+track+".mp3")
musicFile.tag.images.set(3, artworkData, "image/jpeg", sys.argv[1])
musicFile.tag.artist = artist
musicFile.tag.album  = album
musicFile.tag.title  = track

musicFile.tag.save()

Please note that this script should run in Python 3, not Python 2. Also, make sure that you have all dependencies installed:

pip3 install eyed3
brew install libmagic

Now, we can rip songs from Spotify without having to deal with the circumvention of DRM. Enjoy your music!

Bart Simons
Author

Bart Simons

View Comments