Automate Spotify and YouTube Playlists – Chapter 4: Writing the Code

작성자

카테고리:

← 피드로
DEV Community · Tawanda Nyahuye · 2026-07-23 개발(SW)

We have our keys. Now we write the script.

By the end of this chapter, you’ll understand exactly what the code does and why — line by line, in plain English. No assumed knowledge. If something still doesn’t make sense after reading, drop a comment, and I’ll fix the explanation.

The project has three files:

youtube-to-spotify/
├── spotify.py       ← knows how to talk to Spotify
├── youtube.py       ← knows how to talk to YouTube
├── script.py        ← the brain — runs the sync
└── .env             ← your private credentials (never shared)

Enter fullscreen mode Exit fullscreen mode

We’ll go through each one.

The .env File

This isn’t code. It’s just a text file that holds your six credentials from Chapters 2 and 3. Create a file called .env in your project folder and fill it in:

CLIENT_ID=your_spotify_client_id
CLIENT_SECRET=your_spotify_client_secret
REFRESH_TOKEN=your_spotify_refresh_token
SPOTIFY_PLAYLIST_ID=your_spotify_playlist_id
API_KEY=your_youtube_api_key
YOUTUBE_PLAYLIST_ID=PLGBuKfnErZlAkaUUy57-mR97f8SBgMNHh

Enter fullscreen mode Exit fullscreen mode

That last line is already filled in — that’s the 70s playlist we’re using. The rest you replace with your own values from the previous chapters.

This file stays on your computer. It never goes to GitHub. We’ll make sure of that in Chapter 6.

youtube.py — Reading the YouTube Playlist

This file is responsible for one thing: talking to YouTube. It has three jobs we care about.

Job 1: Fetch the playlist videos

def get_playlist_items(self, playlist_id):
    request = self.youtube.playlistItems().list(
        part='snippet',
        playlistId=playlist_id,
        maxResults=50
    )

    videos = []
    while request:
        response = request.execute()
        for item in response['items']:
            title = item['snippet']['title']
            video_id = item['snippet']['resourceId']['videoId']
            videos.append((title, f"https://www.youtube.com/watch?v={video_id}"))
        request = self.youtube.playlistItems().list_next(request, response)
    return videos

Enter fullscreen mode Exit fullscreen mode

YouTube’s API only gives you 50 videos at a time. Our playlist has 150. So the while request loop keeps asking “give me the next 50” until there are no more pages left. By the end, videos is a list of 150 (title, url) pairs.

A typical entry looks like:

("Stayin' Alive - Bee Gees (Official Music Video)", "https://www.youtube.com/watch?v=I_izvAbhExY")

Enter fullscreen mode Exit fullscreen mode

Job 2: Figure out the song and artist from the title

YouTube video titles are a mess. Every creator formats them differently:

Stayin' Alive - Bee Gees (Official Music Video)
Bee Gees - Stayin' Alive | Lyrics
ABBA - Dancing Queen (Official)
Don't Stop Me Now [Queen] HD

Enter fullscreen mode Exit fullscreen mode

There’s no rule. So we try to make sense of them:

def extract_song_and_artist(self, title):
    if "-" in title:
        parts = title.split("-")
        if len(parts) == 2:
            artist_first = parts[0].strip()
            song_first = parts[1].strip()

            song_second = parts[0].strip()
            artist_second = parts[1].strip()

            return (song_first, artist_first), (song_second, artist_second)

    return (title, ""), (title, "")

Enter fullscreen mode Exit fullscreen mode

When we see a - in the title, we split on it and return both possible interpretations: “Artist – Song” and “Song – Artist”. Later, the main script tries both on Spotify and keeps whichever finds a match.

If there’s no -, we treat the whole title as the song name and search with no artist. A broad search often still works.

Job 3: Clean up the noise

Before we search Spotify, we strip all the YouTube clutter — “(Official Music Video)”, “[HD]”, “ft.”, “#lyrics”, and about 40 other variations that would confuse the search:

def clean_title(self, title):
    title = re.sub(r'(.*?)|[.*?]|{.*?}|#S+', '', title)
    # ... removes "Official Video", "Lyrics", "HD", etc.
    title = re.sub(r's+', ' ', title).strip()
    return title

Enter fullscreen mode Exit fullscreen mode

So "Stayin' Alive - Bee Gees (Official Music Video)" becomes "Stayin Alive - Bee Gees" before we send it to Spotify. Much cleaner search.

spotify.py — Talking to Spotify

This file handles everything on the Spotify side. Four functions matter most for our sync.

Staying logged in: refresh_access_token

def refresh_access_token(self):
    # Uses your CLIENT_ID, CLIENT_SECRET, and REFRESH_TOKEN
    # to get a fresh access token every time the script runs

Enter fullscreen mode Exit fullscreen mode

Spotify access tokens expire after one hour. The Refresh Token you got in Chapter 2 lets us generate a new one automatically every time the script starts. This is why the script can run unattended on a schedule — it never needs you to log in again.

Searching for a song

def search_song(self, query, limit=10):
    endpoint = f'search?q={query}&type=track&limit=10'
    # ... asks Spotify for the top 10 results
    # ... then runs through three matching passes

Enter fullscreen mode Exit fullscreen mode

This function doesn’t just take the first result and hope for the best. It runs three passes:

Pass 1 — Is the full search query a substring of the track name or artist? If yes, that’s almost certainly the right song.

