Two small CLI tools that pull your Liked Songs, your playlists, and your YouTube rips out of the cloud and onto a drive you actually control.
Stack Python 3.10+Depends on spotipy · spotdl · youtube-dl · ffmpegLicense surface your own library
Every streaming library is a loan. The playlist you spent three years curating, the “Liked Songs” pile that’s basically a diary — none of it is a file you hold. A label dispute, a market exit, a quietly revoked license, and a chunk of it is just gone. Spotster is the fix: point it at your Spotify account or a YouTube playlist, and it writes real MP3s to a real folder, organized the way a record collection is organized — by artist.
A CSV IS A RECEIPT. IT TELLS YOU EXACTLY WHAT YOU OWNED, EVEN AFTER THE FILE IS GONE.— why export comes before download
WHAT’S IN THE REPO
Spotster is two independent tools sharing one dependency list. Neither needs the other to run.
export_library.py → download.py
Authenticates against the Spotify Web API, paginates through every Liked Song and every playlist you own or follow, and writes it all to one flat CSV. A second script reads that CSV and hands the track URLs to spotdl, which resolves each one, downloads the audio, and tags it.
export_library.py · download.py · requirements.txt · .env.example
download_youtube_playlist.py
Hands one or more playlist URLs to youtube-dl with --ignore-errors, so one region-locked or deleted video doesn’t kill a 300-track playlist. Swap in yt-dlp with a single flag when upstream inevitably lags YouTube again.
download_youtube_playlist.py
THE SPOTIFY SIDE, STEP BY STEP
First, the export. This talks to Spotify only — no audio touched yet, just metadata:
spotster — export_library.py
$ python export_library.py -o spotify_library.csv Fetching Liked Songs... Fetching playlists... fetching playlist: Basement Tapes fetching playlist: Late Shift Only Wrote 2,184 rows to spotify_library.csv
The CSV is the whole point. It’s diffable, greppable, and survives every future decision you make about where the audio actually lives:
| SOURCE | TRACK_NAME | ARTISTS | ALBUM | DURATION_MS | ADDED_AT |
|---|---|---|---|---|---|
| Liked Songs | Sundown | Gordon Lightfoot | Sundown | 212506 | 2019-03-11 |
| Basement Tapes | Wish You Were Here | Pink Floyd | Wish You Were Here | 334743 | 2021-07-02 |
| Late Shift Only | Nightcall | Kavinsky | OutRun | 256000 | 2022-11-19 |
Then the download. It reads that same CSV, dedupes the track URLs, and calls spotdl in batches of twenty so one broken match doesn’t stall the whole queue:
spotster — download.py
$ python download.py spotify_library.csv -o Music --format mp3 [1/110] downloading 20 track(s)... [2/110] downloading 20 track(s)... Downloaded 2,184 track(s) into Music/<artist>/
The output template does the filing for you — Music/{artist}/{title}.{output-ext} — so a library that was one long undifferentiated list in the app comes out the other side looking like a shelf.
SPOTIFY APILiked Songs + playlists
→
CSVone row per track
CSVspotify_library.csv
→
MUSIC/<ARTIST>/spotdl, batched
THE YOUTUBE SIDE
No API keys, no CSV — just a playlist URL and a folder. Point it at as many playlists as you want in one call:
spotster — download_youtube_playlist.py
$ python download_youtube_playlist.py "https://www.youtube.com/playlist?list=..." -o Music
Same idea as the Spotify side, one level less granular: since YouTube doesn’t tag an “artist” the way Spotify does, tracks land in Music/<uploader>/<title>.mp3 — the channel stands in for the artist folder.
GETTING IT RUNNING
Everything needs Python 3.10+ and ffmpeg on PATH — both downloaders shell out to it for transcoding.
setup
$ pip install -r requirements.txt # spotipy, spotdl, youtube-dl $ cp .env.example .env # fill in SPOTIPY_CLIENT_ID / SPOTIPY_CLIENT_SECRET # from a Spotify Developer Dashboard app $ export $(grep -v '^#' .env | xargs)
The Spotify OAuth step opens a browser exactly once per machine; spotipy caches the token afterward, so re-runs of export_library.py don’t ask again.
WHY IT’S BUILT THIS WAY
- The CSV is a checkpoint, not a formality. If
spotdlfalls behind Spotify’s API tomorrow, you still have a complete, portable record of what you owned — grep it, diff it, hand it to a different downloader entirely. - Batching over one giant call. Download requests fail one track at a time, not all at once — twenty per
spotdlinvocation keeps a single bad match from taking down a 2,000-track run. - youtube-dl today, yt-dlp tomorrow. The upstream
youtube-dlproject ages faster than YouTube changes its player.download_youtube_playlist.py --downloader yt-dlpis a one-flag swap to the actively maintained fork, same options, same output layout. - Two scripts, no framework. Nothing here needs a server, a database, or a config language. It’s
argparseandsubprocess— read it in five minutes, and it still works after you stop thinking about it.
GET THE REPO
PETERALCOCK/SPOTSTER
git clone https://github.com/peteralcock/spotster







