Skip to content

Repository files navigation

Spotify + YouTube Bulk Music Downloader

Download entire Spotify and YouTube playlists in bulk — parallel, DJ-ready, straight from the terminal.

Built for DJs, music collectors, and anyone tired of streaming-only libraries. Paste any mix of Spotify and YouTube links, hit enter, and walk away. Your music downloads in parallel as MP3 320k with metadata, renderable cover art, and lyrics embedded — organised into one importable .m3u8 crate per playlist.

Python 3.10+ License: MIT Platform


What This Does

You have a 400-song DJ playlist on Spotify. You want the actual files on your hard drive — tagged, organized, and ready for Rekordbox, Serato, Traktor, or VLC.

This tool:

  1. Takes your Spotify playlist URL(s)
  2. Matches every track to its YouTube Music equivalent
  3. Downloads 8 tracks in parallel (tuned for reliability — see Tuning)
  4. Embeds Spotify metadata (artist, title, album, album art, lyrics)
  5. Saves each playlist into its own folder + an importable .m3u8 crate under downloads/
  6. Retries transient failures automatically and reports exactly which tracks (if any) couldn't be found

One command. DJ-ready MP3 320k by default.

New in v3: paste Spotify and YouTube links together — the tool auto-detects and routes each. YouTube-only songs download via yt-dlp with square cover art; tracks Spotify can't match are auto-sourced from YouTube. Plus parallel playlists (--jobs), per-session logs + --resume, and a library.py to verify/repair thumbnails and clean junk.

From v2: per-playlist .m3u8 crates, MP3 320k default (OPUS loads in no DJ software), auto-retry, accurate reporting, --sync, --update, credentials-path fix, reorganize.py, spotdl_eval.py. See ANALYSIS.md for the full before/after.

New in v3 — quick reference

# Mix Spotify + YouTube in one command (auto-routed):
python spotidownload.py <spotify_playlist_url> <youtube_video_url> <youtube_playlist_url>

# Download 2 playlists at once (threads auto-split to stay ban-safe):
python spotidownload.py URL1 URL2 --jobs 2

# Continue an interrupted session:
python spotidownload.py --resume

# Fast resume for big playlists (RECOMMENDED for any re-run): fetch each track list
# once, diff against disk, download ONLY what's missing — no full re-scan, no
# Spotify rate-limit spiral. Unfindable tracks auto-fallback into the crate:
python spotidownload.py URL1 URL2 URL3 --diff

# Make your existing OPUS thumbnails show up (opus art doesn't render):
python library.py verify              # see the report
python library.py repair --apply      # convert to MP3 320k with square art
python library.py clean --apply       # remove interrupted-download junk
  • Auto-routing: Spotify links → spotdl (metadata + matched audio). YouTube/YT-Music links → yt-dlp direct. A watch?v=…&list=… link grabs just that video; a pure playlist?list=… link grabs the whole playlist.
  • Auto-fallback: if a Spotify track has no YouTube-Music match, it's searched for on YouTube and dropped into downloads/Recovered from YouTube/. Disable with --no-fallback.
  • Logs: every run writes logs/session-<id>.json + per-source .log files so you can see exactly what happened and resume.

Going in with 200–400 songs? Read PRACTICES.md first — how to run playlists in parallel without getting 403'd, and what to do when yt-dlp, Deno, Spotify rate-limits, or a kill mid-crate happens. Written from a live three-playlist night that wrote 432 files.


Table of Contents


Quick Start

If you already have Python, pip, and ffmpeg installed:

pip install spotdl yt-dlp
git clone https://github.com/ckryptickunal/spotidownloader.git
cd spotidownloader
python spotidownload.py

Paste your playlist link when prompted. Done.


Requirements

Tool Version Purpose
Python 3.10+ Runs the script
spotdl Latest Spotify metadata + YouTube matching
yt-dlp Latest Downloads audio from YouTube Music
ffmpeg Any recent Audio conversion & metadata embedding
Deno via spotdl --download-deno Decrypts some YouTube streams (yt-dlp fails without it)

