# 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 ```bash go build ./cmd/navimigrate ``` ## Usage ```bash ./navimigrate \ --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): ```bash ./navimigrate \ --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: ```bash ./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): ```bash ./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: ```bash ./navimigrate \ --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-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: ```bash ./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)