NaviMigrate

Spotify -> Navidrome playlist transfer tool in Go.

What it does

  • Authenticates with Spotify using Authorization Code + PKCE (no client secret required).
  • Fetches one or more Spotify playlists by URL/URI/ID.
  • Optionally transfers Spotify liked songs as a dedicated playlist.
  • Searches for matching tracks in Navidrome using Subsonic search3.
  • Creates matching playlists in Navidrome and adds matched tracks.
  • Writes a JSON transfer report with unmatched tracks and errors.
  • Optional recovery mode: for unmatched tracks, search Qobuz and download whole albums using qobuz-dl.
  • Optional re-add mode: after Navidrome rescan, re-match downloaded tracks and add them to existing playlists.

Requirements

  • Go 1.22+
  • Spotify app client ID with redirect URI configured (default: http://127.0.0.1:8888/callback)
  • If using --liked, Spotify scopes must include user-library-read
  • Navidrome URL, username, and password

Build

go build ./cmd/navimigrate

Usage

./navimigrate \
  --spotify-client-id "<spotify-client-id>" \
  --navidrome-url "https://music.lofitrek.com" \
  --navidrome-username "test_user" \
  --navidrome-password "test_user" \
  --playlist-url "https://open.spotify.com/playlist/16DZpOTLqZvdbqxEavLmWk"

Useful flags

  • --playlist-url "..." (repeatable): Spotify playlist URL/URI/ID
  • --liked: include Spotify liked songs in transfer
  • --liked-playlist-name "Spotify Liked Songs": target playlist name for liked songs
  • --dry-run: resolve matches only, do not create playlists or add tracks
  • --match-threshold 45: minimum score needed to accept a match
  • --remember-spotify=true|false: save and reuse Spotify refresh token (default true)
  • --session-file ~/.config/navimigrate/session.json: session storage path
  • --report transfer-report.json: report output path
  • --concurrency 4: concurrent track match workers
  • --spotify-manual-code=true|false: manual callback URL/code entry or local callback server
  • --qobuz-download-missing: search/download albums for unmatched tracks
  • --qobuz-manifest missing-downloads.json: manifest path used by recovery flows
  • --qobuz-dl-path qobuz-dl: binary path, or qobuz-dl project directory
  • --qobuz-output /path/to/music: output directory for qobuz-dl
  • --add-downloaded-manifest missing-downloads.json: re-add tracks from manifest after Navidrome rescan
  • --add-downloaded-force: retry entries already marked added

Liked-only run (no explicit playlist URLs):

./navimigrate \
  --spotify-client-id "<spotify-client-id>" \
  --navidrome-url "https://music.lofitrek.com" \
  --navidrome-username "test_user" \
  --navidrome-password "test_user" \
  --liked

Navidrome self-test

Quick auth/search test without Spotify:

./navimigrate \
  --navidrome-url "https://music.lofitrek.com" \
  --navidrome-username "test_user" \
  --navidrome-password "test_user" \
  --navidrome-self-test

Write-path self-test (creates playlist, adds one track, then deletes playlist):

./navimigrate \
  --navidrome-url "https://music.lofitrek.com" \
  --navidrome-username "test_user" \
  --navidrome-password "test_user" \
  --navidrome-self-test \
  --navidrome-self-test-write

Missing-track recovery flow

Step 1: transfer and download unmatched albums from Qobuz:

./navimigrate \
  --spotify-client-id "<spotify-client-id>" \
  --navidrome-url "https://music.lofitrek.com" \
  --navidrome-username "test_user" \
  --navidrome-password "test_user" \
  --playlist-url "https://open.spotify.com/playlist/..." \
  --qobuz-download-missing \
  --qobuz-username "<qobuz-email>" \
  --qobuz-password "<qobuz-password>" \
  --qobuz-dl-path "/home/joren/dev/qobuz-dl" \
  --qobuz-output "/path/to/downloads"

Step 2: after Navidrome rescans your library, re-add from manifest:

./navimigrate \
  --navidrome-url "https://music.lofitrek.com" \
  --navidrome-username "test_user" \
  --navidrome-password "test_user" \
  --add-downloaded-manifest "missing-downloads.json"

Environment variables

  • SPOTIFY_CLIENT_ID
  • SPOTIFY_REDIRECT_URI (optional)
  • SPOTIFY_SCOPES (optional)
  • NAVIMIGRATE_SPOTIFY_MANUAL_CODE (optional, defaults to true)
  • NAVIMIGRATE_REMEMBER_SPOTIFY (optional, defaults to true)
  • NAVIMIGRATE_SESSION_FILE (optional)
  • NAVIMIGRATE_INCLUDE_LIKED (optional)
  • NAVIMIGRATE_LIKED_NAME (optional)
  • NAVIDROME_URL
  • NAVIDROME_USERNAME
  • NAVIDROME_PASSWORD
  • NAVIMIGRATE_DRY_RUN (optional)
  • NAVIMIGRATE_CONCURRENCY (optional)
  • NAVIMIGRATE_MATCH_THRESHOLD (optional)
  • NAVIMIGRATE_REPORT (optional)
  • NAVIMIGRATE_QOBUZ_DOWNLOAD_MISSING (optional)
  • NAVIMIGRATE_QOBUZ_MANIFEST (optional)
  • NAVIMIGRATE_QOBUZ_DL_PATH (optional)
  • NAVIMIGRATE_QOBUZ_OUTPUT (optional)
  • NAVIMIGRATE_QOBUZ_QUALITY (optional)
  • QOBUZ_USERNAME (optional, required when qobuz download mode enabled)
  • QOBUZ_PASSWORD (optional, required when qobuz download mode enabled)
  • QOBUZ_APP_ID (optional)
  • QOBUZ_APP_SECRET (optional)
  • NAVIMIGRATE_ADD_DOWNLOADED_MANIFEST (optional)
  • NAVIMIGRATE_ADD_DOWNLOADED_FORCE (optional)
  • NAVIMIGRATE_NAVIDROME_SELF_TEST (optional)
  • NAVIMIGRATE_NAVIDROME_SELF_TEST_WRITE (optional)
  • NAVIMIGRATE_NAVIDROME_SELF_TEST_QUERY (optional)
Description
No description provided
Readme 62 KiB
Languages
Go 100%