MEET SPOTSTER

Written by

in

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:

SOURCETRACK_NAMEARTISTSALBUMDURATION_MSADDED_AT
Liked SongsSundownGordon LightfootSundown2125062019-03-11
Basement TapesWish You Were HerePink FloydWish You Were Here3347432021-07-02
Late Shift OnlyNightcallKavinskyOutRun2560002022-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 spotdl falls 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 spotdl invocation keeps a single bad match from taking down a 2,000-track run.
  • youtube-dl today, yt-dlp tomorrow. The upstream youtube-dl project ages faster than YouTube changes its player. download_youtube_playlist.py --downloader yt-dlp is 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 argparse and subprocess — 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

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *