From 086dd99eff8f41190685d1beacb148f33b4134c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Skyler=20M=C3=A4ntysaari?= Date: Sun, 16 Aug 2026 13:08:55 +0300 Subject: [PATCH] Update README to reflect status --- README.md | 67 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e3b7ce7..830dcec 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,70 @@ # Music Migrator -A local web app for moving your music library — liked songs, playlists, followed artists, -saved albums — between streaming platforms. Supports Spotify and Tidal as both source and -destination, including merging one Spotify account's library into a different Spotify account. -Last.fm is used to help disambiguate tricky cross-platform matches and as an optional extra -source ("loved tracks"). +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. -See `/home/sky/.claude/plans/plan-an-application-to-wiggly-castle.md` for the full design. +## 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 1. `npm install` -2. Copy `.env.local.example` to `.env.local` and fill in `SPOTIFY_CLIENT_ID` (register an app at - https://developer.spotify.com/dashboard and add the redirect URI shown in the example file). +2. Copy `.env.local.example` to `.env.local` and 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. 3. `npm run dev` -4. Open http://localhost:3000/accounts and connect your Spotify account. +4. Open http://localhost:3000/accounts and connect your accounts. +5. 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.example` are 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. -## Status +## Architecture + +- `lib/adapters/` — one `MusicPlatformAdapter` implementation per platform (Spotify, Tidal, + Last.fm), all normalizing to the canonical types in `lib/types/model.ts` +- `lib/matching/` — the reconciliation engine (`engine.ts` for tracks, `entity-matching.ts` for + artists/albums), scoring, and Last.fm disambiguation +- `lib/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 queue +- `app/` — Next.js App Router pages and API routes + +## Known limitations -Currently implements milestone M1: Spotify OAuth (PKCE) + fetching liked songs. See the plan -doc for the full phased build order (Tidal adapter, matching engine, playlists/artists/albums, -Spotify-to-Spotify merge, Last.fm integration, review UI). +- 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 -- 2.51.2