10 min read
Automating Music Transfers Between YouTube and Spotify

Recently, my partner and I decided to rationalize our music subscriptions. She had been using Spotify, and I was on YouTube Music. Spotify’s family program looked like it would work perfectly for us, but it meant I would lose all my playlists and favorite songs on YouTube.

Naturally, I wanted to transfer my playlists over from YouTube to Spotify.

A quick Google search showed there were a number of tools that could do this, but they all required me to grant quite permissive access to my accounts to a third-party tool that I wasn’t completely sure of the provenance of. So, I decided to investigate how hard it would be to do it myself.

Wonderfully, both Spotify and YouTube Music have excellent API access available, and it’s pretty straightforward to set up developer-style access to your account. Below are the steps you can take to try this yourself!

The guide below assumes some experience setting up a python coding environment.

Setting Up the Environment

First things first, I needed to set up my development environment. I created a virtual environment to keep things organized and installed the necessary Python packages.

You should run the following in the shell you use for your development environment.

Here’s how I set it up:

Create a Virtual Environment:

python -m venv myenv

This command creates a virtual environment named myenv.

Activate the Virtual Environment:

On macOS/Linux:

source myenv/bin/activate

Install Required Packages:

pip install spotipy python-dotenv google-api-python-client google-auth google-auth-oauthlib google-auth-httplib2

This command installs all the necessary libraries you’ll need for interacting with the Spotify and YouTube APIs.

Authentication with APIs

To interact with both YouTube and Spotify, I needed to authenticate my application with their APIs. Both services have developer consoles to generate credentials to authorise your application via OAuth flows.

Spotify Authentication

I used Spotipy for Spotify API authentication. Spotipy makes it easy to interact with the Spotify API in Python.

Setting Up Spotify API Credentials

Sign Up for a Spotify Developer Account:

Create a New Application:

  • Once logged in, click on the “Create an App” button.
  • Fill in the required details, such as App name and App description. These can be anything you like.
  • After filling in the details, click “Create”.

Get Your Client ID and Client Secret:

  • After creating the app, you will be redirected to your app’s dashboard.
  • Here, you’ll see your Client ID and Client Secret. Click on “Show Client Secret” to view it.

Set Up Redirect URI:

  • In your app’s dashboard, scroll down to the “Redirect URIs” section.
  • Click on “Edit Settings”.
  • Add http://localhost:8888/callback as a Redirect URI.
  • Click “Add” and then “Save”.

Create a .env File: Create a file named .env in your project directory and add your Spotify API credentials:

SPOTIFY_CLIENT_ID=your_spotify_client_id
SPOTIFY_CLIENT_SECRET=your_spotify_client_secret
SPOTIFY_REDIRECT_URI=http://localhost:8888/callback

Load Environment Variables and Authenticate:

The code below loads your Spotify credentials from the .env file and authenticates your application with the Spotify API.

import spotipy
from spotipy.oauth2 import SpotifyOAuth
from dotenv import load_dotenv
import os

# Load environment variables from .env file

load_dotenv()

SPOTIFY_CLIENT_ID = os.getenv('SPOTIFY_CLIENT_ID')
SPOTIFY_CLIENT_SECRET = os.getenv('SPOTIFY_CLIENT_SECRET')
SPOTIFY_REDIRECT_URI = os.getenv('SPOTIFY_REDIRECT_URI')

sp = spotipy.Spotify(auth_manager=SpotifyOAuth(
  client_id=SPOTIFY_CLIENT_ID,
  client_secret=SPOTIFY_CLIENT_SECRET,
  redirect_uri=SPOTIFY_REDIRECT_URI,
  scope='playlist-read-private playlist-modify-public playlist-modify-private'
))

YouTube Authentication

For YouTube, I used the Google API Client library. This library helps you interact with Google APIs using Python.

Download Credentials:

  • Go to the Google Cloud Console.
  • Create a new project and enable the YouTube Data API v3.
  • Create OAuth 2.0 credentials and download the client_secret.json file.

Authenticate and Build the Service:

This function handles the authentication process, allowing you to interact with the YouTube API. When you run this cell, you’ll be asked to click a link to start the OAuth flow and allow access to your youtube account.

from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from google.auth.transport.requests import Request
import pickle
import os

# Define the scope for YouTube - we are only reading existing info, so need the readonly scope