Pass 2 — Do two or more artist names from the result appear in our query? A query like “Stayin Alive Bee Gees” matches “Stayin’ Alive by Bee Gees” because “Bee Gees” is in both.

Pass 3 — Do at least two words from our query appear in the track title? Last resort, but catches songs where the artist name wasn’t in the YouTube title at all.

If all three passes fail, the function returns None and the script logs the song as not found.

Adding and removing tracks

def add_tracks_to_playlist(self, playlist_id, track_uris):
    # Adds tracks in batches of 100 (Spotify's limit per request)

def remove_tracks_from_playlist(self, playlist_id, track_uris):
    # Removes tracks in batches of 100

Enter fullscreen mode Exit fullscreen mode

Spotify’s API only accepts 100 tracks per request, so both functions split large lists into batches automatically. You don’t have to think about this — it just works.

Reordering to match YouTube

def reorder_playlist_tracks(self, playlist_id, track_uris):
    # Moves tracks one by one until the order matches YouTube

def reorder_playlist_many_tracks(self, playlist_id, track_uris):
    # More efficient version for playlists over 100 tracks

Enter fullscreen mode Exit fullscreen mode

Because our playlist has 150 songs, the script automatically uses reorder_playlist_many_tracks. The order of your Spotify playlist will match the YouTube playlist exactly after every sync.

script.py — The Brain

This is the file you actually run. It loads your credentials, connects to both APIs, and calls the sync function.

def main():
    load_dotenv()  # reads your .env file

    client_id = os.getenv('CLIENT_ID')
    client_secret = os.getenv('CLIENT_SECRET')
    # ... loads all six values

    spotify = SpotifyAPI(client_id, client_secret, refresh_token)
    youtube = YouTubeAPI(api_key)

    sync_playlist(spotify_playlist_id, youtube_playlist_id, spotify, youtube)

Enter fullscreen mode Exit fullscreen mode

load_dotenv() reads the .env file and makes its values available as environment variables. os.getenv('CLIENT_ID') picks them up. The script then checks that none of them are missing — if you forgot to fill in one of the six values, it tells you exactly which one is missing instead of crashing with a confusing error.

The sync function, step by step

def sync_playlist(spotify_playlist_id, youtube_playlist_id, spotify, youtube, remove=True):

Enter fullscreen mode Exit fullscreen mode

Step 1 — Fetch YouTube videos

youtube_videos = youtube.get_playlist_items(youtube_playlist_id)

Enter fullscreen mode Exit fullscreen mode

Gets all 150 videos from our 70s playlist, with their titles.

Step 2 — Search each one on Spotify

for i, (title, url) in enumerate(youtube_videos, start=1):
    (song_a, artist_a), (song_b, artist_b) = youtube.extract_song_and_artist(title)

    query_a = f"{youtube.clean_title(song_a)} {youtube.clean_title(artist_a)}".strip()
    query_b = f"{youtube.clean_title(song_b)} {youtube.clean_title(artist_b)}".strip()

    result = spotify.search_song(query_a) or spotify.search_song(query_b)

Enter fullscreen mode Exit fullscreen mode

For each video, we build two queries (both possible Artist/Song orderings), clean them up, and try them on Spotify. or means: try query_a first, and only try query_b if query_a found nothing.

Step 3 — Try bracket alternatives

for alt in (youtube.extract_bracket_alternatives(song_a) + ...):
    result = spotify.search_song(alt_query)

Enter fullscreen mode Exit fullscreen mode

Some videos hide an alternative song title in brackets: "Stayin' Alive (From Saturday Night Fever Soundtrack)". If the main queries failed, we try searching with the text inside the brackets as a last resort.

Step 4 — Add, remove, reorder

to_add = [uri for uri in found_tracks if uri not in spotify_uris]
spotify.add_tracks_to_playlist(spotify_playlist_id, to_add)

to_remove = [uri for uri in spotify_uris if uri not in found_tracks]
spotify.remove_tracks_from_playlist(spotify_playlist_id, to_remove)

spotify.reorder_playlist_many_tracks(spotify_playlist_id, found_tracks)

Enter fullscreen mode Exit fullscreen mode

Three list comparisons:

  • Tracks on YouTube but not on Spotify → add them
  • Tracks on Spotify but not on YouTube → remove them
  • Reorder what’s left to match YouTube’s order

Step 5 — Print the summary

print(f"YouTube videos:     {len(youtube_videos)}")
print(f"Found on Spotify:   {len(found_tracks)}")
print(f"Not found:          {len(not_found)}")
print(f"Added:              {len(to_add)}")
print(f"Removed:            {len(to_remove)}")

Enter fullscreen mode Exit fullscreen mode

When the script finishes, you get a clean summary of everything it did. On the first run, expect a big “Added” number. On every run after that, only the changes since the last sync will show.

Why Three Files Instead of One?

You might wonder why we didn’t just put everything in one big script.py.

The short answer is separation of concerns — each file knows about one thing. youtube.py knows YouTube. spotify.py knows Spotify. script.py knows the logic. If Spotify changes their API tomorrow, you only edit spotify.py. If you want to swap YouTube for a different music source, you only change youtube.py. The logic in script.py doesn’t need to know or care.

This is how real software is structured. You’ve just done it.

What’s Next?

In Chapter 5, we install Python, set up the project on your computer, and run the script for the first time. You’ll watch it pull all 150 songs from the 70s playlist and build your Spotify playlist live.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다