Transferring Music Libraries#

Last updated: November 19, 2023

Minim can be used as a free, open-source alternative to services like TuneMyMusic for moving playlists and synchronizing libraries between the supported streaming services.

from minim import qobuz, spotify, tidal

Prerequisites#

All clients must be authenticated to access private user information. Assuming the relevant client credentials are stored as environment variables, the recommended client instantiation is as follows:

client_qobuz = qobuz.PrivateAPI(flow="password", browser=True)
client_spotify = spotify.WebAPI(flow="pkce", 
                                scopes=spotify.WebAPI.get_scopes("all"),
                                web_framework="http.server")
client_tidal = tidal.PrivateAPI(flow="device", browser=True)

See also

See Getting Started for more information about setting up clients with user authentication.

Moving playlists#

The general process is to

  1. get information about the tracks in the source playlist,

  2. create a new playlist in the destination service, and

  3. find and add the corresponding tracks to the newly-created playlist.

The challenge often lies in the third step. The tracks in the source playlist may not be available in the destination service or it may be difficult finding the matching track in the destination service, especially if its catalog lookup does not support searching by ISRC or UPC.

The following examples provide barebones implementations of the process above for various service pairs. Additional fine-tuning is likely necessary to handle tracks with complex metadata, such as those with multiple or featured artists, remixes, etc.

From Qobuz#

We start with a Qobuz playlist with 5 tracks:

QOBUZ_PLAYLIST_ID = 17865119

We can get the playlist information and the items in the playlist using minim.qobuz.PrivateAPI.get_playlist():

qobuz_playlist = client_qobuz.get_playlist(QOBUZ_PLAYLIST_ID)

To Spotify#

First, we create a new playlist on Spotify with the same details as the Qobuz playlist using minim.spotify.WebAPI.create_playlist():

new_spotify_playlist = client_spotify.create_playlist(
    qobuz_playlist["name"],
    description=qobuz_playlist["description"],
    public=qobuz_playlist["is_public"],
    collaborative=qobuz_playlist["is_collaborative"],
)

Then, we get the Spotify tracks equivalent to those in the Qobuz playlist. This is a simple process as Spotify allows looking up tracks by their ISRCs with its best-in-class API:

spotify_track_uris = []
for qobuz_track in qobuz_playlist["tracks"]["items"]:
    spotify_track = client_spotify.search(f'isrc:{qobuz_track["isrc"]}', type="track", limit=1)["items"][0]
    spotify_track_uris.append(f"spotify:track:{spotify_track['id']}")

Finally, we add the tracks to the Spotify playlist using minim.spotify.WebAPI.add_playlist_items():

client_spotify.add_playlist_items(new_spotify_playlist["id"], spotify_track_uris)

To TIDAL#

First, we create a new playlist on TIDAL with the same details as the Qobuz playlist using minim.tidal.PrivateAPI.create_playlist():

new_tidal_playlist = client_tidal.create_playlist(
    qobuz_playlist["name"], 
    description=qobuz_playlist["description"],
    public=qobuz_playlist["is_public"]
)

Then, we try to find TIDAL tracks equivalent to those in the Qobuz playlist. Unfortunately, TIDAL does not support searching by ISRCs, so we have to look up the tracks using their titles and artists. The TIDAL API does, however, return ISRCs so we can confirm that we have the right tracks before adding them to the TIDAL playlist.

tidal_track_ids = []
for qobuz_track in qobuz_playlist["tracks"]["items"]:
    title = qobuz_track["title"]
    if qobuz_track["version"]:
        title += f' {qobuz_track["version"]}'
    tidal_track = client_tidal.search(
        f'{qobuz_track["performer"]["name"]} {title}', 
        type="track", 
        limit=1
    )["items"][0]
    if qobuz_track["isrc"] == tidal_track["isrc"]:
        tidal_track_ids.append(tidal_track["id"])

Finally, we add the tracks to the TIDAL playlist using minim.tidal.PrivateAPI.add_playlist_items():

client_tidal.add_playlist_items(new_tidal_playlist["data"]["uuid"], tidal_track_ids)