Optional but recommended:

Tool Purpose
YouTube cookies Unlocks 256kbps OPUS with YouTube Premium
mediainfo Verify audio quality after download

Installation

macOS

# Install Homebrew if you don't have it
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Install Python and ffmpeg
brew install python@3.11 ffmpeg

# Install the download tools
pip3 install spotdl yt-dlp

# Clone this repo
git clone https://github.com/ckryptickunal/spotidownloader.git
cd spotidownloader

Linux (Ubuntu/Debian)

# Install system dependencies
sudo apt update
sudo apt install -y python3 python3-pip python3-venv ffmpeg

# Install the download tools
pip3 install spotdl yt-dlp

# Clone this repo
git clone https://github.com/ckryptickunal/spotidownloader.git
cd spotidownloader

Windows

  1. Download and install Python 3.11+check "Add to PATH"
  2. Download and install ffmpeg — add to PATH
  3. Open PowerShell:
pip install spotdl yt-dlp
git clone https://github.com/ckryptickunal/spotidownloader.git
cd spotidownloader

Using a Virtual Environment (Recommended)

python3 -m venv .venv
source .venv/bin/activate        # macOS/Linux
# .venv\Scripts\Activate.ps1     # Windows PowerShell

pip install spotdl yt-dlp

Or use the included setup script:

chmod +x setup.sh
./setup.sh

Spotify Developer Setup

You need a free Spotify Developer App to authenticate API requests. This takes 2 minutes.

Step 1: Create a Spotify Developer App

  1. Go to developer.spotify.com/dashboard
  2. Log in with your Spotify account
  3. Click Create App
  4. Fill in the fields:
Field Value
App Name SpotDL
App Description Spotify Downloader
Redirect URI http://127.0.0.1:9900/
  1. Under Which API/SDKs are you planning to use?, select Web API only
  2. Make sure the app is in Development mode

Step 2: Copy Your Credentials

  1. Open your app in the dashboard
  2. Click Settings
  3. Copy your Client ID
  4. Click View Client Secret and copy your Client Secret

Step 3: Enter Credentials

Run the downloader for the first time — it will ask for your Client ID and Secret:

── First-Time Setup ──
Paste your Spotify Client ID: ████████████████████████████████
Paste your Spotify Client Secret: ████████████████████████████████

[OK] Credentials saved to ~/.spotdl/config.json

Credentials are saved locally and never transmitted anywhere except Spotify's API.


Usage

Interactive Mode

Just run the script with no arguments. It will keep asking for playlist URLs until you quit:

python spotidownload.py
  ╔═══════════════════════════════════════════════════╗
  ║       Spotify Bulk Playlist Downloader            ║
  ║       Powered by spotdl + yt-dlp                  ║
  ╚═══════════════════════════════════════════════════╝

  Output   : /Users/you/spotify-bulk-downloader/downloads
  Format   : mp3 @ 320k
  Threads  : 8   Retries: 1
  Layout   : one folder + .m3u8 crate per playlist

Paste a Spotify playlist URL (or 'q' to quit): https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M

  Downloading: https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M
  ...
  ================================================================
    Today's Top Hits
      New this run : 48
      In crate     : 50
      Failed       : 2

Paste a Spotify playlist URL (or 'q' to quit): q
Bye!

Direct Mode

Pass the URL as an argument — great for scripting or one-off downloads:

python spotidownload.py https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M

All Options

python spotidownload.py [URL ...] [OPTIONS]

Positional:
  url                     Spotify and/or YouTube playlist/album/video URL(s)
                          (omit for interactive mode)

