GitHub - Ssenseii/harmoni: A modular, Python-based command-line application for downloading music from Spotify exports using yt-dlp. Supports interactive menu navigation, system resource checks, pending download tracking, and logging.

10 min read Original article β†—

🎢 HARMONI

A modular, Python-based command-line tool for downloading music from Spotify & Youtube using yt-dlp.
Features interactive menus, system checks, download management, metadata embedding, robust logging, and Spotify Web API (OAuth PKCE) playlist/liked-song loading.


Screenshots

Downloader Downloading


πŸ“Œ Features

NEW: Spotify Web API (OAuth PKCE) β€” authenticate and download directly from your Spotify playlists and Liked Songs

  • Enhanced Interactive CLI menus for downloads, management, and automation
  • Batch and single downloads pick and choose what you prefer
  • Playlist file download: using the playlist file.
  • Download from Exportify CSVs that you can get from Exportify
  • Download From Youtube paste the link and download playlist or video as audio file.
  • Flexible playlist downloads: whole playlists at once or individual ones.
  • System resource checks (CPU, RAM, storage)
  • Track management via JSON files (pending, failed, history)
  • Download by artist and song name
  • Configurable settings in config.json
  • metadata embedding for downloaded music
  • Retry failed downloads
  • Duplicate detection and file organization
  • Colorful terminal logs (via colorama)
  • Persistent logging to app.log
  • Modular, maintainable codebase
  • Export library data as JSON with detailed track and album info.
  • Clean up music library by removing broken, or unreadable tracks.
  • Choosing audio format and target bitrate w/ quality and size impacts.

🎧 Spotify Web API (NEW) β€” Setup + Authentication (Recommended)

HARMONI can now load tracks directly from your Spotify account (playlists + liked songs) via the Spotify Web API.

What you need

  • A Spotify account
  • (Recommended) a Spotify Developer app Client ID
  • A Redirect URI (default: http://127.0.0.1:8888/callback)

This project uses Authorization Code + PKCE, so no client secret is required.

1) Create a Spotify app (recommended)

  1. Go to https://developer.spotify.com/dashboard
  2. Create an app (or select an existing app)
  3. In the app settings, add this Redirect URI exactly (must match byte-for-byte):
    • http://127.0.0.1:8888/callback
  4. Copy your Client ID
  5. Paste it into your config:
    • spotify_client_id in spotify-yt-dlp-downloader/config.json

2) Configure Spotify API settings in config.json

These are the key settings used by the Spotify Web API workflow:

{
  "spotify_client_id": "", 
  "spotify_redirect_uri": "http://127.0.0.1:8888/callback",
  "spotify_scopes": [
    "playlist-read-private",
    "playlist-read-collaborative",
    "user-library-read"
  ],
  "spotify_cache_tokens": true,
  "spotify_auto_refresh": true
}

Notes:

  • spotify_scopes controls what Spotify permissions you request.
    • The defaults support reading your playlists and your liked songs.
  • Token caching is stored at data/spotify_tokens.json when enabled.
  • Spotify no longer allows localhost as a redirect URI; use a loopback IP like 127.0.0.1.

3) Authenticate (OAuth PKCE) in the app

  1. Run the app
  2. Go to: Downloads Menu β†’ Spotify Web API (OAuth) β€” Playlists / Liked Songs
  3. Choose: Authenticate with Spotify (OAuth PKCE)
  4. The app will show an authorize URL and offer to open it in your browser
  5. After you approve, Spotify redirects to your spotify_redirect_uri
  6. HARMONI starts a tiny local callback server and should auto-capture the redirect.
    • If the callback server cannot start (port in use / restricted environment), it will fall back to asking you to paste the full redirect URL.

After authentication, HARMONI caches the token (if enabled) and will reuse/refresh it automatically.

4) Download from playlists or liked songs

From the same Spotify menu:

  • Download from my playlists
  • Download from liked songs

You’ll be prompted to:

  • pick playlists (checkbox UI)
  • optionally cap the maximum tracks loaded (for large libraries)
  • choose tracks to download using the per-playlist song selection UI

πŸ›  Spotify Export downloads (Legacy inputs)

If you are using the Spotify Web API workflow above, you can skip this section.

If you prefer file-based workflows (CSV/JSON), HARMONI still supports Spotify exports via either Exportify or Spotify’s official data export.

NOTE: You have two options for getting Spotify data into the application:

  1. Official Spotify Data Export (detailed below)
  2. Exportify (recommended alternative - no waiting for email)

Option 1: Official Spotify Data Export

Before using HARMONI for spotify downloads, you can request your personal Spotify data from Spotify's Privacy page. Spotify will provide you with a ZIP file containing several JSON files, including one named YourLibrary.json.

This YourLibrary.json file contains your saved tracks, albums, and playlists metadata, which HARMONI can use to generate the track list and manage downloads.

How to get your Spotify data:

 Go to Spotify's Privacy Request page.

 Request your personal data export.

 Spotify will email you a ZIP file when ready.

 Extract the ZIP and locate YourLibrary.json.

 Use or convert this JSON file as the basis for your data/tracks.json to run HARMONI.

Option 2: Exportify (Recommended Alternative)

Exportify is a simpler, faster way to get your Spotify playlists without waiting for Spotify's email response.

How to use Exportify:

 Go to https://exportify.net/

 Click "Login with Spotify" and authorize the application

 Click "Export" next to your chosen playlist

 Save the CSV file to the data/exportify/ folder in your project

Exportify downloads are immediately available and don't require the waiting period associated with official Spotify data exports.

This step is essential to generate the input data HARMONI needs for downloading your favorite music.


πŸ“‚ Project Structure

spotify-ytdlp/
β”‚
β”œβ”€β”€ main.py                # Entry point, interactive menus
β”œβ”€β”€ config.py              # Loads config from config.json
β”œβ”€β”€ config.json            # User-configurable settings
β”œβ”€β”€ requirements.txt       # Dependencies
β”œβ”€β”€ changelog.md           # change log
β”œβ”€β”€ app.log                # Log file
β”œβ”€β”€ todo.md                # Development notes
β”œβ”€β”€ constants.py           # constants
β”‚
β”œβ”€β”€ history/
β”‚   └── prototype.py       # First version of this entire app 

β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ exportify              # Directory where you should place your exportify csv files
β”‚   β”œβ”€β”€ tracks.json            # Track list (with artist, album, track, uri)
β”‚   β”œβ”€β”€ playlists.json         # (Optional/legacy) playlists export format
β”‚   β”œβ”€β”€ failed_downloads.json  # Tracks that failed to download
β”‚   └── download_history.json  # Downloaded tracks history
β”‚   └── spotify_tokens.json    # (Optional) cached Spotify OAuth token (Spotify Web API)

β”œβ”€β”€ export/
β”‚   β”œβ”€β”€ potyy_export_(MDY).json  # export of tracks in music folder
β”‚   └── playlist_tracklist.json  # playlist in tracks format

β”œβ”€β”€ downloader/
β”‚   β”œβ”€β”€ base_downloader.py                # Download logic (single, batch)
β”‚   β”œβ”€β”€ playlist_download.py              # Download playlists
β”‚   β”œβ”€β”€ metadata.py                       # Embed metadata
β”‚   β”œβ”€β”€ retry_manager.py                  # Retry failed downloads
β”‚   β”œβ”€β”€ youtube_link_downloader.py        # Download Directly from youtube link
β”‚   └── __init__.pyβ”‚

β”œβ”€β”€ menus/                     # Interactive menu modules
β”‚   β”œβ”€β”€ automation_menu.py     # Menu for automation section
β”‚   β”œβ”€β”€ downloads_menu.py      # Menu for downloads section
β”‚   β”œβ”€β”€ main_menu.py           # Menu for main section
β”‚   β”œβ”€β”€ management_menu.py     # Menu for management section
β”‚   β”œβ”€β”€ tools_menu.py          # Menu for tools section
β”‚   └── __init__.py
+
+β”œβ”€β”€ spotify_api/                # Spotify Web API (stdlib-only) OAuth PKCE + client + loaders

β”œβ”€β”€ tools/
β”‚   β”œβ”€β”€ choose_audio_format.py      # pick global format for download
β”‚   β”œβ”€β”€ compress_music.py           # compress songs to a certain format
β”‚   β”œβ”€β”€ dependency_check.py         # check if your dependencies are installed
β”‚   β”œβ”€β”€ library_cleanup.py          # deletes broken track files
β”‚   β”œβ”€β”€ library_export_json.py      # all tracks in music folder as json
β”‚   β”œβ”€β”€ open_log.py                 # opens app.log
β”‚   β”œβ”€β”€ playlist_to_tracklist.py    # playlist turned into tracklist format
β”‚   └── __init__.py

β”œβ”€β”€ managers/
β”‚   β”œβ”€β”€ file_manager.py        # Duplicate detection, file organization
β”‚   β”œβ”€β”€ resume_manager.py      # Resume batch downloads
β”‚   β”œβ”€β”€ schedule_manager.py    # Scheduled downloads
β”‚   └── __init__.py

β”œβ”€β”€ utils/
β”‚   β”œβ”€β”€ logger.py              # Logging utilities
β”‚   β”œβ”€β”€ loaders.py             # Loading utilities
β”‚   β”œβ”€β”€ system.py              # System resource checks
β”‚   β”œβ”€β”€ track_checker.py       # Check downloaded files
β”‚   └── __init__.py

└── music/                 # Downloaded music files

βœ… Prerequisites

  • Python 3.9+
  • ffmpeg available on your system PATH (required for yt-dlp audio extraction/post-processing)
  • Internet connection (YouTube)