From Spotify#

We start with a Spotify playlist with 5 tracks:

SPOTIFY_PLAYLIST_ID = "3rw9qY60CEh6dfJauWdxMh"

We can get the playlist information and the items in the playlist using minim.spotify.WebAPI.get_playlist():

spotify_playlist = client_spotify.get_playlist(SPOTIFY_PLAYLIST_ID)

To Qobuz#

First, we create a new playlist on Qobuz with the same details as the Spotify playlist using minim.qobuz.PrivateAPI.create_playlist():

new_qobuz_playlist = client_qobuz.create_playlist(
    spotify_playlist["name"],
    description=spotify_playlist["description"],
    public=spotify_playlist["public"],
    collaborative=spotify_playlist["collaborative"],
)

Then, we get the Qobuz tracks equivalent to those in the Spotify playlist. Thankfully, we can search by ISRC on Qobuz, so we can get the correct Qobuz tracks directly if they are available in the Qobuz catalog:

qobuz_track_ids = []
for spotify_track in spotify_playlist["tracks"]["items"]:
    qobuz_track = client_qobuz.search(
        spotify_track["track"]["external_ids"]["isrc"],  
        limit=1
    )["tracks"]["items"][0]
    qobuz_track_ids.append(qobuz_track["id"])

Finally, we add the tracks to the Qobuz playlist using minim.qobuz.PrivateAPI.add_playlist_tracks():

client_qobuz.add_playlist_tracks(new_qobuz_playlist["id"], qobuz_track_ids)

To TIDAL#

First, we create a new playlist on TIDAL with the same details as the Spotify playlist:

new_tidal_playlist = client_tidal.create_playlist(
    spotify_playlist["name"], 
    description=spotify_playlist["description"],
    public=spotify_playlist["public"]
)

Then, we try to find TIDAL tracks equivalent to those in the Spotify playlist:

tidal_track_ids = []
for spotify_track in spotify_playlist["tracks"]["items"]:
    tidal_track = client_tidal.search(
        f'{spotify_track["track"]["artists"][0]["name"]} '
        f'{spotify_track["track"]["name"]}', 
        type="track", 
        limit=1
    )["items"][0]
    if spotify_track["track"]["external_ids"]["isrc"] == tidal_track["isrc"]:
        tidal_track_ids.append(tidal_track["id"])

Finally, we add the tracks to the TIDAL playlist:

client_tidal.add_playlist_items(new_tidal_playlist["data"]["uuid"], tidal_track_ids)

From TIDAL#

We start with a TIDAL playlist with 5 tracks:

TIDAL_PLAYLIST_UUID = "40052e73-58d4-4abb-bc1c-abace76d2f15"

We can get the playlist information using minim.tidal.PrivateAPI.get_user_playlist() and the items in the playlist using minim.tidal.PrivateAPI.get_playlist_items():

tidal_playlist = client_tidal.get_user_playlist(TIDAL_PLAYLIST_UUID)
tidal_playlist_items = client_tidal.get_playlist_items(TIDAL_PLAYLIST_UUID)["items"]

To Qobuz#

First, we create a new playlist on Qobuz with the same details as the TIDAL playlist:

new_qobuz_playlist = client_qobuz.create_playlist(
    spotify_playlist["name"],
    description=spotify_playlist["description"],
    public=spotify_playlist["public"],
    collaborative=spotify_playlist["collaborative"],
)

Then, we get the Qobuz tracks equivalent to those in the TIDAL playlist:

qobuz_track_ids = []
for tidal_track in tidal_playlist_items:
    qobuz_track = client_qobuz.search(
        tidal_track["item"]["isrc"],  
        limit=1
    )["tracks"]["items"][0]
    qobuz_track_ids.append(qobuz_track["id"])

Finally, we add the tracks to the Qobuz playlist:

client_qobuz.add_playlist_tracks(new_qobuz_playlist["id"], qobuz_track_ids)

To Spotify#

First, we create a new playlist on Spotify with the same details as the TIDAL playlist:

new_spotify_playlist = client_spotify.create_playlist(
    qobuz_playlist["name"],
    description=qobuz_playlist["description"],
    public=qobuz_playlist["is_public"],
    collaborative=qobuz_playlist["is_collaborative"],
)

Then, we get the Spotify tracks equivalent to those in the TIDAL playlist:

spotify_track_uris = []
for tidal_track in tidal_playlist_items:
    spotify_track = client_spotify.search(f'isrc:{tidal_track["item"]["isrc"]}', 
                                          type="track", limit=1)["items"][0]
    spotify_track_uris.append(f"spotify:track:{spotify_track['id']}")

Finally, we add the tracks to the Spotify playlist:

client_spotify.add_playlist_items(new_spotify_playlist["id"], 
                                  spotify_track_uris)

Synchronizing favorites#

Synchronizing favorite albums, artists, tracks, etc. across services follows a similar procedure as above; we first get information about the entities in the source service and then try to find the corresponding media or people in the destination service. For albums and tracks, we can search using their UPCs and ISRCs, respectively, when available, or their titles and the main artist names. For artists, we can only search using their names.

Sample implementations for synchronizing albums and artists are available below for various service pairs.

From Qobuz#

We start by getting the current user’s favorite albums and artists using minim.qobuz.PrivateAPI.get_favorites():

qobuz_favorites = client_qobuz.get_favorites()
qobuz_favorite_albums = qobuz_favorites["albums"]["items"]
qobuz_favorite_artists = qobuz_favorites["artists"]["items"]

To Spotify#

The Spotify Web API supports searching for albums by UPC, but sometimes the UPCs returned by Qobuz do not align with those in the Spotify catalog. In those cases, we can search for the albums using their titles and the main artist names. Then, we select the correct album from the search results by matching the album title, main artists, and number of tracks. Finally, we add the albums to the user’s Spotify library using their Spotify album IDs and minim.spotify.WebAPI.save_albums():

spotify_album_ids = []
for qobuz_album in qobuz_favorite_albums:
    try:
        spotify_album = client_spotify.search(f'upc:{qobuz_album["upc"][1:]}', 
                                              "album")["items"][0]
    except IndexError:
        spotify_albums = client_spotify.search(
            f'{qobuz_album["artist"]["name"]} {qobuz_album["title"]}', "album"
        )["items"]
        for spotify_album in spotify_albums:
            if (spotify_album["name"] == qobuz_album["title"] 
                    and spotify_album["artists"][0]["name"] 
                        == qobuz_album["artist"]["name"]
                    and spotify_album["total_tracks"] 
                        == qobuz_album["tracks_count"]):
                break
    spotify_album_ids.append(spotify_album["id"])
client_spotify.save_albums(spotify_album_ids)
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Cell In[34], line 18
     16                 break
     17     spotify_album_ids.append(spotify_album["id"])
---> 18 client_spotify.save_albums(spotify_album_ids)

File /mnt/c/USers/Benjamin/Documents/GitHub/minim-dev/src/minim/spotify.py:1857, in WebAPI.save_albums(self, ids)
   1854     self._request("put", f"{self.API_URL}/me/albums", 
   1855                   params={"ids": ids})
   1856 elif isinstance(ids, list):
-> 1857     self._request("put", f"{self.API_URL}/me/albums",
   1858                   json={"ids": ids})

File /mnt/c/USers/Benjamin/Documents/GitHub/minim-dev/src/minim/spotify.py:941, in WebAPI._request(self, method, url, retry, **kwargs)
    939         return self._request(method, url, False, **kwargs)
    940     else:
--> 941         raise RuntimeError(emsg)
    942 return r

RuntimeError: 502 Bad gateway.

For artists, we can search for them using their names and add them to the user’s Spotify library using their Spotify artist IDs and minim.spotify.WebAPI.follow_artists():

spotify_artist_ids = []
for qobuz_artist in qobuz_favorite_artists:
    spotify_artist = client_spotify.search(qobuz_artist["name"], "artist")["items"][0]
    spotify_artist_ids.append(spotify_artist["id"])
client_spotify.follow_people(spotify_artist_ids, "artist")

To TIDAL#

The private TIDAL API does not support searching for albums by UPC, so we have to search for them using their titles and the main artist names. Then, we select the correct albums by matching UPCs. Finally, we add the albums to the user’s TIDAL library using their TIDAL album IDs and minim.tidal.PrivateAPI.favorite_albums():

tidal_album_ids = []
for qobuz_album in qobuz_favorite_albums:
    tidal_albums = client_tidal.search(
        f'{qobuz_album["artist"]["name"]} {qobuz_album["title"]}', type="album"
    )["items"]
    for tidal_album in tidal_albums:
        if tidal_album["upc"].lstrip("0") == qobuz_album["upc"].lstrip("0"):
            tidal_album_ids.append(tidal_album["id"])
            break
client_tidal.favorite_albums(tidal_album_ids)

For artists, we can search for them using their names and add them to the user’s TIDAL library using their TIDAL artist IDs and minim.tidal.PrivateAPI.favorite_artists():

tidal_artist_ids = []
for qobuz_artist in qobuz_favorite_artists:
    tidal_artist = client_tidal.search(qobuz_artist["name"], 
                                       type="artist")["items"][0]
    tidal_artist_ids.append(tidal_artist["id"])
client_tidal.favorite_artists(tidal_artist_ids)

From Spotify#

We start by getting the current user’s favorite albums and artists using minim.spotify.WebAPI.get_saved_albums() and minim.spotify.WebAPI.get_followed_artists(), respectively:

spotify_favorite_albums = client_spotify.get_saved_albums()["items"]
spotify_favorite_artists = client_spotify.get_followed_artists()["items"]

To Qobuz#

The private Qobuz API does not support searching for albums by UPC, so we have to search for them using their titles and the main artist names. Then, we select the correct albums by matching UPCs or the album title, main artists, and number of tracks. Finally, we add the albums to the user’s Qobuz library using their Qobuz album IDs and minim.qobuz.PrivateAPI.favorite_items():

qobuz_album_ids = []
for spotify_album in spotify_favorite_albums:
    qobuz_albums = client_qobuz.search(
        f'{spotify_album["album"]["artists"][0]["name"]} '
        f'{spotify_album["album"]["name"]}'
    )["albums"]["items"]
    for qobuz_album in qobuz_albums:
        if (spotify_album["album"]["external_ids"]["upc"].lstrip("0") 
                    == qobuz_albums[0]["upc"].lstrip("0")
                or (spotify_album["album"]["name"] == qobuz_album["title"]
                    and spotify_album["album"]["artists"][0]["name"] 
                        == qobuz_album["artist"]["name"]
                    and spotify_album["album"]["tracks"]["total"] 
                        == qobuz_album["tracks_count"])):
            qobuz_album_ids.append(qobuz_album["id"])
            break
client_qobuz.favorite_items(album_ids=qobuz_album_ids)

For artists, we can search for them using their names and add them to the user’s Qobuz library using their Qobuz artist IDs and minim.qobuz.PrivateAPI.favorite_items():

qobuz_artist_ids = []
for spotify_artist in spotify_favorite_artists:
    qobuz_artist = client_qobuz.search(
        spotify_artist["name"]
    )["artists"]["items"][0]
    qobuz_artist_ids.append(qobuz_artist["id"])
client_qobuz.favorite_items(artist_ids=qobuz_artist_ids)

To TIDAL#

To search for albums using their titles and the main artist names, select the correct albums by matching UPCs or the album title, main artists, and number of tracks, and add the albums to the user’s TIDAL library,

tidal_album_ids = []
for spotify_album in spotify_favorite_albums:
    tidal_albums = client_tidal.search(
        f'{spotify_album["album"]["artists"][0]["name"]} '
        f'{spotify_album["album"]["name"]}',
        type="album"
    )["items"]
    for tidal_album in tidal_albums:
        if (tidal_album["upc"].lstrip("0") 
                    == spotify_album["album"]["external_ids"]["upc"].lstrip("0")
                or (tidal_album["title"] == spotify_album["album"]["name"]
                    and tidal_album["artists"][0]["name"] 
                        == spotify_album["album"]["artists"][0]["name"]
                    and tidal_album["numberOfTracks"] 
                        == spotify_album["album"]["tracks"]["total"])):
            tidal_album_ids.append(tidal_album["id"])
            break
client_tidal.favorite_albums(tidal_album_ids)

To search for artists using their names and add them to the user’s TIDAL library,

tidal_artist_ids = []
for spotify_artist in spotify_favorite_artists:
    tidal_artist = client_tidal.search(spotify_artist["name"], 
                                       type="artist")["items"][0]
    tidal_artist_ids.append(tidal_artist["id"])
client_tidal.favorite_artists(tidal_artist_ids)

From TIDAL#

We start by getting the current user’s favorite albums and artists using minim.tidal.PrivateAPI.get_favorite_albums() and minim.tidal.PrivateAPI.get_favorite_artists(), respectively:

tidal_favorite_albums = client_tidal.get_favorite_albums()["items"]
tidal_favorite_artists = client_tidal.get_favorite_artists()["items"]

To Qobuz#

To search for albums using their titles and the main artist names, select the correct albums by matching UPCs or the album title, main artists, and number of tracks, and add the albums to the user’s Qobuz library,

qobuz_album_ids = []
for tidal_album in tidal_favorite_albums:
    qobuz_albums = client_qobuz.search(
        f'{tidal_album["item"]["artist"]["name"]} {tidal_album["item"]["title"]}'
    )["albums"]["items"]
    for qobuz_album in qobuz_albums:
        if (tidal_album["item"]["upc"].lstrip("0") 
                    == qobuz_album["upc"].lstrip("0")
                or (tidal_album["item"]["title"] == qobuz_album["title"]
                    and tidal_album["item"]["artist"]["name"] 
                        == qobuz_album["artist"]["name"]
                    and tidal_album["item"]["numberOfTracks"] 
                        == qobuz_album["tracks_count"])):
            qobuz_album_ids.append(qobuz_album["id"])
            break
client_qobuz.favorite_items(album_ids=qobuz_album_ids)

To search for artists using their names and add them to the user’s Qobuz library,

qobuz_artist_ids = []
for tidal_artist in tidal_favorite_artists:
    qobuz_artist = client_qobuz.search(
        tidal_artist["item"]["name"]
    )["artists"]["items"][0]
    qobuz_artist_ids.append(qobuz_artist["id"])
client_qobuz.favorite_items(artist_ids=qobuz_artist_ids)

To Spotify#

To search for albums using their UPCs or titles and the main artist names, select the correct albums by matching the album title, main artists, and number of tracks, and add the albums to the user’s Spotify library,

spotify_album_ids = []
for tidal_album in tidal_favorite_albums:
    try:
        spotify_album = client_spotify.search(
            f'upc:{tidal_album["item"]["upc"]}', 
            "album"
        )["items"][0]
    except IndexError:
        spotify_albums = client_spotify.search(
            f'{tidal_album["item"]["artist"]["name"]} '
            f'{tidal_album["item"]["title"]}', 
            "album"
        )["items"]
        for spotify_album in spotify_albums:
            if (spotify_album["name"] == tidal_album["item"]["title"]
                    and spotify_album["artists"][0]["name"] 
                        == tidal_album["item"]["artist"]["name"]
                    and spotify_album["total_tracks"] 
                        == tidal_album["item"]["numberOfTracks"]):
                break
    spotify_album_ids.append(spotify_album["id"])
client_spotify.save_albums(spotify_album_ids)

To search for artists using their names and add them to the user’s Spotify library,

spotify_artist_ids = []
for tidal_artist in tidal_favorite_artists:
    spotify_artist = client_spotify.search(tidal_artist["item"]["name"], 
                                           "artist")["items"][0]
    spotify_artist_ids.append(spotify_artist["id"])
client_spotify.follow_people(spotify_artist_ids, "artist")