Synchronize a playlist between Spotify and Tidal¶

This example keeps one playlist mirrored across Spotify and Tidal. You can start from:

  • an existing playlist on either service, or

  • two empty playlists.

Tracks are matched using their ISRC (International Standard Recording Code), which makes cross‑service matching reliable.

Note

This notebook is intentionally step-by-step and a bit more explicit than strictly necessary, to make the workflow easy to follow.

Prerequisites¶

Before running the notebook, make sure you have spotify and tidal authenticated.

plistsync spotify auth  
plistsync tidal auth

The initial playlist(s)¶

We start by fetching both the existing spotify playlist and tidal playlist. We can also create a new one if needed. If you think the name of the playlist will change you should consider using ids for the retrieval instead.

name = "Fresh Songs"
description = "New songs from the last 2 weeks."
from plistsync.services.spotify import SpotifyLibrary

spotify_library = SpotifyLibrary()

# Get playlist by name
_spotify_playlist = spotify_library.get_playlist(name=name)

# Create if it does not exist
if _spotify_playlist is None:
    spotify_playlist = spotify_library.create_playlist(
        name=name,
        description=description,
    )
else:
    spotify_playlist = _spotify_playlist

print(
    f"Playlist (spotify): {spotify_playlist.name} "
    f"({len(spotify_playlist.tracks)} tracks)"
)

Tidal is intentionally handled the same as Spotify: same interface, same normalization, so the get-or-create playlist logic is identical.

from plistsync.services.tidal import TidalLibrary

tidal_library = TidalLibrary()

# Get playlist by name
_tidal_playlist = tidal_library.get_playlist(name=name)

# Create if it does not exist
if _tidal_playlist is None:
    tidal_playlist = tidal_library.create_playlist(
        name=name,
        description=description,
    )
else:
    tidal_playlist = _tidal_playlist

print(f"Playlist (tidal): {tidal_playlist.name} ({len(tidal_playlist.tracks)} tracks)")

Note

If the playlist doesn’t exist on the service yet, we only create a local playlist object here. The actual remote playlist on Spotify/Tidal is created later (at the end of the notebook) when we persist changes.

The playlist differences¶

Even if playlists start out identical, they can drift over time. Tracks may be added or removed manually, the order can change, and some songs may exist on one service but be unavailable (or matched differently) on another. In this section we compute and than apply the updates.

We recommend choosing one playlist as the source of truth. If both playlists are edited manually, additions/removals (and especially ordering) can conflict in ways that can’t be resolved automatically without a baseline to sync against. In a later section, we extend this to bidirectional sync using a local reference track list.

from plistsync.core.diff import list_diff
from plistsync.core.track import Track


def hash_func(x: Track):
    """Return a stable identity key for diffing tracks across services.

    `list_diff` groups and compares items using this key. If two tracks produce
    the same key, they are treated as the same logical song (even if other
    metadata differs).

    We use the ISRC because it is typically stable across Spotify and Tidal and
    more reliable than string matching on title/artist. Tracks without an ISRC
    return `None` and may be treated as unmatched.
    """
    return x.isrc


ops = list_diff(
    old=tidal_playlist.tracks,  # current state (to be updated)
    new=spotify_playlist.tracks,  # desired state (source of truth)
    hash_func=hash_func,
)

The list_diff function computes the minimal set of operations needed to transform an old list into a new list. In this notebook we treat Spotify as the source of truth, so we diff the current Tidal playlist (old) against the Spotify playlist (new) and then apply the resulting operations to bring Tidal in sync.

Next we apply the changes and upsert the playlist(s). We use the edit context manager here.

from plistsync.core.diff import DeleteOp, InsertOp, MoveOp
from plistsync.services.tidal.track import TidalPlaylistTrack, TidalTrack

# After closing the `edit` context manager
# the changes are minified, batched and applied
# to the remote version of the playlist
with tidal_playlist.edit():
    # Name or description update
    tidal_playlist.name = spotify_playlist.name
    tidal_playlist.description = spotify_playlist.description

    # Track changes
    for op in ops:
        if isinstance(op, InsertOp):
            # find the new track on tidal
            tidal_track: TidalTrack | None = tidal_library.match(op.item).best_match
            if tidal_track is not None:
                tidal_playlist.tracks.append(
                    TidalPlaylistTrack(
                        tidal_track,
                    ),
                )
        elif isinstance(op, MoveOp):
            tidal_track = tidal_playlist.tracks.pop(op.old_idx)
            tidal_playlist.tracks.insert(op.new_idx, tidal_track)
        elif isinstance(op, DeleteOp):
            tidal_playlist.tracks.pop(op.idx)

After running this example, we have successfully synchronized a Tidal playlist to match a Spotify playlist.
This synchronization is one-directional: Spotify is treated as the source of truth, and changes are applied only to Tidal (it does not sync updates back to Spotify).