Bidirectional sync for Spotify liked songs ↔ YouTube Music liked songs
Dry-run by default • No API keys • Session auth only
One-shot (recommended):
uvx music-liked-sync --helpPermanent install:
uv tool install music-liked-syncClassic:
pip install music-liked-syncKeeps your liked-songs libraries in sync across Spotify and YouTube Music.
- Bidirectional — defaults to both directions; run one-way with flags
- Dry-run by default — preview every change before applying (
--applyrequired) - No API keys — uses live browser sessions (Playwright) for both services
- Stateful — SQLite cache remembers match mappings so re-runs are fast
- Safe — lock files prevent concurrent browser sessions; conservative matching
uv tool install music-liked-sync
uv run playwright install chromium # first time onlyOn first run the tool opens Chromium for each service. Log in once — sessions persist.
# Spotify session
music-liked-sync --dry-run # opens https://open.spotify.com, log in, close
# YouTube Music session
music-liked-sync --dry-run # opens https://music.youtube.com, log in, closeSession profiles are stored in
auth/spotify-web-session/andauth/ytmusic-browser-session/.
auth/is.gitignored and should never be committed.
music-liked-syncShows what would be added in each direction without writing anything.
music-liked-sync --apply# Spotify → YouTube Music only
music-liked-sync --spotify-to-ytm --apply
# YouTube Music → Spotify only
music-liked-sync --ytm-to-spotify --apply# Smaller batches, slower pace
music-liked-sync --batch-size 25 --batch-delay 2 --apply
# Cap per-direction additions
music-liked-sync --max-add 100 --apply# Custom SQLite path
music-liked-sync --cache-db state/my-sync.sqlite3
# Reuse cached library snapshots for 30 minutes
music-liked-sync --cache-library-ttl 1800 --apply
# Disable cache
music-liked-sync --no-cache-read --no-cache-write --apply--market IN Spotify market code (default: IN)
--apply Actually save/like matched tracks
--max-add INT Cap tracks to add per direction
--batch-size INT Tracks per batch (default: 50)
--batch-delay FLOAT Seconds between batches (default: 1.0)
--cache-db PATH SQLite cache path
--cache-library-ttl FLOAT Library snapshot TTL in seconds
--no-cache-read Skip reading cached data
--no-cache-write Skip writing cached data
--spotify-to-ytm Only Spotify → YouTube Music
--ytm-to-spotify Only YouTube Music → Spotify
--report PATH JSON report path (default: sync-report.json)
--workers INT Concurrency for searches/library fetching
--verbose Detailed logging
| music-liked-sync | sigma67/spotify_to_ytmusic | linsomniac/spotify_to_ytmusic | |
|---|---|---|---|
| Direction | Bidirectional | One-way (Spotify → YTM) | One-way (Spotify → YTM) |
| Scope | Liked songs | Playlists + liked songs | Playlists + liked songs |
| Auth | Browser session (no keys) | OAuth app required | Manual header extraction |
| Dry-run | ✅ Default | ❌ | ❌ |
| GUI | ❌ CLI only | ❌ | ✅ |
| Install | uvx / pip |
pip |
Clone + venv |
Use this if you want bidirectional liked-songs sync without configuring API apps.
| Component | Purpose |
|---|---|
music_liked_sync/cli.py |
Entry point (music-liked-sync console script) |
music_liked_sync/spotify.py |
Spotify Web Player session + liked-library read/write |
music_liked_sync/ytmusic.py |
YouTube Music session + liked-library read/write |
music_liked_sync/sync.py |
Diff + fuzzy match resolution |
music_liked_sync/cache.py |
SQLite persistence for matches and snapshots |
auth/ |
Persistent Chromium profiles (gitignored) |
state/locks/ |
File locks preventing concurrent browser sessions |
uv sync --all-groups
uv run ruff check .
uv run pytest -q
uv run music-liked-sync --helpMIT © 2026 Rabil