Options:
  -t, --threads N         Total parallel downloads (default: 8)
  -j, --jobs N            Concurrent playlist processes, max 3 (default: 1;
                          threads are split across them)
  -o, --output DIR        Output directory (default: ./downloads)
  -f, --format FORMAT     Audio format: mp3, flac, ogg, opus, m4a, wav
                          (default: mp3)
  -b, --bitrate RATE      Bitrate: auto, 128k, 256k, 320k (default: 320k)
  -c, --cookies FILE      Path to YouTube cookies (Netscape format)
  -r, --retries N         Extra passes for transient failures (default: 1)
  --diff                  Fast resume: download only tracks missing on disk
  --resume                Continue the last session's unfinished links
  --no-fallback           Don't YouTube-search unmatched Spotify tracks
  --update                Upgrade spotdl + yt-dlp
  --setup                 Re-run first-time setup
  -h, --help              Show help

Full parallel / failure playbook: PRACTICES.md.


YouTube Premium (Higher Quality)

Without cookies, you get ~128kbps OPUS (format 251) — still very good quality.

With YouTube Premium cookies, you unlock 256kbps OPUS (format 771) — the best available short of lossless.

How to Export Cookies

  1. Install a browser extension that exports cookies in Netscape format:

  2. Go to music.youtube.com and log in with your Premium account

  3. Click the extension and export cookies

  4. Save the file to:

OS Path
macOS/Linux ~/.spotdl/cookies.txt
Windows C:\Users\YourName\.spotdl\cookies.txt

The downloader auto-detects this file. No extra flags needed.

Verify Quality

After downloading, check a file with mediainfo:

mediainfo --Full "Artist - Song.opus" | grep -i "bit rate"

Expected output for Premium quality:

Overall bit rate : 256 kb/s

Output Formats

Format Extension DJ software? Best For
MP3 320k .mp3 ✅ All (Rekordbox/Serato/Traktor/CDJs) Default — universal DJ compatibility
M4A (AAC) .m4a ✅ Most Smaller than mp3 at similar quality, Apple ecosystem
FLAC .flac ✅ Modern software Lossless container (note: source is lossy, so no real quality gain)
OPUS .opus None Personal listening library only (VLC/mpv) — cannot load in DJ gear
OGG .ogg ⚠️ Serato/Traktor only Linux players
WAV .wav ✅ All (huge files) Production, sampling

Recommendation for DJs

  • Default (recommended): MP3 320kbps — universal compatibility with CDJs and all DJ software. This is now the default; just run the tool.
  • Apple/space-conscious: --format m4a — smaller files, loads in Rekordbox/Serato/Traktor.
  • Personal listening only (NOT for DJing): --format opus — best quality-to-size, but no DJ software can load .opus.
  • Production/sampling: --format wav or --format flac.

Examples

Download a single playlist

python spotidownload.py https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M

Download in MP3 format at 320kbps

python spotidownload.py URL --format mp3 --bitrate 320k

Download to a custom folder with more threads

# 8 is the reliability ceiling. Drop to 4 if you see 403/429. Do not jump to 30.
python spotidownload.py URL --threads 8 --output ~/Music/DJ-Sets

Download an entire album

python spotidownload.py https://open.spotify.com/album/ALBUM_ID

Download an artist's entire discography

python spotidownload.py https://open.spotify.com/artist/ARTIST_ID

Download your Liked Songs

python spotidownload.py saved

(Opens a browser once for Spotify user-auth — required for private/liked content.)

Download multiple playlists at once

One command, shared thread pool — songs from every playlist download interleaved. Do not open one terminal per playlist.

python spotidownload.py \
  https://open.spotify.com/playlist/PLAYLIST_1 \
  https://open.spotify.com/playlist/PLAYLIST_2 \
  https://open.spotify.com/playlist/PLAYLIST_3

# Two processes, threads auto-split so YouTube load stays ~8:
python spotidownload.py URL1 URL2 --jobs 2

# Re-run a big crate — only missing tracks (avoids Spotify rate-limits):
python spotidownload.py URL1 URL2 URL3 --diff

Or use interactive mode and paste them one by one.

See PRACTICES.md for the full parallel + recovery playbook.


