🚀 Executive Summary

TL;DR: This Python script automates the tedious process of syncing Spotify ‘Liked Songs’ to a YouTube playlist. It authenticates with both Spotify and YouTube APIs, fetches all liked tracks, searches for corresponding videos on YouTube, and adds them to a specified playlist, handling pagination and preventing duplicates.

🎯 Key Takeaways

  • Requires Python 3.8+, `spotipy`, `google-api-python-client`, `google-auth-oauthlib`, and `python-dotenv` for execution.
  • API credentials from Spotify Developer Dashboard and Google Cloud Console (YouTube Data API v3) are essential, stored securely in a `config.env` file.
  • The script employs pagination for both Spotify’s `current_user_saved_tracks` and YouTube’s `playlistItems().list` to fetch all relevant data.
  • Duplicate video additions to YouTube playlists are managed by attempting to add all songs and catching the `playlistItemDuplicate` error from the YouTube API.
  • Automation is achieved via cron jobs, allowing scheduled, hands-off synchronization of liked songs, with an example running weekly.

Syncing Spotify 'Liked Songs' to a Youtube Playlist

Syncing Spotify ‘Liked Songs’ to a Youtube Playlist

Alright, let’s talk about a little quality-of-life automation. I’m constantly discovering new music through Spotify’s algorithm, but my main hub for sharing music with friends and family is YouTube. For months, I was manually searching for each new “Liked Song” on YouTube and adding it to a playlist. It was tedious and felt like the kind of repetitive task I’m paid to eliminate. This script was born out of that frustration; it saves me time and ensures my “Discovery” playlist on YouTube is always up-to-date without me lifting a finger.

This isn’t just about saving a few minutes; it’s about building a seamless bridge between your discovery platform and your sharing platform. Let’s get this set up.

Prerequisites

Before we dive in, make sure you have the following ready. I’m assuming you’re comfortable with Python and managing your own environment.

  • Python 3.8 or higher installed.
  • A Spotify account and a created application in the Spotify Developer Dashboard to get your Client ID and Client Secret.
  • A Google account and a project set up in the Google Cloud Console with the YouTube Data API v3 enabled. You’ll need to generate API credentials (an `client_secrets.json` file).
  • The ability to install a few Python packages. I’ll list them, but I’ll skip the standard virtualenv setup since you likely have your own workflow for that. You’ll need `spotipy`, `google-api-python-client`, `google-auth-oauthlib`, and `python-dotenv`.

The Guide: Step-by-Step

Step 1: Environment and Configuration

First things first, let’s handle our secrets. In your project directory, create a file named config.env. This is where we’ll store our API keys so they aren’t hardcoded in the script. It’s a simple, effective way to manage credentials for personal projects.

Pro Tip: In my production setups at TechResolve, we’d use a dedicated secrets manager like HashiCorp Vault or AWS Secrets Manager. For a personal script like this, a config.env file, added to your .gitignore, is perfectly acceptable and keeps things clean.

Your config.env file should look like this:

SPOTIPY_CLIENT_ID='YOUR_SPOTIFY_CLIENT_ID'
SPOTIPY_CLIENT_SECRET='YOUR_SPOTIFY_CLIENT_SECRET'
SPOTIPY_REDIRECT_URI='http://localhost:8888/callback'
YOUTUBE_PLAYLIST_ID='YOUR_TARGET_YOUTUBE_PLAYLIST_ID'

You can get the YouTube Playlist ID from its URL. It’s the string of characters that comes after `list=`. If you want the script to create a new playlist for you, you can leave this blank for now.

Step 2: The Python Script – Authentication and Setup

Let’s create our Python script, `sync_playlists.py`. We’ll start by importing the necessary libraries and setting up the clients for both services. The first time you run this, it will open a browser window for you to authorize both Spotify and YouTube access. It will then save token files locally so you don’t have to re-authenticate every time.

import os
import google_auth_oauthlib.flow
import googleapiclient.discovery
import spotipy
from spotipy.oauth2 import SpotifyOAuth
from dotenv import load_dotenv

