Photo by Kaboompics.com from Pexels
Just want the code? Got ya covered. GitHub
Right. So I've been using Spotify for some years now. I have a collection of carefully curated playlists and liked music. I'm loathe to move off the platform and start over with another platform. But, YouTube music comes free with my YouTube premium subscription... so, it's a bit silly for me to keep shelling out $$ to Spotify each month. What a conundrum.
Doing a quick search, there are plenty of third-party apps that purport to transfer your playlists for you. You just log into your Spotify and YouTube accounts through their webapp and away you go. I find this kind of service... sketchy, though - you're essentially giving that site your logins/auth tokens for your music accounts. The service is free, so I'd put money on them scraping your accounts for metadata they can sell. And if you've read any of my other posts, you probably know how much this irks me. I value my privacy. And perhaps, you value yours.
The good news is these webapp services aren't doing anything that we can't do ourselves. They're accessing the Spotify and YouTube developer APIs to grab data from one account and shove it into the other. Building a tool to do this from scratch wouldn't take too long, probably. But this seems like something a lot of people would find useful - which makes it super likely someone in the open source community has already done something like it. Let's check github.
There's this project, exportify, that exports playlists from Spotify into CSV format. This sounds great. CSVs are easy to work with, so we should be able to take this data and import it into YouTube music, even if we have to do a little massaging to make it the right format.
We could download their source and try to make it run, but it looks like the developer hosts the webapp here. While this is basically the same setup as the third party web apps I detest, I have a lot more trust in the open source community than I do random results from Google. I'll use it.
Okay, after logging into my Spotify account via exportify, I get a list of all my playlists. I grabbed my liked songs and a couple other playlists. The CSVs are full of all sorts of spotify-specific data that we probably don't need.
Let's take a look at what formats YouTube Music accepts for import. Oh, ugh. YouTube Music doesn't appear to offer any sort of convenient import function. What's more is it doesn't seem to have any API of its own - the YouTube API is basically also its API, maybe. That seems about right for Google, which is making all the same mistakes that Microsoft did back in the 90s.
Anyway, I digress. There exists this unofficial API, which we might be able to use. Let's give it a try.
The API is python (yessss), so we'll make a virtual environment for playing with the API. This is always good practice, as it keeps one project's dependencies from interfering with another. If you install all of your python packages to your global python install, after a while you can end up with conflicts that break things in weird and unexpected ways. Just better to compartmentalize.
$ virtualenv -p python3 venv
That creates a virtual environment for python3 in a new directory, venv. I'm on Linux, so that's what the terminal command looks like. I haven't used Windows seriously in a very long time - if you're a Windows or Mac user, you're gonna have to find your own way (on Mac it's probably the same thing).
source venv/bin/activate
will activate the virtual environment. Now we're playing in the sandbox with a fresh python3 install. Let's grab the unofficial YouTube Music API package.
pip install ytmusicapi
Follow the rest of the setup instructions to get an authenticated session. You can press ctrl+shift+c in Chrome to get to the developer console, and click on the Network tab to see the network traffic. You may have to click on some content or search for some music before you'll have the appropriate POST request to snag.
Their instructions were a bit unclear to me - copy-pasting all the header data from accept
onward didn't really work... probably because without some tweaking, the copy-paste content is an improperly formed JSON file. All you actually need for auth is the cookie, so I used the "manual file creation" method and pasted in my own cookie, and that worked fine.
Now that we're auth'd, we can play around with the API. I tried a quick song search to verify that the API seemed to work as expected, using a few lines of code:
from ytmusicapi import YTMusic
ytmusic = YTMusic("auth_file.json")
search_results = ytmusic.search('Oasis Wonderwall')
print(search_results)
It barfed back some results in JSON format. Lovely.
Wandering through the reference for the API, I'm particularly interested in create_playlist
and add_playlist_items
. I'll try creating a test playlist, and see if it shows up in my account.
from ytmusicapi import YTMusic
ytmusic = YTMusic("/home/dizzyspiral/projects/spotify-to-youtube/auth_file.json")
playlist_id = ytmusic.create_playlist(title="Test Playlist", description="Testing 1, 2, 3")
print(playlist_id)
It worked. Excellent. Now, about add_playlist_items
... It requires a YouTube video ID in order to add a song, so it looks like we'll have to first search for a song, grab the corresponding music video ID, and add it to the playlist using that. But when we searched for Wonderwall, we got a bunch of results. How do we know which one is the "right" one...
Nothing for it - we don't. But the search results are ordered from most to least relevant, with the "top result" labeled in the JSON and appearing first. I'll just trust that that's the right one.
Okay, now we come back around to our exported Spotify playlists. We need the song title and artist in order to search for the song using ytmusicapi. Each row of the exported playlist CSV is a song, and the track name and artist name are in the second and fourth fields, or indices 1 and 3, respectively. Python has a csv module for handling CSVs. We'll write a little code to import and read the CSV.
import csv
from ytmusicapi import YTMusic
def load_spotify_playlist(playlist_file):
songs = []
with open(playlist_file) as csvfile:
reader = csv.reader(csvfile, delimiter=',', quotechar='"')
for row in reader:
songs.append("{}{}".format(row[1], row[3]))
return songs
As we noted earlier, the top result is returned first by the search API call. But, the very first result is some kind of "top result" result, which actually doesn't contain a video ID. And we need the video ID. The second result should have it. (My guess here is that the "top result" thing is just metadata for displaying the top result separately from the rest of the results, since these "API" calls are really only intended to be used by the actual YouTube Music app(s).)
def search_for_songs(ytmusic, search_strings):
video_ids = []
for s in search_strings:
result = ytmusic.search(s)
video_ids.append(result[1]['videoId'])
return video_ids
Finally, to put it all together, we'll make a main function:
if __name__ == '__main__':
print("Connecting to YouTube Music...")
ytmusic = YTMusic("auth_file.json")
playlist_file = "exported_playlist.csv"
print("Loading exported Spotify playlist...")
songs = load_spotify_playlist(playlist_file)
print("Searching for songs on YouTube...")
video_ids = search_for_songs(ytmusic, songs)
print("Creating playlist...")
# See if the playlist exists already before creating it
try:
res = ytmusic.search("Some Playlist", filter="playlists", scope="library")
playlist_id = res[0]['browseId']
except:
playlist_id = ytmusic.create_playlist("Some Playlist", "Some description")
print("Adding songs...")
for video_id in video_ids:
ytmusic.add_playlist_items(playlist_id, [video_id])
print("Done!")
While the API claims that add_playlist_items
takes a list of strings as an argument, for whatever reason, that doesn't actually work. It silently fails to add any songs. So, we just iterate through our list of strings, artificially making them a list of one for each API call. It takes a bit longer, but it gets the job done.
And that's it. It's not pretty, but it was a one-day hack. I'll probably toss the code on github after cleaning it up a bit.
Cheers.
Update: Code is here.
Top comments (1)
Could you show what is there in
auth_file.json
file? I am getting *json encoder error * form all ytmusic methods