YOUTUBE_SCOPES = ['https://www.googleapis.com/auth/youtube.readonly']

def get_authenticated_service():
  creds = None
  token_path = 'token.pickle'

  # Load existing credentials from file if available
  if os.path.exists(token_path):
      with open(token_path, 'rb') as token:
          creds = pickle.load(token)

  # If there are no valid credentials available, request new ones
  if not creds or not creds.valid:
      if creds and creds.expired and creds.refresh_token:
          creds.refresh(Request())
      else:
          flow = InstalledAppFlow.from_client_secrets_file(
              'client_secret.json', YOUTUBE_SCOPES, redirect_uri='http://localhost:8888/'
          )
          creds = flow.run_local_server(port=8888)

      # Save the credentials for future use
      with open(token_path, 'wb') as token:
          pickle.dump(creds, token)

  return build('youtube', 'v3', credentials=creds)

youtube = get_authenticated_service()

Fetching Liked Songs from YouTube

YouTube has a special playlist for your liked songs, which is not listed as a standard playlist. This playlist is identified by the ID LL. Here’s how I fetched my liked songs:

Define a Function to Fetch Liked Songs:

def fetch_liked_songs(youtube):
    songs = []
    request = youtube.playlistItems().list(
        part="snippet,contentDetails",
        playlistId='LL',
        maxResults=50
    )
    while request is not None:
        response = request.execute()
        songs.extend(response.get('items', []))
        request = youtube.playlistItems().list_next(request, response)
    return songs

liked_songs = fetch_liked_songs(youtube)

Print the Liked Songs:

This code retrieves and prints the details of your liked songs from YouTube.

for song in liked_songs:
  print(f"Title: {song['snippet']['title']}, Video ID: {song['snippet']['resourceId']['videoId']}")

Fetching Standard Playlists from YouTube

In addition to your liked songs, you might have created several playlists on YouTube. Here’s how to fetch these playlists:

Define a Function to Fetch Playlists:

def fetch_playlists(youtube):
    playlists = []
    request = youtube.playlists().list(
        part="snippet,contentDetails",
        mine=True,
        maxResults=50
    )
    while request is not None:
        response = request.execute()
        playlists.extend(response.get('items', []))
        request = youtube.playlists().list_next(request, response)
    return playlists

playlists = fetch_playlists(youtube)

Print the Playlists:

This code retrieves and prints the details of your playlists from YouTube. for playlist in playlists:

print(f"Playlist: {playlist['snippet']['title']}, Playlist ID: {playlist['id']}")

Fetch Songs from a Specific Playlist:

def fetch_songs_from_playlist(youtube, playlist_id):
    songs = []
    request = youtube.playlistItems().list(
        part="snippet,contentDetails",
        playlistId=playlist_id,
        maxResults=50
    )
    while request is not None:
        response = request.execute()
        songs.extend(response.get('items', []))
        request = youtube.playlistItems().list_next(request, response)
    return songs

Creating Playlists on Spotify

With the liked songs and playlists fetched, the next step was to create a playlist on Spotify and add these tracks.

Create a Playlist

Define a Function to Create a New Playlist:

def create_spotify_playlist(sp, playlist_name, description=""):
  user_id = sp.current_user()['id']
  playlist = sp.user_playlist_create(user=user_id, name=playlist_name, public=True, description=description)
  return playlist
# Example usage

playlist_name = "My New Playlist"
description = "A new playlist created via Spotipy"
new_playlist = create_spotify_playlist(sp, playlist_name, description)

print(f"Created playlist: {new_playlist['name']} with ID: {new_playlist['id']}")

Add Tracks to Playlist

To add tracks to the playlist, I wrote a function to search for the track on Spotify and handle errors gracefully:

Define a Function to Extract Artist and Title from YouTube Song Data:

Due to the strcuture of the song data returned from the YouTube endpoint, we need to extract the song name and artist name.

The Song name is easily available in the ['snippet']['title'] dictionary entry, but the artist name is a bit more hidden.

The pattern I’ve observed is that it is stored in the ['snippet']['videoOwnerChannelTitle'] dictionary entry, but with a trailing ” - Topic” string part. We create a helper function to extract this below.

def extract_artist_name(channel_title):
    if " - Topic" in channel_title:
        return channel_title.split(" - Topic")[0]
    return channel_title

Define a Function to Search for a Track:

def search_track(track_name, artist_name):
    query = f'track:{track_name} artist:{artist_name}'
    result = sp.search(q=query, type='track', limit=1)

    if result['tracks']['items']:
        track = result['tracks']['items'][0]
        track_info = {
            'name': track['name'],
            'artist': track['artists'][0]['name'],
            'album': track['album']['name'],
            'release_date': track['album']['release_date'],
            'uri': track['uri']
        }
        return track_info
    else:
        return None

Batch Adding Tracks with Error Handling

To avoid hitting API rate limits, I batched the requests and handled any errors that occurred:

Define a Function to Add Tracks in Batches:

import time

def add_tracks_to_playlist(sp, playlist_id, track_uris):
    for i in range(0, len(track_uris), 20):  # Batch size of 20
        batch = track_uris[i:i + 20]
        try:
            sp.playlist_add_items(playlist_id, batch)
        except Exception as e:
            print(f"Error adding batch: {str(e)}")
        time.sleep(1)  # Wait for 1 second between batches

Wiring Up YouTube and Spotify

Now, let’s wire up the playlists and songs we found on YouTube and use that to create playlists with songs on Spotify.

Fetch Liked Songs and Create a Playlist on Spotify

Do the Liked Song playlist first.

Fetch Liked Songs:

liked_songs = fetch_liked_songs(youtube)

Create a New Playlist on Spotify:

playlist_name = "My YouTube Favorites"
description = "Favorite songs from YouTube, now on Spotify"
new_playlist = create_spotify_playlist(sp, playlist_name, description)

Search and Add Songs to Spotify Playlist:

track_uris = []
for song in liked_songs:
    title = song['snippet']['title']
    artist = extract_artist_name(song['snippet']['videoOwnerChannelTitle'])
    print(f"Processing: {title} by {artist}")

    track_info = search_track(title, artist)
    if track_info:
        track_uris.append(track_info['uri'])
    else:
        print(f"Error: Track not found - {title} by {artist}")


# Add tracks to the new playlist in batches
if track_uris:
    try:
        add_tracks_to_playlist(sp, new_playlist['id'], track_uris)
        print(f"Added {len(track_uris)} tracks to playlist: {new_playlist['name']}")
    except Exception as e:
        print(f"Error adding tracks to playlist: {str(e)}")
else:
    print("No valid tracks to add to the playlist.")

Fetch Standard Playlists and Create Corresponding Playlists on Spotify

Now transfer the standard playlists.

Fetch Standard Playlists:

playlists = fetch_playlists(youtube)

Create Spotify Playlists from YouTube Playlists:

for playlist in playlists:
    playlist_name = playlist['snippet']['title']
    playlist_id = playlist['id']
    print(f"Processing playlist: {playlist_name}")

    # Create a new playlist on Spotify
    new_spotify_playlist = create_spotify_playlist(sp, playlist_name, f"Playlist from YouTube: {playlist_name}")

    # Fetch songs from the YouTube playlist
    songs = fetch_songs_from_playlist(youtube, playlist_id)

    # Search and add songs to the Spotify playlist
    track_uris = []
    for song in songs:
        title = song['snippet']['title']
        artist = extract_artist_name(song['snippet']['videoOwnerChannelTitle'])
        print(f"Processing: {title} by {artist}")

        track_info = search_track(title, artist)
        if track_info:
            track_uris.append(track_info['uri'])
        else:
            print(f"Error: Track not found - {title} by {artist}")

    # Add tracks to the new Spotify playlist in batches
    if track_uris:
        try:
            add_tracks_to_playlist(sp, new_spotify_playlist['id'], track_uris)
            print(f"Added {len(track_uris)} tracks to playlist: {new_spotify_playlist['name']}")
        except Exception as e:
            print(f"Error adding tracks to playlist: {str(e)}")
    else:
        print("No valid tracks to add to the playlist.")

Summary

This project streamlined the process of transferring liked songs and playlists from YouTube to Spotify playlists. By automating the authentication, fetching, and playlist creation steps, I saved a lot of manual effort.

The search_track function is relatively naive and looks for exact matches only.

I’ve found that it will occasionally fail to find a match especially if there are unusual characters in the artist name or there are multiple versions of the song which the platforms capture slightly differently. But you can see these ones in the console output labelled: Error: Track not found... so can just add them manually.

Hope this helps others in a similar situation!