def get_spotify_client():
    """Establishes and returns an authenticated Spotipy client."""
    scope = "user-library-read"
    auth_manager = SpotifyOAuth(scope=scope)
    sp = spotipy.Spotify(auth_manager=auth_manager)
    print("Spotify client authenticated successfully.")
    return sp

def get_youtube_client():
    """Establishes and returns an authenticated YouTube API client."""
    os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
    api_service_name = "youtube"
    api_version = "v3"
    client_secrets_file = "client_secrets.json" # Download this from Google Cloud Console

    scopes = ["https://www.googleapis.com/auth/youtube"]
    flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file(
        client_secrets_file, scopes)
    credentials = flow.run_local_server(port=0)
    
    youtube = googleapiclient.discovery.build(
        api_service_name, api_version, credentials=credentials)
    print("YouTube client authenticated successfully.")
    return youtube

# Load environment variables
load_dotenv('config.env')

The logic here is straightforward. Each function handles the OAuth2 flow for its respective service. We’re requesting `user-library-read` for Spotify to see your liked songs and the general `youtube` scope for YouTube to manage playlists and add videos.

Step 3: Fetching Spotify Liked Songs

Now, let’s get the core data. We need to paginate through all of your liked songs on Spotify and build a clean list of them.

def get_spotify_liked_songs(sp_client):
    """Fetches all liked songs from Spotify, handling pagination."""
    all_tracks = []
    offset = 0
    limit = 50
    while True:
        results = sp_client.current_user_saved_tracks(limit=limit, offset=offset)
        if not results['items']:
            break
        
        for item in results['items']:
            track = item['track']
            if track and track['name']:
                track_name = track['name']
                artist_name = track['artists'][0]['name']
                all_tracks.append(f"{track_name} {artist_name}")
        
        offset += limit
    
    print(f"Found {len(all_tracks)} liked songs on Spotify.")
    return all_tracks

The key here is the `while True` loop and the `offset`. The Spotify API returns a maximum of 50 songs per request. We keep making requests, increasing the offset by 50 each time, until the API returns an empty list of items. This ensures we get every single song, not just the first 50.

Step 4: Finding and Adding to YouTube

This is the most complex part. We need to get our YouTube playlist, check which songs are already in it to avoid duplicates, search for the missing ones, and add them.

Pro Tip: For the YouTube search query, appending “Official Audio” or “Lyric Video” can significantly improve the quality of the results. It helps filter out covers, remixes, and low-quality live recordings. I’m keeping it simple here, but feel free to enhance the `search_query` string.

def get_existing_yt_playlist_videos(yt_client, playlist_id):
    """Returns a set of video titles already in the playlist."""
    existing_videos = set()
    next_page_token = None
    while True:
        request = yt_client.playlistItems().list(
            part="snippet",
            playlistId=playlist_id,
            maxResults=50,
            pageToken=next_page_token
        )
        response = request.execute()

        for item in response['items']:
            existing_videos.add(item['snippet']['title'])
        
        next_page_token = response.get('nextPageToken')
        if not next_page_token:
            break
            
    print(f"Found {len(existing_videos)} existing videos in the YouTube playlist.")
    return existing_videos

def add_song_to_youtube_playlist(yt_client, playlist_id, song_title):
    """Searches for a song on YouTube and adds the first result to a playlist."""
    # Search for the video
    search_request = yt_client.search().list(
        part="snippet",
        q=song_title,
        maxResults=1,
        type="video"
    )
    search_response = search_request.execute()

    if not search_response['items']:
        print(f"  - Could not find a YouTube video for: {song_title}")
        return

    video_id = search_response['items'][0]['id']['videoId']
    video_title = search_response['items'][0]['snippet']['title']

    # Add the video to the playlist
    insert_request = yt_client.playlistItems().insert(
        part="snippet",
        body={
            "snippet": {
                "playlistId": playlist_id,
                "resourceId": {
                    "kind": "youtube#video",
                    "videoId": video_id
                }
            }
        }
    )
    insert_request.execute()
    print(f"  + Added '{video_title}' to the playlist.")

