c131bf6ab1ffa018a95a99d514af4168590bfefb
QTransfer
Spotify -> Qobuz playlist transfer tool in Go.
Features
- Spotify OAuth login via Authorization Code + PKCE (client secret not required).
- Manual Spotify callback code entry (paste callback URL/code) enabled by default.
- Session cache for Spotify/Qobuz credentials and tokens (so you do not need to re-enter each run).
- Fetches all Spotify playlists and liked songs.
- Playlist URL mode (
--playlist-url) for direct targeted transfers. - Monitor mode to detect playlist updates (
--monitor) with optional sync-to-Qobuz (--monitor-transfer) and sync modes (--sync-mode). - Interactive selection prompt (or non-interactive flags).
- Creates Qobuz playlists and fills them with best-effort track matches.
- Transfers liked songs into a dedicated playlist (not favorites).
- Writes a JSON transfer report with unmatched tracks and errors.
Requirements
- Go 1.22+
- Spotify app client ID with redirect URI configured (default:
http://127.0.0.1:8888/callback) - Qobuz account username/password
Build
go build ./cmd/qtransfer
Usage
./qtransfer login
qtransfer login runs an interactive setup and stores Spotify/Qobuz credentials/tokens in the session file.
After login, run transfers without re-entering credentials:
./qtransfer
For first-time non-interactive usage (without login), you can still pass flags:
./qtransfer \
--spotify-client-id "<spotify-client-id>" \
--qobuz-username "<qobuz-user>" \
--qobuz-password "<qobuz-password>"
Credentials/tokens are cached in ~/.config/qtransfer/session.json by default.
Useful flags
--all: transfer all Spotify playlists--liked: include liked songs as a generated Qobuz playlist--playlist "Name"(repeatable): transfer specific playlists by exact name--playlist-url "..."(repeatable): transfer specific Spotify playlists by URL/URI/ID--config sync.toml: TOML config with global options and per-playlist monitor sync jobs--spotify-manual-code=true|false: paste callback URL/code manually or use local callback server--remember-creds=true|false: persist/reuse tokens and credentials in session file--session-file path: custom session file path (default~/.config/qtransfer/session.json)--monitor: monitor selected playlists for updates--monitor-interval 5m: monitor polling interval--monitor-once: run one monitor check and exit--monitor-transfer: in monitor mode, sync changed playlists to Qobuz--sync-mode append|mirror: append only, or mirror source by recreating target playlist--target-playlist-id 123456: bind single monitored source playlist to an existing Qobuz playlist ID--qobuz-self-test: run Qobuz login/verify/search checks and exit (skips Spotify)--qobuz-self-test-write: when self-test is enabled, also create a test playlist and add one track--qobuz-self-test-query "...": search query used during self-test--non-interactive: fail instead of prompting when no explicit selection is given--dry-run: resolve matches only, do not create playlists or add tracks--report transfer-report.json: report output path--public-playlists: create public Qobuz playlists
Quick auth check without waiting for Spotify:
./qtransfer \
--qobuz-self-test \
--qobuz-username "<qobuz-user>" \
--qobuz-password "<qobuz-password>"
Transfer from direct Spotify playlist URLs:
./qtransfer \
--spotify-client-id "<spotify-client-id>" \
--qobuz-username "<qobuz-user>" \
--qobuz-password "<qobuz-password>" \
--playlist-url "https://open.spotify.com/playlist/37i9dQZF1DX0XUsuxWHRQd" \
--playlist-url "spotify:playlist:37i9dQZF1DWY4xHQp97fN6"
Monitor selected playlists for changes:
./qtransfer \
--spotify-client-id "<spotify-client-id>" \
--playlist-url "https://open.spotify.com/playlist/37i9dQZF1DX0XUsuxWHRQd" \
--monitor --monitor-interval 2m
Monitor with append-only sync (never removes from Qobuz):
./qtransfer \
--playlist-url "https://open.spotify.com/playlist/37i9dQZF1DX0XUsuxWHRQd" \
--monitor --monitor-transfer --sync-mode append --monitor-interval 2m
Monitor with mirror sync (recreates playlist on change):
./qtransfer \
--playlist-url "https://open.spotify.com/playlist/37i9dQZF1DX0XUsuxWHRQd" \
--monitor --monitor-transfer --sync-mode mirror --monitor-interval 2m
Config-file mode:
./qtransfer --config sync.toml
Example sync.toml:
[global]
monitor = true
monitor_transfer = true
monitor_interval = "10m"
sync_mode = "append"
include_liked = true
[[playlist]]
url = "https://open.spotify.com/playlist/37i9dQZF1DX0XUsuxWHRQd"
sync_mode = "append"
target_playlist_id = 61646089
[[playlist]]
url = "spotify:playlist:37i9dQZF1DWY4xHQp97fN6"
sync_mode = "mirror"
enabled = true
Login command:
./qtransfer login
Logout command (removes cached session):
./qtransfer logout
Environment variables
SPOTIFY_CLIENT_IDSPOTIFY_REDIRECT_URI(optional)SPOTIFY_SCOPES(optional)QTRANSFER_SPOTIFY_MANUAL_CODE(optional, defaults to true)QTRANSFER_SESSION_FILE(optional)QTRANSFER_REMEMBER_CREDS(optional, defaults to true)QTRANSFER_CONFIG(optional)QTRANSFER_MONITOR(optional)QTRANSFER_MONITOR_ONCE(optional)QTRANSFER_MONITOR_TRANSFER(optional)QTRANSFER_MONITOR_INTERVAL(optional)QTRANSFER_SYNC_MODE(optional: append|mirror)QTRANSFER_TARGET_PLAYLIST_ID(optional, monitor mode only)QTRANSFER_QOBUZ_SELF_TEST(optional)QTRANSFER_QOBUZ_SELF_TEST_WRITE(optional)QTRANSFER_QOBUZ_SELF_TEST_QUERY(optional)QOBUZ_USERNAMEQOBUZ_PASSWORDQOBUZ_APP_ID(optional, defaults to reverse-engineered app id)QOBUZ_APP_SECRET(optional, defaults to reverse-engineered app secret)
Description
Languages
Go
100%