Skip to content

Initial implementation: Spotify to Tidal playlist migrator - #1

Merged
richplant merged 5 commits into
mainfrom
feat/initial-implementation
Aug 27, 2026
Merged

Initial implementation: Spotify to Tidal playlist migrator#1
richplant merged 5 commits into
mainfrom
feat/initial-implementation

Conversation

@richplant

Copy link
Copy Markdown
Owner

Summary

A desktop GUI (PySide6) for migrating playlists from Spotify to Tidal in three stages:

  • Harvest — paste credentials captured from a logged-in open.spotify.com DevTools session (no Spotify Developer app needed), browse a profile's public playlists, and import the ones you want.
  • Match — fuzzy-match each imported track against Tidal (ISRC first, then artist/title + duration), with a manual override for anything wrong or missing.
  • Push — create or update the matched playlists on Tidal, grouped into a folder, idempotently (safe to re-run).

Commits

  • chore: scaffold project tooling and packaging — package layout, ruff/pre-commit/commitizen, CI, AppImage packaging
  • feat: add core data models and local snapshot store — shared dataclasses, config/data paths, on-disk snapshot storage, text utilities
  • feat: add Spotify client for harvesting playlists — reverse-engineered internal Spotify endpoints (spclient + pathfinder GraphQL)
  • feat: add Tidal client and track matching — OAuth session, fuzzy matching, idempotent push
  • feat: add desktop GUI for the harvest, match, and push workflow — the three-tab PySide6 app

Test plan

  • pytest — 149 tests passing
  • ruff check / ruff format --check — clean
  • Manual smoke test of all three tabs against real Spotify/Tidal accounts during development

Adds the shared dataclasses (playlist/track/match models), the
per-user config/data paths, on-disk JSON snapshot storage for
harvested playlists, and small text-handling utilities (Spotify
user-id parsing, DevTools token extraction, HTML-to-plain-text).
Reverse-engineers the same internal endpoints open.spotify.com's own
web player uses (spclient.wg.spotify.com for a profile's playlist
listing, api-partner.spotify.com's GraphQL "pathfinder" API for a
playlist's tracks), authenticated with a bearer token + client-token
pair copied out of DevTools -- there's no public Web API path left
for this since Spotify locked it down.
Adds OAuth login/session persistence, ISRC-first fuzzy track
matching against Tidal (rapidfuzz + duration sanity check), and
idempotent playlist push (folder/playlist reuse by a locally
recorded id, falling back to name lookup only on first push).
PySide6 app with three tabs backed by background QThread workers:
Harvest (paste DevTools credentials, browse a profile's playlists,
import selected ones), Match (fuzzy-match imported tracks against
Tidal, fix mismatches by hand), and Push (create/update the matched
playlists on Tidal). Columns are user-resizable and auto-fit to
content; selection is native click/ctrl-click/shift-click.
@richplant richplant self-assigned this Aug 27, 2026
@richplant richplant added documentation Improvements or additions to documentation enhancement New feature or request labels Aug 27, 2026
The bare ubuntu-latest runner has none of the shared libraries
PySide6's offscreen platform plugin dynamically links against
(EGL/GL, xkbcommon, fontconfig, dbus) -- pytest failed to even
import PySide6 with "libEGL.so.1: cannot open shared object file".
Same requirement applies to the AppImage smoke test in the build
job, which also runs with QT_QPA_PLATFORM=offscreen.
@richplant
richplant merged commit fc7df92 into main Aug 27, 2026
5 checks passed
@richplant
richplant deleted the feat/initial-implementation branch August 27, 2026 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant