{"id":1823,"date":"2022-01-10T12:58:31","date_gmt":"2022-01-10T16:28:31","guid":{"rendered":"https:\/\/blog.danjoannis.com\/?p=1823"},"modified":"2026-02-26T19:59:35","modified_gmt":"2026-02-26T23:29:35","slug":"personal-use-how-do-you-takeout-your-spotify-data","status":"publish","type":"post","link":"https:\/\/blog.danjoannis.com\/?p=1823","title":{"rendered":"Personal Use: How do you &#8220;takeout&#8221; your Spotify data?"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Like many, I&#8217;ve built dozens of playlists in Spotify and truly enjoyed how the algorithm <em>knows<\/em> me. It was almost surreal finding Discover Weekly playlists where every single song fit my musical taste du jour.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, probably also like many, it was eventually overwhelming to hear thousands of new songs a year &#8211; to the point where I could no longer remember when I first heard or song, or whether it was a remix or the original. I was happy with the static selection in my playlists, but couldn&#8217;t justify paying $10\/month for what amounts to a CDN and .m3u host&#8230;<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Subscription services are often inherently designed around user retention. How do you export your Spotify data?<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>You can copy-paste from the desktop client to get a list of Spotify URLs &#8211; not useful without a subscription.<\/li>\n\n\n\n<li>You can pay for questionable &#8220;Spotify to MP3&#8221; utilities online.<\/li>\n\n\n\n<li>You keep paying.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">I won&#8217;t get into DMCA, DRM, fair use, or other concepts here. Instead, let&#8217;s take a look at a cool script&#8230;<\/p>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\"><div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><a href=\"https:\/\/blog.danjoannis.com\/wp-content\/uploads\/2022\/01\/received_313335010712438-e1641831293583.jpeg\"><img loading=\"lazy\" decoding=\"async\" width=\"397\" height=\"501\" src=\"https:\/\/blog.danjoannis.com\/wp-content\/uploads\/2022\/01\/received_313335010712438-e1641831293583.jpeg\" alt=\"\" class=\"wp-image-1824\" style=\"width:244px;height:308px\" srcset=\"https:\/\/blog.danjoannis.com\/wp-content\/uploads\/2022\/01\/received_313335010712438-e1641831293583.jpeg 397w, https:\/\/blog.danjoannis.com\/wp-content\/uploads\/2022\/01\/received_313335010712438-e1641831293583-238x300.jpeg 238w\" sizes=\"auto, (max-width: 397px) 100vw, 397px\" \/><\/a><\/figure>\n<\/div><\/div>\n<\/div>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">Overview<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">TL;DR: a script records your sound card to mp3 while it plays Spotify, and creates a new file for each song.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The blurry cam photo shows more or less what happens:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>User opens the Spotify <s>desktop<\/s> web client in Firefox (also works with YT).<\/li>\n\n\n\n<li>User runs the script, which waits for playback to start.<\/li>\n\n\n\n<li>User starts a playlist at the beginning.<\/li>\n\n\n\n<li>The script reads the song artist and title from <strong><s>dbus<\/s><\/strong> <strong>playerctl<\/strong><\/li>\n\n\n\n<li>The filename for the current song is in the form &#8220;# &#8211; Artist &#8211; Title.mp3&#8221;, where # is the playback sequence count.<\/li>\n\n\n\n<li>The script pipes the sound card output from <strong><s>parec<\/s> pw-record <\/strong>into the <strong>lame <\/strong>encoder.<\/li>\n\n\n\n<li>When the song title changes, the script ends the current file and starts a new one with a new filename.<\/li>\n\n\n\n<li>Repeats until playback status is not &#8220;Playing&#8221;.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">The conversion happens in real-time, which means it can take many days.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You will need to install a few packages.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><s>sudo apt install spotify-client\nsudo apt install bluez-tools<\/s>\nsudo apt install lame\nsudo apt install playerctl<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Script (for use with pipewire)<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n\n# Obtains current song info\ngetSong() {\n    artist=$(playerctl metadata artist | sed -e 's\/&#91;&lt;>:\"\/\\\\|?*]\/_\/g')\n    title=$(playerctl metadata title | sed -e 's\/&#91;&lt;>:\"\/\\\\|?*]\/_\/g')\n\n    filename=\"$artist - $title\"\n}\n\n# Obtains playback status\ngetPlaybackStatus() {\n    status=$(playerctl status)\n}\n\n# Global variables\npreviousSong=\"\"\npreviousPID=0\nsongID=0\n\n# TODO: query user to specify a playlist\/directory name and create it\n\n# Begin\necho \"Running (wait for start)...\"\n\n# Wait until user starts playback\nwhile &#91;&#91; \"$status\" != \"Playing\" ]]; do\n    getPlaybackStatus\ndone\n\n# Main loop\nwhile &#91;&#91; true ]]; do\n    getSong\n    getPlaybackStatus\n    if &#91; \"$status\" == \"Playing\" ]\n    then\n        if &#91; \"$previousSong\" != \"$filename\" ]\n        then\n            #ensure title and artist sync\n            getSong\n\n            # stop recording previous song\n            if &#91; $previousPID != 0 ]\n            then\n                echo \"stopping song '$previousSong'... \"\n                killall lame\n                previousPID=0\n                previousSong=0\n            fi\n\n            # start new recording using currentSong as filename\n            ((songID=songID+1))\n            echo \"starting song '$filename'...\"\n            \n            # Update this with browser node name\n            recording=$(pw-record --target \"Firefox\" - | lame -s 48000 -r -b 320 - \"$songID - $filename.mp3\") &amp;\n            currentPID=$!\n\n            previousSong=$filename\n            previousPID=$currentPID\n        fi\n    else\n        # stop recording - playback is ended\n        if &#91; $previousPID != 0 ]\n        then\n            echo \"stopping song '$previousSong'... \"\n            # this is not elegant but the PID returned was for the recorder, not lame\n            killall lame\n            previousPID=0\n            previousSong=0\n        fi\n    fi\ndone<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Script (Old &#8211; worked in 2022 with pulseaudio)<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n\n# Obtains current song info from the Spotify DBUS entry\ngetSong() {\n  artist=$(dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify \/org\/mpris\/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:org.mpris.MediaPlayer2.Player string:Metadata | sed -n '\/albumArtist\/{n;n;p}' | cut -d '\"' -f 2)\n  title=$(dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify \/org\/mpris\/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:org.mpris.MediaPlayer2.Player string:Metadata | sed -n '\/title\/{n;p}' | cut -d '\"' -f 2)\n\n  filename=\"$artist - $title\"\n  \n  # TODO: clean filename to only use valid characters\n}\n\n# Obtains playback status from the Spotify DBUS entry\ngetPlaybackStatus() {\n  status=$(dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify \/org\/mpris\/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:org.mpris.MediaPlayer2.Player string:PlaybackStatus | sed -n '\/variant\/{p}' | cut -d '\"' -f 2)\n}\n\n# Global variables\npreviousSong=\"\"\npreviousPID=0\nsongID=0\n\n# TODO: query user to specify a playlist\/directory name and create it\n\n# Begin\necho \"Running (wait for start)...\"\n\n# Wait until user starts Spotify playback\nwhile &#91;&#91; \"$status\" != \"Playing\" ]]; do\n  getPlaybackStatus\ndone\n\n# Main loop\nwhile &#91;&#91; true ]]; do\n  getSong\n  getPlaybackStatus\n  if &#91; \"$status\" == \"Playing\" ]\n  then\n    if &#91; \"$previousSong\" != \"$filename\" ]\n    then\n\t  #ensure title and artist sync\n      getSong\n\n      # stop recording\n      if &#91; $previousPID != 0 ]\n      then\n        echo \"stopping song '$previousSong'... \"\n        killall lame\n      fi\n\n      # start new recording using currentSong as filename\n      ((songID=songID+1))\n      echo \"starting song '$filename'...\"\n\t  \n\t  # Update this with your specific sound device\n      recording=$(parec -d alsa_output.pci-0000_00_1b.0.analog-stereo.monitor | lame -s -r -b 320 - \"$songID - $filename.mp3\") &amp;\n      currentPID=$!\n\n      previousSong=$filename\n      previousPID=$currentPID\n    fi\n  else\n    # stop recording - playback is ended\n    if &#91; $previousPID != 0 ]\n    then\n      echo \"stopping song '$previousSong'... \"\n\t  # this is not elegant but the PID returned was for parec not lame\n      killall lame\n    fi\n    echo \"Playback ended, program exiting!\"\n    exit\n  fi\ndone<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Pre-Flight Checklist<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Make sure you update the sound device used in <strong>parec<\/strong> to your own output device.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For the new pipewire script, you can change the <strong>pw-record<\/strong> target to your application name found using <strong>wpctl status<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Operation<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Open the player in your browser.<\/li>\n\n\n\n<li>Run the script.<\/li>\n\n\n\n<li>Start playlist playback.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">The script will save the files in the current directory.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Tips for Success<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For best results, you may want to do the following in the Spotify settings:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Set streaming quality to maximum.<\/li>\n\n\n\n<li>Disable Autoplay.<\/li>\n\n\n\n<li>Disable crossfading and smooth transitions.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">You will need your computer volume to be non-0 and not muted &#8211; but in Ubuntu you can easily divert the sound out of your headphone jack with nothing plugged in \ud83d\ude09<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Post-Production<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Once your playlist is done playing and the script stops, you may need to do a few things:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Group files in a playlist folder.<\/li>\n\n\n\n<li>Remove invalid characters from filenames (ex. ? &#8221; \/ \\ and so on).<\/li>\n\n\n\n<li>Use an MP3 tagging tool to update ID3 tags from the filenames.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\"># TODO<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">There are a bunch of ways it could be improved, but this was functional enough for me.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Remove invalid characters automatically<\/li>\n\n\n\n<li>Prompt user for a playlist name, to create a new folder<\/li>\n\n\n\n<li>Properly end the background recording process for each song.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s see how long this works for.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Like many, I&#8217;ve built dozens of playlists in Spotify and truly enjoyed how the algorithm knows me. It was almost surreal finding Discover Weekly playlists where every single song fit my musical taste du jour. However, probably also like many, it was eventually overwhelming to hear thousands of new songs a year &#8211; to the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1824,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,6,9],"tags":[342,344,343,341,340],"class_list":["post-1823","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-computers","category-music","category-software","tag-bash","tag-data","tag-linux","tag-mp3","tag-spotify"],"_links":{"self":[{"href":"https:\/\/blog.danjoannis.com\/index.php?rest_route=\/wp\/v2\/posts\/1823","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.danjoannis.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.danjoannis.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.danjoannis.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.danjoannis.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1823"}],"version-history":[{"count":11,"href":"https:\/\/blog.danjoannis.com\/index.php?rest_route=\/wp\/v2\/posts\/1823\/revisions"}],"predecessor-version":[{"id":2089,"href":"https:\/\/blog.danjoannis.com\/index.php?rest_route=\/wp\/v2\/posts\/1823\/revisions\/2089"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.danjoannis.com\/index.php?rest_route=\/wp\/v2\/media\/1824"}],"wp:attachment":[{"href":"https:\/\/blog.danjoannis.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1823"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.danjoannis.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1823"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.danjoannis.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1823"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}