FAQ & Troubleshooting

The full symptom → fix table from live crates is in PRACTICES.md. Short version below.

"No results found" for some songs

Some tracks may not have a YouTube Music equivalent. These are logged in downloads/errors.txt. Common causes:

  • Regional restrictions on YouTube Music
  • The song is a Spotify exclusive
  • Very obscure or recently released tracks

Downloads are slow

  • Don't just crank threads. Above ~8, spotdl gets no throughput gain (each thread is a full yt-dlp session) and only raises your odds of a 403/429 rate-limit. The default is 8; drop to 4 if you see errors. Find your machine's real optimum with spotdl_eval.py.
  • YouTube may be rate-limiting your IP — wait, or add Premium cookies (--cookies).
  • Run python spotidownload.py --update — a stale yt-dlp is the #1 cause of stalls/errors.

"403 Forbidden" errors

Your cookies may have expired. Re-export them from your browser and replace cookies.txt.

"Client ID" errors

Songs are downloading in low quality

Without YouTube Premium cookies, you're limited to ~128kbps. See YouTube Premium section for how to unlock 256kbps.

Permission errors on macOS

chmod +x spotidownload.py
python3 spotidownload.py  # use python3 explicitly

How do I update spotdl/yt-dlp?

pip install --upgrade spotdl yt-dlp

Do this regularly — YouTube frequently changes their API and yt-dlp updates to match.

Can I pause and resume?

Yes. Finished files stay on disk. After a Ctrl+C or a kill:

python spotidownload.py --resume              # same URL list as last session
python spotidownload.py URL1 URL2 URL3 --diff # any re-run of a big crate (preferred)

--diff only downloads what is missing. A full re-scan of hundreds of tracks is what trips Spotify rate-limits.

Deno / "YT-DLP download error"

spotdl --download-deno
python spotidownload.py --update
python spotidownload.py URL1 URL2 --diff

Where is my config stored?

OS Config Path
macOS/Linux ~/.spotdl/config.json
Windows C:\Users\YourName\.spotdl\config.json

How It Works

┌─────────────────┐     ┌──────────────┐     ┌──────────────┐
│  Spotify API    │────▶│   spotdl     │────▶│   yt-dlp     │
│  (metadata)     │     │  (matching)  │     │  (download)  │
└─────────────────┘     └──────────────┘     └──────┬───────┘
                                                     │
                                                     ▼
                                              ┌──────────────┐
                                              │    ffmpeg     │
                                              │  (convert +   │
                                              │   embed tags) │
                                              └──────┬───────┘
                                                     │
                                                     ▼
                                              ┌──────────────┐
                                              │  downloads/  │
                                              │  Playlist/   │
                                              │  Artist -    │
                                              │  Title.mp3   │
                                              └──────────────┘
  1. Spotify API provides the playlist track list with full metadata (artist, title, album, artwork, lyrics)
  2. spotdl matches each Spotify track to its YouTube Music counterpart using audio fingerprinting and metadata comparison
  3. yt-dlp downloads the audio stream from YouTube Music (8 tracks simultaneously by default)
  4. ffmpeg converts to your chosen format and embeds all the Spotify metadata
  5. Files land in downloads/<Playlist Name>/ as Artist - Title.mp3, plus a .m3u8 crate

When a YouTube Music match fails, the track is searched on YouTube and recovered into the crate (or downloads/Recovered from YouTube/). Details and failure improvisation: PRACTICES.md.


Tips for DJs

Organize by BPM/Genre

After downloading, use Mp3Tag (Windows) or PuddleTag (Linux) to sort and batch-edit tags.

Import to DJ Software

Software Supported Formats
Rekordbox MP3, WAV, FLAC, M4A, AAC
Serato DJ MP3, OGG, FLAC, WAV, M4A, AAC
Traktor MP3, WAV, FLAC, OGG, AAC
VirtualDJ MP3, WAV, FLAC, OGG, M4A, AAC

For maximum compatibility across all DJ software, use MP3 320kbps:

python spotidownload.py URL --format mp3 --bitrate 320k

Transfer to USB for CDJs

  1. Download in MP3 format
  2. Copy the downloads/ folder to a USB stick formatted as FAT32 or exFAT
  3. Plug into your CDJ and browse

Sync Playlists Over Time

Two options:

  • Quick resume: just re-run the same command. Already-downloaded songs are skipped, so only new tracks get pulled.
  • True sync: add --sync. This saves a .spotdl file per playlist and, on re-run, adds new tracks and removes ones you deleted from the Spotify playlist — keeping your crate a mirror of the playlist.
python spotidownload.py URL --sync

Tuning (threads & eval)

--threads is the main speed/reliability dial. More is not better: each thread is a full YouTube download session from your one IP, so past ~8 you get no extra speed and a rising chance of rate-limiting. Defaults: 8 (drop to 4 if you hit errors).

To find your machine + network's real optimum empirically, use the included benchmark:

# SAFE anytime (metadata only, no audio, won't disturb a running download):
python spotdl_eval.py --mode meta

# Full sweep — run only when idle. Downloads a fixed 12-track sample at
# threads 4/8/16/24 and ranks them by speed × success × quality:
python spotdl_eval.py --mode audio --threads-sweep 4 8 16 24 --reps 3

It writes eval_results/summary.csv and prints the winning config.

Reorganize an old flat folder into crates

If you downloaded with v1 (everything in one flat downloads/), sort it into per-playlist crates without re-downloading:

python reorganize.py URL1 URL2 URL3            # preview (safe, moves nothing)
python reorganize.py URL1 URL2 URL3 --apply    # execute

Supported Platforms

Platform Status
macOS (Apple Silicon & Intel) Fully supported
Linux (Ubuntu, Debian, Arch, Fedora) Fully supported
Windows 10/11 Fully supported
WSL/WSL2 Fully supported

Project Structure

spotify-bulk-downloader/
├── spotidownload.py    # Main CLI — Spotify + YouTube auto-routing downloader
├── mediatools.py       # Shared art/extension helpers (square cover, verify, repair)
├── library.py          # Verify/repair thumbnails + clean junk in an existing library
├── reorganize.py       # Sort an old flat folder into per-playlist crates
├── spotdl_eval.py      # Benchmark harness to tune threads/format
├── ANALYSIS.md         # Full breakdown of what changed and why
├── PRACTICES.md        # Parallel download + failure improvisation (from live crates)
├── setup.sh            # One-time setup script (macOS/Linux)
├── README.md           # This file
├── .gitignore          # Keeps downloads/, logs/, cookies out of git
├── logs/               # Per-session logs + JSON manifest (for --resume)
└── downloads/          # Your downloaded music (created automatically)
    ├── My House Set/            # one folder per playlist
    │   ├── Artist - Song.mp3
    │   └── Artist - Song.mp3
    ├── My House Set.m3u8        # importable crate for that playlist
    ├── YouTube/                 # direct YouTube downloads
    ├── Recovered from YouTube/  # tracks Spotify couldn't match
    └── errors.txt               # Failed downloads log

Contributing

Found a bug? Have a feature idea? Open an issue or submit a PR.

  1. Fork the repo
  2. Create your branch: git checkout -b feature/my-feature
  3. Make your changes
  4. Submit a pull request

License

MIT License. See LICENSE for details.

Use this tool responsibly. Support the artists you love by attending their shows, buying merch, and purchasing music when you can.


Credits

Built on top of these incredible open-source projects:

  • spotdl — Spotify metadata + YouTube matching
  • yt-dlp — YouTube audio downloader
  • ffmpeg — Audio conversion & metadata embedding

Made with love for the DJ community. Take back your music.

About

Spotify + YouTube bulk music downloader for DJs — auto-routing, parallel, DJ-ready MP3 crates

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages