Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
TypeScript 93%
CSS 7%
JavaScript <1%
README.md
Music Migrator #
A local web app for moving your music library between streaming platforms: liked songs, playlists (with track order preserved), followed artists, and saved albums. Supports Spotify and Tidal as both source and destination — including merging one Spotify account's library into a different Spotify account — and Last.fm as a read-only source (loved tracks) plus an assist for disambiguating tricky cross-platform matches.
Features #
- Spotify ↔ Tidal, either direction, for liked songs / playlists / followed artists / saved albums
- Spotify → Spotify account merge (union of both libraries, deduplicated)
- Last.fm as an additional read-only source (loved tracks), and as a matching assist
- Matching engine: ISRC exact match → cached decision reuse → fuzzy title/artist/duration scoring → Last.fm-assisted rescoring for medium-confidence matches → manual review queue
- Review queue: ambiguous matches are held for you to confirm, pick an alternate, search manually, or skip — not silently dropped
- Live progress over SSE, with per-category stats (matched / already present / needs review / failed)
- Resumable: safely rerunnable (dedups against the destination's live state every run), and a job orphaned by a server restart is automatically relaunched from where it left off
- Rate-limit aware: automatic retry with backoff on 429s, capped so a long ban fails fast instead of hanging
Setup #
npm install- Copy
.env.local.exampleto.env.localand fill in credentials for whichever platforms you want to use (Spotify is required as a baseline; Tidal and Last.fm are optional). The example file has the exact redirect URIs and scope lists each platform needs. npm run dev- Open http://localhost:3000/accounts and connect your accounts.
- Go to http://localhost:3000/migrate/new to start a migration.
Platform notes #
- Spotify: register an app at https://developer.spotify.com/dashboard. Uses OAuth Authorization Code + PKCE (no client secret).
- Tidal: register an app at https://developer.tidal.com. Tidal only grants scopes explicitly
approved in the app's dashboard settings — requesting an unapproved scope fails the whole
authorize request, not just that scope, so make sure the scopes listed in
.env.local.exampleare all added there first. - Last.fm: create an API account at https://www.last.fm/api/account/create. Uses Last.fm's own web-auth flow (predates PKCE/OAuth2), so there's no redirect URI to register in advance. Last.fm is read-only — it can be a migration source, never a destination.
Connected account tokens and job state are stored locally in ./data/app.db (SQLite), with
OAuth tokens encrypted at rest using a key in ./data/.master-key. Both are gitignored.
Architecture #
lib/adapters/— oneMusicPlatformAdapterimplementation per platform (Spotify, Tidal, Last.fm), all normalizing to the canonical types inlib/types/model.tslib/matching/— the reconciliation engine (engine.tsfor tracks,entity-matching.tsfor artists/albums), scoring, and Last.fm disambiguationlib/jobs/— the migration runner, one scope-runner per library category, SSE progress emitter, and the startup sweep that resumes orphaned jobs (instrumentation.ts)lib/db/— SQLite persistence: accounts (encrypted tokens), jobs, match cache (so reruns don't re-ask about the same track), review queueapp/— Next.js App Router pages and API routes
Known limitations #
- Playlist/account-merge dedup is name-based for playlists and canonical-ID-based for tracks/artists/albums — not a perfect identity match in every edge case
- No automated tests beyond the matching engine (
tests/matching/) — everything else has been verified manually against real accounts - Tidal's Open API is in beta; endpoint behavior may shift upstream
Scripts #
npm run dev— start the dev servernpm run build/npm start— production build/runnpm test— run vitest unit tests