2026-05-16 13:01:28 +02:00

Jarri Spooty

Self-hosted Spotify playlist and track downloader built with NestJS + Angular.

Jarri Spooty does not download audio from Spotify itself.
It retrieves metadata from Spotify and locates matching audio on YouTube.

Jarri Spooty Demo

Deterministic Spotify metadata ingestion with duration-aware YouTube candidate scoring, paced queue execution, hardened Docker deployment, and local archival workflows.

This hardened branch focuses on:

  • Large playlist support (>100 tracks)
  • Spotify OAuth login flow
  • Better YouTube pacing and throttling resistance
  • Improved Docker deployment
  • Safer credential handling
  • More resilient cover-art embedding
  • Better queue stability
  • Deterministic YouTube fallback handling
  • Explicit yt-dlp CLI execution
  • Automatic failed-candidate rejection
  • Improved age-gated video handling
  • Persistent SQLite state across container restarts
  • Deterministic Docker config persistence
  • Improved operational observability
  • Reduced unused dependency surface
  • Hardened filename, cover-art, subprocess, and websocket boundaries

Features

  • Download Spotify playlists
  • Download individual Spotify tracks
  • Playlist auto-subscription support
  • Automatic YouTube matching
  • MP3 tagging and embedded cover art
  • Docker deployment
  • Queue-based download system
  • Spotify OAuth integration
  • Large playlist pagination support
  • Download pacing controls
  • YouTube cookie support
  • Automatic YouTube retry/fallback handling
  • Failed YouTube candidate rejection memory
  • Deterministic yt-dlp error classification
  • Hardened cover-art validation and embedding
  • Explicit client-facing websocket payload shaping

Important Notice

Use this software responsibly.

Only download music you legally own or are permitted to access.

The maintainers are not responsible for misuse.


Supported URLs

  • Spotify playlists
  • Spotify tracks

Example:

https://open.spotify.com/playlist/...
https://open.spotify.com/track/...

Quick Start (Recommended)

1. Create Spotify Developer App

Go to:

https://developer.spotify.com/dashboard

Create an application.

Add this Redirect URI:

http://127.0.0.1:3000/api/spotify/callback

Copy:

  • Client ID
  • Client Secret

2. Store Credentials Outside Repository

Create a secure env file:

sudo mkdir -p /etc/tokens

sudo tee /etc/tokens/spotify.env > /dev/null <<'EOT'
SPOTIFY_CLIENT_ID=your_client_id
SPOTIFY_CLIENT_SECRET=your_client_secret
EOT

sudo chown root:$USER /etc/tokens/spotify.env
sudo chmod 640 /etc/tokens/spotify.env

Never commit this file.


YouTube increasingly rate-limits or age-gates anonymous downloads.

Export a Netscape-format cookies.txt from a logged-in browser session.

Recommended storage:

sudo cp cookies.txt /etc/tokens/youtube.cookies.txt
sudo chown root:$USER /etc/tokens/youtube.cookies.txt
sudo chmod 640 /etc/tokens/youtube.cookies.txt

Docker Run

docker run --rm -p 3000:3000 \
  --env-file /etc/tokens/spotify.env \
  -e SPOTIFY_REDIRECT_URI='http://127.0.0.1:3000/api/spotify/callback' \
  -e AUTH_ENABLED=true \
  -e SPOOTY_AUTH_TOKEN=change_this_token \
  -e YT_SEARCH_DELAY_MS=7000 \
  -e YT_DOWNLOADS_PER_MINUTE=6 \
  -e YT_COOKIES_FILE=/spooty/config/youtube.cookies.txt \
  -v "$PWD/downloads:/spooty/backend/downloads" \
  -v "$PWD/spooty-config:/spooty/backend/config" \
  -v "/etc/tokens/youtube.cookies.txt:/spooty/config/youtube.cookies.txt:ro" \
  jarri-spooty:local

Open:

http://127.0.0.1:3000/?token=change_this_token

Then:

  1. Click "Connect Spotify"
  2. Login to Spotify
  3. Approve access
  4. Paste playlist URL
  5. Download

Deterministic YouTube Fallback Handling

The hardened branch now uses direct yt-dlp CLI execution rather than relying entirely on wrapper abstractions.

This provides:

  • Explicit stderr visibility
  • Better Docker compatibility
  • Deterministic retry handling
  • Automatic failed-candidate rejection
  • Better age-gated video handling
  • Improved operational observability

If a YouTube candidate fails:

  1. The failed URL is recorded
  2. The candidate is rejected
  3. A new YouTube search is performed
  4. The next-best valid candidate is attempted automatically

This prevents infinite retry loops against dead or restricted videos.


Queue Pacing

Aggressive YouTube access can trigger:

  • HTTP 302 loops
  • CAPTCHA
  • temporary throttling
  • incomplete downloads

Recommended safe pacing:

-e YT_SEARCH_DELAY_MS=7000
-e YT_DOWNLOADS_PER_MINUTE=6

The hardened branch also includes additional internal pacing and retry coordination to reduce:

  • repeated failed candidate loops
  • aggressive retry bursts
  • queue collisions
  • YouTube anti-bot triggers

Security Notes

Never commit:

  • Spotify secrets
  • OAuth tokens
  • cookies.txt
  • downloaded music
  • local databases
  • spooty-config/

Recommended .gitignore additions:

downloads/
config/
spooty-config/
*.sqlite
cookies.txt
.env
.env.local

License

MIT

S
Description
Self-hosted spotify downloader
Readme
3.2 MiB
Languages
TypeScript 81.3%
HTML 11.6%
SCSS 5.1%
Shell 1.4%
Dockerfile 0.6%