Spotify input options (choose one)

  • Spotify Web API (recommended):
    • a Spotify account
    • your own Spotify Developer app Client ID
    • a Redirect URI (default: http://127.0.0.1:8888/callback)
  • Legacy file-based inputs: Exportify CSVs and/or Spotify official export JSON

Prefer not installing system dependencies? Use the Docker deployment below.


βš™οΈ Installation (Python version 3.9)

  1. Clone the repository:

    git clone https://github.com/Ssenseii/spotify-yt-dlp-downloader.git
    cd spotify-ytdlp
  2. Create and activate virtual environment:

    python3 -m venv .venv
    source .venv/bin/activate  # On Windows: .venv\Scripts\activate
  3. Install dependencies:

    python3 -m pip install -r requirements.txt

(if you use system check it'll say yt-dlp is not installed even if it is so don't worry about that until I fix that check).

  1. Ensure yt-dlp is installed:

    python3 -m pip install yt-dlp

πŸš€ Quick Start with start.sh

For an effortless setup and launch experience, use the provided start.sh script:

This script automatically handles:

  • Virtual Environment Creation: If .venv doesn't exist, it creates one for you
  • Dependency Installation: Automatically installs all required packages from requirements.txt when creating a new virtual environment
  • Application Launch: Activates the virtual environment and starts the application

No manual setup required! The script will guide you through the process with clear status messages.


🐳 Docker Deployment

If you'd rather not install Python/ffmpeg locally, you can run HARMONI fully inside Docker. The provided Docker setup includes system dependencies like ffmpeg and runs the same interactive questionary menus.

Prerequisites

  • Docker
  • Docker Compose (recommended)

Option A: Docker Compose (recommended)

From the spotify-yt-dlp-downloader/ folder:

docker compose build
docker compose run --rm --service-ports spotify-yt-dlp-downloader

If you have an older Docker setup, docker-compose may be the correct command.

Option B: Docker only

docker build -t spotify-yt-dlp-downloader .

docker run --rm -it \
  -v "$(pwd)/data:/app/data" \
  -v "$(pwd)/music:/app/music" \
  -v "$(pwd)/export:/app/export" \
  -v "$(pwd)/config.json:/app/config.json" \
  -e TERM=xterm-256color \
  spotify-yt-dlp-downloader

Persistent data (volume mounts)

The examples above mount local folders/files into the container so your library survives container restarts:

  • ./data β†’ /app/data (tracks/playlists/history/failed + data/exportify/*.csv)
  • ./music β†’ /app/music (downloaded audio)
  • ./export β†’ /app/export (JSON exports, etc.)
  • ./config.json β†’ /app/config.json (your settings)

Common commands

# Rebuild after code/dependency changes
docker compose build --no-cache

# Run again (interactive menu)
docker compose run --rm spotify-yt-dlp-downloader

# Open a shell inside the container (for debugging)
docker compose run --rm --entrypoint /bin/sh spotify-yt-dlp-downloader

Notes (interactive mode)

  • This is an interactive CLI app, so you need a TTY:
    • Compose already sets stdin_open: true and tty: true
    • With plain Docker, always use -it
  • If the checkbox menus don’t respond, try a real terminal (not a basic IDE console) and ensure TERM is set (see examples above).

βš™οΈ Upgrade guide

If the system fails to donwload your music too frequently, make sure to run this command

```bash
python3 -m pip install --upgrade yt-dlp
```

πŸ“„ Configuration

Edit config.json to set your preferences:

{
  "tracks_file": "data/tracks.json",
  "playlists_file": "data/playlists.json",

  "output_dir": "music",
  "audio_format": "mp3",
  "sleep_between": 5,
  "average_download_time": 20,

  "spotify_client_id": "",
  "spotify_redirect_uri": "http://127.0.0.1:8888/callback",
  "spotify_scopes": [
    "playlist-read-private",
    "playlist-read-collaborative",
    "user-library-read"
  ],
  "spotify_cache_tokens": true,
  "spotify_auto_refresh": true
}

🎡 Track List Format

data/tracks.json should contain:

{
	"tracks": [
		{
			"artist": "Artist Name",
			"album": "Album Name",
			"track": "Song Title",
			"uri": "spotify:track:xxxx"
		}
	]
}

🎡 Playlist Format

data/playlists.json should contain:

{
  "playlists": [
    {
      "name": "Simon vol.94",
      "lastModifiedDate": "2025-05-03",
      "items": [
        {
          "track": {
            "trackName": "Time of Our Lives",
            "artistName": "Pitbull",
            "albumName": "Globalization",
            "trackUri": "spotify:track:2bJvI42r8EF3wxjOuDav4r"
          },
          "episode": null,
          "audiobook": null,
          "localTrack": null,
          "addedDate": "2025-05-03"
        },
      ]
   }]
}

▢️ Usage

Run the program:

# First activate the virtual environment
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Then run the program
python3 main.py

or

# Just do everything for me...
./start.sh

You will see a menu with options for downloading, checking files, importing playlists, retrying failed downloads, and more.


Here's the updated Dependencies section including mutagen and schedule:


πŸ›  Dependencies

  • yt-dlp - YouTube downloader
  • psutil - System resource monitoring
  • colorama - Colored terminal output
  • mutagen - Audio metadata tagging and manipulation
  • schedule - Job scheduling for periodic tasks
  • Questionar - for the interactive menu
  • Shutil - for systems stuff

Install all:

python3 -m pip install -r requirements.txt

or


πŸ“œ Logging

  • Terminal output is colored for readability
  • app.log stores a full log history of actions, warnings, and errors

⚠️ Disclaimer

This tool is for personal use only.
Ensure you respect copyright laws and YouTube's terms.

License

This project is licensed under the MIT License β€” see the LICENSE file for details.