A Spotify to Plex Synchronization and download for offline listening with SpotDL.
7.0K
ā ļø This is a breaking change that drops automatic Plex playlist management in favor of Plex Smart Playlists
Plexify automatically syncs Spotify playlists to your local filesystem, downloading tracks using YouTube search + yt-dlp. Files are organized by playlist, then artist and album for seamless Plex library integration. Use Plex Smart Playlists (filtered by folder path) to surface these files in Plex.
MUSIC_PATH/<Playlist>/<Artist>/<Album>/<Track>.flacsnapshot_id to detect playlist changes and minimize API callsREMOVED:
spotdl dependency (replaced with YouTube search + yt-dlp)NEW APPROACH:
MUSIC_PATH/<Playlist>/<Artist>/<Album>/Why this change?
spotdl was downloading wrong tracks due to lyrics processing corruptionFor the 1-star deletion feature to work, your Plex library structure must match Spotify playlist names.
Create separate Plex libraries for each Spotify playlist you want to sync
Stoner.Blues.Rock ā Plex library Stoner.Blues.RockPoint each library to its corresponding folder
Stoner.Blues.Rock ā Folder /data/Music/Stoner.Blues.RockChill.Vibes ā Folder /data/Music/Chill.VibesHow it works:
Spotify Playlists:
Stoner.Blues.RockChill.VibesWorkout MixFile Structure:
/data/Music/
āāā Stoner.Blues.Rock/
ā āāā Black Sabbath/...
āāā Chill.Vibes/
ā āāā Tycho/...
āāā Workout Mix/
āāā Run The Jewels/...
Plex Libraries (Settings ā Libraries ā Add Library):
Stoner.Blues.Rock ā Folder: /data/Music/Stoner.Blues.RockChill.Vibes ā Folder: /data/Music/Chill.VibesWorkout Mix ā Folder: /data/Music/Workout MixOne-time setup per playlist:
Music<MUSIC_PATH>/<PlaylistName>Example Smart Playlist filters:
Folder contains /data/Music/Stoner.Blues.RockFolder contains /data/Music/Rock AND Genre is RockFolder contains /data/Music AND Artist is AC/DCAs Plexify downloads new tracks, your Smart Playlists auto-update. No API calls needed!
| Variable | Description | Required | Default |
|---|---|---|---|
SPOTIPY_CLIENT_ID | Your Spotify application client ID. | Yes | |
SPOTIPY_CLIENT_SECRET | Your Spotify application client secret. | Yes | |
SPOTIFY_REFRESH_TOKEN | OAuth refresh token for playlist modifications. See OAuth Setupā . | Yes* | |
SPOTIFY_URIS | Comma-separated Spotify URIs (user or playlist). | Yes | |
MUSIC_PATH | Absolute path where music files are stored/downloaded. | Yes | /music |
PLEX_URL | Full URL for your Plex server (e.g., http://localhost:32400ā ). | Yes | |
PLEX_TOKEN | Your Plex authentication token. | Yes |
*Required for 1-star deletion feature. Without it, the app runs in read-only mode (downloads work, but track removal from Spotify playlists won't).
| PREFER_FLAC | Download FLAC when available, MP3 fallback (true/false). | No | true |
| SECONDS_TO_WAIT | Seconds to wait between sync cycles. | No | 3600 |
| LOG_LEVEL | Python logging level (DEBUG, INFO, WARNING, ERROR). | No | INFO |
| DOWNLOAD_DELAY | Seconds to wait between track downloads (rate limiting). | No | 0.1 |
PREFER_FLAC
Controls download format preference:
true (default): Downloads FLAC when available, falls back to MP3 if FLAC failsfalse: Downloads MP3 only (saves storage space)File detection always checks for both FLAC and MP3 regardless of this setting - won't re-download if either format exists.
SPOTIPY_CLIENT_ID & SPOTIPY_CLIENT_SECRET
Your personal Spotify API credentials. These are automatically passed to prevent rate limiting. Without your own credentials, the app uses shared keys that quickly hit rate limits.
DOWNLOAD_DELAY
Time to wait between each track download. Default is 0.1 seconds (10 requests/second), which respects Spotify's recommended rate limit. Increase this value if you still encounter rate limiting (e.g., 0.2 for 5 req/sec).
Example SPOTIFY_URIS:
spotify:user:USERNAME,spotify:playlist:PLAYLIST_ID,spotify:playlist:ANOTHER_ID
ā ļø Important: Use your own credentials to avoid rate limiting!
http://127.0.0.1:8888/callback (required for OAuth setup)Your credentials are automatically used - no manual configuration needed.
X-Plex-Token= in the URLWhy is this needed?
Spotify's basic "Client Credentials" authentication only allows read-only access. To remove tracks from playlists (1-star deletion feature), you need OAuth user authorization.
The solution: Generate a refresh token once on your local machine, then use it forever in Docker.
http://127.0.0.1:8888/callback
Run this on your local machine (not on the NAS/Docker host) - it requires a browser:
# Clone or download the repo
cd Plexify
# Set your credentials
export SPOTIPY_CLIENT_ID="your_client_id"
export SPOTIPY_CLIENT_SECRET="your_client_secret"
# Run the token generator
python generate_spotify_token.py
A browser window will open ā Log in to Spotify ā Authorize the app ā The script prints your refresh token.
Copy the printed SPOTIFY_REFRESH_TOKEN value to your docker-compose.yml:
environment:
- SPOTIPY_CLIENT_ID=your_client_id
- SPOTIPY_CLIENT_SECRET=your_client_secret
- SPOTIFY_REFRESH_TOKEN=AQDxxxxxxxxxxxxxxxx # <-- Add this line
# ... rest of your env vars
docker-compose down && docker-compose up -d
That's it! The refresh token is permanent (doesn't expire unless you revoke access in Spotify settings). You only need to do this once.
What if I don't set SPOTIFY_REFRESH_TOKEN?
The app will run in read-only mode:
export SPOTIPY_CLIENT_ID="your_spotify_client_id"
export SPOTIPY_CLIENT_SECRET="your_spotify_client_secret"
export SPOTIFY_REFRESH_TOKEN="your_refresh_token" # Required for 1-star deletion
export SPOTIFY_URIS="spotify:user:your_username"
export MUSIC_PATH="/data/Music"
export PLEX_URL="http://localhost:32400"
export PLEX_TOKEN="your_plex_token"
export PREFER_FLAC="true" # Optional: FLAC preferred (default), set to "false" for MP3-only
export LOG_LEVEL="INFO"
export DOWNLOAD_DELAY="0.1" # Optional: 10 req/sec (default)
python src/main.py
docker run -d \
--name=plexify \
-e SPOTIPY_CLIENT_ID="your_spotify_client_id" \
-e SPOTIPY_CLIENT_SECRET="your_spotify_client_secret" \
-e SPOTIFY_REFRESH_TOKEN="your_refresh_token" \
-e SPOTIFY_URIS="spotify:user:your_spotify_username" \
-e MUSIC_PATH="/music" \
-e PLEX_URL="http://plex:32400" \
-e PLEX_TOKEN="your_plex_token" \
-e PREFER_FLAC="true" \
-e LOG_LEVEL="INFO" \
-e DOWNLOAD_DELAY="0.1" \
-v /path/to/your/music:/music \
nyancod3r/plexify:latest
MUSIC_PATH/
āāā Stoner.Blues.Rock/ (Playlist 1 = Plex Library 1)
ā āāā Black Sabbath/
ā ā āāā Paranoid/
ā ā ā āāā Black Sabbath - War Pigs.flac
ā ā ā āāā Black Sabbath - Paranoid.mp3
ā ā āāā Master of Reality/
ā ā āāā Black Sabbath - Sweet Leaf.flac
ā āāā Kyuss/
ā āāā Blues for the Red Sun/
ā āāā Kyuss - Thumb.flac
āāā Chill.Vibes/ (Playlist 2 = Plex Library 2)
āāā Tycho/
āāā Dive/
āāā Tycho - A Walk.flac
Structure explained:
MUSIC_PATH - Your root music folderArtist - Track.flac (or .mp3 if FLAC unavailable)File naming convention: Artist - Track.extension
Format priority: FLAC preferred, MP3 fallback - automatic, no configuration needed.
This structure allows you to:
Folder contains /music/Stoner.Blues.Rock)Plexify uses Spotify's snapshot_id to detect playlist changes. This dramatically reduces API calls:
First run: Fetches all playlists and tracks
Subsequent runs: Only checks snapshot IDs (1 API call per playlist)
If unchanged: No additional API calls needed
If changed: Only re-fetches that specific playlist
This prevents rate limiting and saves bandwidth.
Multiple layers of protection against API rate limits:
SPOTIPY_CLIENT_ID and SPOTIPY_CLIENT_SECRET are automatically usedFile Detection (when checking if track exists):
Artist - Track.flacArtist - Track.mp3Download Priority (when downloading new tracks):
bestaudio[ext=flac]/bestaudio[acodec*=flac]/bestaudio[ext=m4a]/bestaudio[ext=webm]/bestaudio/bestExample scenarios:
Why we replaced spotdl:
Download process:
Format Selection:
bestaudio[ext=flac] - Real FLAC if YouTube has itbestaudio[acodec*=flac] - Any FLAC codecbestaudio[ext=m4a] - High-quality AAC for conversionbestaudio[ext=webm] - Usually Opus, good for conversionbestaudio - Any audio-only formatbest[height<=480] - Low-res video as last resort (has audio)Files are organized using Spotify metadata with a 4-level hierarchy:
< > : " / \ | ? *)File naming convention: Artist - Track.extension
Examples:
AC/DC becomes AC_DCStoner.Blues.Rock becomes Stoner.Blues.Rock (dots are valid)My Playlist: Best Of becomes My Playlist_ Best OfBlack Sabbath - War Pigs.flacAll downloaded files are automatically tagged with:
The <Playlist>/<Artist>/<Album>/<Track> structure allows precise Plex Smart Playlist targeting. Point your Plex library at MUSIC_PATH and all playlists will be available for filtering.
Status: Fully implemented and enabled
When you rate a track 1-star in Plex, Plexify will:
Stoner.Blues.Rock)Requirements:
Usage:
Breaking Changes:
SPOTIFY_CLIENT_ID ā SPOTIPY_CLIENT_ID<Playlist>/<Artist>/<Album>/<Track> structure (added playlist folder level)PLEX_URL and PLEX_TOKEN variablesDOWNLOAD_DELAY for rate limit controlWhat stays the same:
Migration steps:
PLEX_URL and PLEX_TOKENMUSIC_PATH to your Plex music library rootspotify_playlists.json will be recreated)<Artist>/<Album>/ will not be moved. New structure is <Playlist>/<Artist>/<Album>/. Consider reorganizing manually or starting fresh.Problem: Logs show Your application has reached a rate/request limit. Retry will occur after: 86400
Root Cause: Too many API calls to Spotify during downloads.
Solutions:
ā Verify your credentials are being used:
# Set LOG_LEVEL to DEBUG
export LOG_LEVEL="DEBUG"
# Run Plexify and check logs for credential usage confirmation
Create your own Spotify credentials if using shared/public keys:
SPOTIPY_CLIENT_ID and SPOTIPY_CLIENT_SECRETIncrease download delay:
export DOWNLOAD_DELAY="0.2" # 5 req/sec instead of 10
Reduce sync frequency:
export SECONDS_TO_WAIT="7200" # 2 hours between syncs
Reduce number of playlists:
SPOTIFY_URISSmart caching (enabled by default) should prevent this on subsequent runs
Problem: Downloaded files don't show up in Plex
Solutions:
MUSIC_PATH matches your Plex library path exactlyMUSIC_PATHls -la $MUSIC_PATH/<Playlist>/<Artist>/<Album>/Problem: Error fetching 1-star tracks from library 'Stoner.Blues.Rock': Invalid library section
Cause: Your Plex library name doesn't match the Spotify playlist name.
Solution:
Example:
Music ā Playlist folder: /data/Music/Stoner.Blues.Rock (Won't work)Stoner.Blues.Rock ā Playlist folder: /data/Music/Stoner.Blues.Rock (Works)Problem: Plexify keeps re-fetching playlists or shows wrong track counts
Solutions:
rm spotify_playlists.jsoncat spotify_playlists.json | python3 -m json.toolProblem: Downloads timeout after 300 seconds
Solutions:
yt-dlp is installed: yt-dlp --versionyt-dlp "ytsearch:Artist - Track Name"Problem: Logs stuck at "Downloading: Artist - Track"
Solution: Set LOG_LEVEL=DEBUG to see detailed yt-dlp output. If still stuck for >5 minutes:
ps aux | grep yt-dlppkill yt-dlpProblem: Application can't find environment variables
Solutions:
echo $SPOTIPY_CLIENT_ID~/.bashrc or ~/.zshrc:
export SPOTIPY_CLIENT_ID="..."
export SPOTIPY_CLIENT_SECRET="..."
# etc.
source ~/.bashrcProblem: App downloads songs that don't match the Spotify track
This should no longer happen with the YouTube search approach. If it does:
LOG_LEVEL=DEBUG to see search queriesspotipy (Spotify API client)yt-dlp (YouTube downloader)youtubesearchpython (YouTube search)plexapi (Plex server integration)mutagen (Audio metadata handling)Install with:
pip install -r requirements.txt
For issues, feature requests, or questions:
Content type
Image
Digest
sha256:b706f752eā¦
Size
122.9 MB
Last updated
8 months ago
docker pull nyancod3r/plexify