Step 5: Bringing It All Together

Finally, let’s create a `main` function to orchestrate the entire workflow. This is where we’ll call all our helper functions in the correct order.

def main():
    """Main function to run the sync process."""
    playlist_id = os.getenv('YOUTUBE_PLAYLIST_ID')
    if not playlist_id:
        print("Error: YOUTUBE_PLAYLIST_ID not set in config.env")
        return # Use return instead of sys.exit

    sp = get_spotify_client()
    yt = get_youtube_client()
    
    spotify_songs = get_spotify_liked_songs(sp)
    # The YouTube API is case-sensitive, but titles might vary.
    # It's better to search for everything and let YouTube's algorithm handle it.
    # A simple check for duplicates is often not robust enough.
    
    songs_to_add = spotify_songs # For simplicity, we'll try to add all. 
                                 # A more advanced check could be implemented.
    
    print(f"\nStarting to sync {len(songs_to_add)} songs to YouTube...")
    
    for song in reversed(spotify_songs): # Add oldest liked songs first
        try:
            add_song_to_youtube_playlist(yt, playlist_id, song)
        except Exception as e:
            # A common error is playlist item duplication, we can ignore it
            if 'duplicate' in str(e).lower():
                print(f"  - Song '{song}' is already in the playlist. Skipping.")
            else:
                print(f"  - An error occurred for '{song}': {e}")

if __name__ == '__main__':
    main()

I’ve simplified the duplicate check here. A truly robust check is tricky because video titles on YouTube rarely match Spotify titles perfectly. A more reliable approach is to attempt adding all songs and catch the `playlistItemDuplicate` error that the API returns, which is what the `try…except` block handles. We also reverse the list to add your oldest liked songs first, preserving some sense of history.

Step 6: Automation with Cron

To make this truly hands-off, you can schedule it to run automatically. A simple cron job is perfect for this. I run mine once a week, which is more than enough.

You can set up a job that looks like this. This example runs the script every Monday at 2 AM.

0 2 * * 1 python3 /path/to/your/project/sync_playlists.py

Just make sure to use the correct path to your script and the `python3` executable within your project’s environment.

Common Pitfalls

Here are a couple of places I’ve tripped up in the past that you can hopefully avoid:

  • API Quotas: The YouTube Data API has a daily quota. This script is fairly light, but if you have thousands of songs and run it multiple times, you might hit the limit. You can monitor your usage in the Google Cloud Console.
  • Authentication Tokens: The token files (`.cache` for Spotipy, `token.json` for Google) can sometimes expire or become invalid. If you’re getting persistent authentication errors, the quickest fix is often to just delete those files and re-authenticate.
  • Incorrect Scopes: Requesting the wrong permissions is a classic mistake. If you get a 403 Forbidden error, double-check that the scopes you requested in the script match the actions you’re trying to perform.

Conclusion

And there you have it. It might look like a lot of code, but the logic is quite linear: authenticate, fetch from source, and add to destination. By breaking the problem down into small, manageable functions, we’ve built a reliable and automated bridge between two of the most popular music platforms. Now you can spend less time on manual playlist management and more time actually enjoying the music. Happy syncing!

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ How does the script manage authentication for Spotify and YouTube?

The script uses `SpotifyOAuth` for Spotify and `google_auth_oauthlib.flow.InstalledAppFlow` for YouTube, initiating a browser-based OAuth2 flow on first run and saving token files locally for persistent authentication.

âť“ What are the advantages of this custom script over commercial music syncing services?

This custom Python script provides a free, open-source, and highly customizable solution specifically for syncing Spotify ‘Liked Songs’ to YouTube, offering greater control and transparency compared to potentially paid or less flexible commercial alternatives.

âť“ What are common pitfalls to avoid when setting up this Spotify to YouTube sync?

Common pitfalls include hitting YouTube Data API daily quotas, potential expiration or invalidation of authentication tokens (requiring token file deletion and re-authentication), and ensuring correct API scopes are requested to avoid 403 Forbidden errors.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading