Bulk-convert Exportify CSV exports into Navidrome playlists via the Subsonic API. Fuzzy-matches tracks. Retries unmatched songs on weekly runs.
Process a single CSV or an entire directory of 100+ Exportify playlists in one command.
Two-pass Levenshtein search — title + artist weighted scoring. Handles typos, remasters, and alternate versions.
Persistent state file tracks every unmatched track. Re-run weekly as your library grows — previously unmatched songs are retried automatically.
Includes a run.sh wrapper and setup_cron.sh for hands-free weekly runs via WSL cron or Windows Task Scheduler.
Exports every track that couldn't be matched to a CSV. Use it to identify what's missing from your local library.
Pure Go standard library — no external runtime deps. Single static binary for Linux, macOS, and Windows.
Reads Exportify exports. Extracts Track Name, Artist, Album, and Spotify URI.
Calls Navidrome's search3 API. Two passes: title-only, then artist + title.
Levenshtein similarity: title (60%) + artist (40%). Accepts matches above threshold.
Matched song IDs sent to createPlaylist. Large playlists chunked to 200 songs.
Matched URIs + unmatched tracks persisted to JSON. Next run retries the unmatched.
Requires Go 1.22+ and a running Navidrome instance.
# Clone and build
git clone https://github.com/justsky/spotsonic
cd spotsonic
go build -o spotsonic .
go install github.com/justsky/spotsonic@latest
Pre-built binaries for Linux, macOS, and Windows are available on the Releases page.
| Flag | Default | Description |
|---|---|---|
-server required |
— | Navidrome URL, e.g. http://localhost:4533 |
-user required |
— | Navidrome username |
-password required |
— | Navidrome password |
-input |
. |
CSV file or directory of CSV files |
-state |
spotsonic-state.json |
State file for incremental updates |
-threshold |
0.80 |
Fuzzy match threshold 0.0–1.0. Lower = more matches, higher = stricter. |
-dry-run |
false |
Preview matches without creating or modifying playlists |
-report |
— | Write unmatched tracks to this CSV file |
spotsonic \ -server http://localhost:4533 \ -user admin \ -password secret \ -input ./spotify_playlists/ \ -state spotsonic-state.json \ -report unmatched.csv
SpotSonic detects first-run vs update automatically from the state file. Run the exact same command weekly.
spotsonic \ -server http://localhost:4533 \ -user admin \ -password secret \ -input ./spotify_playlists/ \ -state spotsonic-state.json \ -report unmatched.csv
Each candidate from Navidrome is scored by combining title and artist similarity.
Similarity is computed with Levenshtein edit distance after normalizing both strings (lowercase, strip punctuation, collapse whitespace). A two-pass search strategy maximizes recall:
# Pass 1: broad — search by title, score by artist search3(query="Blinding Lights", songCount=20) # Pass 2: narrow — if no match, try artist + title combined search3(query="The Weeknd Blinding Lights", songCount=20)
Tuning:
default threshold 0.80 is a good balance.
Lower to 0.70 for more aggressive matching (may produce false positives).
Raise to 0.90 for strict matching (more tracks left unmatched but no wrong songs).
The state file is what makes weekly runs work. It never adds duplicates and always picks up where it left off.
All Spotify URIs from Exportify
URI → Navidrome ID map
+ unmatched list
Playlist updated
no duplicates
What happens each run:
[update] "Chill Mix" — retry 8 unmatched, 3 new tracks ✓ (retry) Blinding Lights — The Weeknd (91%) # now in library ✓ (retry) Levitating — Dua Lipa (95%) ✗ (retry) Some Obscure Track — Artist (best 38%) # still missing ✓ (new) New Song — Artist (89%) # added to Spotify playlist added 3 songs to playlist id=a1b2c3d4
If a Navidrome playlist is accidentally deleted, SpotSonic finds it by name via getPlaylists
or recreates it from the full matched-URIs history in the state file.
Run SpotSonic automatically every week so new songs added to your library are picked up without any manual effort.
Edit scripts/run.sh with your Navidrome credentials, then install the cron job:
# 1. Make scripts executable chmod +x scripts/run.sh scripts/setup_cron.sh # 2. Install weekly cron (Monday 09:00) bash scripts/setup_cron.sh # 3. Edit crontab to set your password crontab -e # 4. Verify it's installed crontab -l
Or add it manually — this runs every Monday at 09:00:
0 9 * * 1 NAVIDROME_PASSWORD='secret' /path/to/spotsonic/scripts/run.sh
WSL cron may need to be started manually. Add sudo service cron start to ~/.bashrc
or configure it via /etc/wsl.conf.
Run SpotSonic through WSL using Windows Task Scheduler (run as Administrator):
$action = New-ScheduledTaskAction ` -Execute "wsl.exe" ` -Argument "-e bash /mnt/d/Github/SpotSonic/scripts/run.sh" $trigger = New-ScheduledTaskTrigger ` -Weekly -DaysOfWeek Monday -At 9am Register-ScheduledTask ` -TaskName "SpotSonic Weekly" ` -Action $action ` -Trigger $trigger ` -RunLevel Highest
Set NAVIDROME_PASSWORD inside scripts/run.sh before registering the task.
Logs are written to spotsonic.log in the project directory.