simonschellaert / spotify2am Goto Github PK
View Code? Open in Web Editor NEWImport your Spotify library into Apple Music
Import your Spotify library into Apple Music
I followed all the steps and when I execute insert-songs.py in the terminal, it issued this error:
urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:600)
Would there be any way to verify the certificate in iTunes?
hi ! don't know what i'm doin wrong but i get this mon my IDLE terminal when running insert-song.py :
====== RESTART: /Users/mac/Downloads/spotify2am-master/insert-songs.py ======
Something went wrong while inserting 376421737 : [Errno 54] Connection reset by peer
Would be nice to export the not found songs into a seperate .csv to manually add them into itunes. Your script finds up to 90% of my playlists.
I enabled SSL for every itunes connection in Charles (there is no option to enable all) and when I open iTunes I get this message:
The certificate for this server is invalid. You might be connecting to a server that is pretending to be “init.itunes.apple.com”, which could put your confidential information at risk. Would you like to connect to the server anyway?
Then when I accept, nothing shows up in apple music and I get a blank screen.
This also happens for the following servers:
“init.itunes.apple.com”
“upp.itunes.apple.com”
“se2.itunes.apple.com”
I put the spotify.csv file in the same directory as the .py files, but nothing seems to happen when I run them. It doesn't seem to find the csv file in that directory. Any ideas?
Without that, the service kept returning 401 error. Of course, the User-Agent needs to be copied from intercepted request, just like with other headers.
All ready to go, and receiving an index error when running the first script. I am new to python, so this could very much be user error on my part. Google pointed me in the right direction, but figured it was just better to post it here.
python3 retrieve-identifiers.py
Traceback (most recent call last):
File "retrieve-identifiers.py", line 43, in
title, artist = row[1], row[2]
IndexError: list index out of range
Maxs-MBP:spotify2am mknutsen$ PYTHONIOENCODING=utf-8 python3 retrieve-identifiers.py
Traceback (most recent call last):
File "retrieve-identifiers.py", line 40, in
next(playlist_reader)
File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/codecs.py", line 319, in decode
(result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd1 in position 8: invalid continuation byte
When I run retrieve-identifiers.py it doesn't find a single match. The script creates an empty itunes.csv file. Am I missing something?
Hi,
I am unable to find the https://ld-4.itunes.apple.com/WebObjects/MZDaap.woa/daap/databases/1/cloud-add request on Charles, therefore, unable to populate the IDs in the script. Please let me know if I am missing something or whether I can get the IDs some other way.
I'm getting Something went wrong while inserting 965221594 : must be bytes or read-only buffer, not bytearray after setting the cookies values and running the script
python3 insert-songs.py
Any ideas?
Updated the script to use sorting and DictReader. Not clean code but works. Was considering making async too for speed, this has been running for 3 days lol. But i have like 10k songs so yk. This sorts by date, oldest first added in playlist.
from sys import argv
import csv
import urllib.parse, urllib.request
import json
from time import sleep
import requests
import os
# Checking if the command is correct
if len(argv) > 1 and argv[1]:
pass
else:
print(
"\nCommand usage:\npython3 convertsongs.py yourplaylist.csv\nMore info at https://github.com/therealmarius/Spotify-2-AppleMusic"
)
exit()
# Function to get contents of file if it exists
def get_connection_data(f, prompt):
if os.path.exists(f):
with open(f, "r") as file:
return file.read().rstrip("\n")
else:
return input(prompt)
def create_apple_music_playlist(session, playlist_name):
url = "https://amp-api.music.apple.com/v1/me/library/playlists"
data = {
"attributes": {
"name": playlist_name,
"description": "A new playlist created via API",
}
}
# test if playlist exists and create it if not
response = session.get(url)
if response.status_code == 200:
for playlist in response.json()["data"]:
if playlist["attributes"]["name"] == playlist_name:
print(f"Playlist {playlist_name} already exists!")
return playlist["id"]
response = session.post(url, json=data)
if response.status_code == 201:
sleep(1.5)
return response.json()["data"][0]["id"]
else:
raise Exception(
f"Error {response.status_code} while creating playlist {playlist_name}!"
)
return None
# Getting user's data for the connection
token = get_connection_data(
"token.dat", "\nPlease enter your Apple Music Authorization (Bearer token):\n"
)
media_user_token = get_connection_data(
"media_user_token.dat", "\nPlease enter your media user token:\n"
)
cookies = get_connection_data("cookies.dat", "\nPlease enter your cookies:\n")
# playlist_identifier = input("\nPlease enter the playlist identifier:\n")
# function to escape apostrophes
def escape_apostrophes(s):
return s.replace("'", "\\'")
# Function to get the iTunes ID of a song
def get_itunes_id(title, artist, album):
BASE_URL = "https://itunes.apple.com/search?country=FR&media=music&entity=song&limit=5&term="
# Search the iTunes catalog for a song
try:
# Search for the title + artist + album
url = BASE_URL + urllib.parse.quote(title + " " + artist + " " + album)
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
data = json.loads(response.read().decode("utf-8"))
# If no result, search for the title + artist
if data["resultCount"] == 0:
url = BASE_URL + urllib.parse.quote(title + " " + artist)
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
data = json.loads(response.read().decode("utf-8"))
# If no result, search for the title + album
if data["resultCount"] == 0:
url = BASE_URL + urllib.parse.quote(title + " " + album)
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
data = json.loads(response.read().decode("utf-8"))
# If no result, search for the title
if data["resultCount"] == 0:
url = BASE_URL + urllib.parse.quote(title)
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
data = json.loads(response.read().decode("utf-8"))
except:
return print("An error occured with the request.")
# Try to match the song with the results
try:
response = urllib.request.urlopen(request)
data = json.loads(response.read().decode("utf-8"))
for each in data["results"]:
# Trying to match with the exact track name, the artist name and the album name
if (
each["trackName"].lower() == title.lower()
and each["artistName"].lower() == artist.lower()
and each["collectionName"].lower() == album.lower()
):
return each["trackId"]
# Trying to match with the exact track name and the artist name
elif (
each["trackName"].lower() == title.lower()
and each["artistName"].lower() == artist.lower()
):
return each["trackId"]
# Trying to match with the exact track name and the album name
elif (
each["trackName"].lower() == title.lower()
and each["collectionName"].lower() == album.lower()
):
return each["trackId"]
# Trying to match with the exact track name and the artist name, in the case artist name are different between Spotify and Apple Music
elif each["trackName"].lower() == title.lower() and (
each["artistName"].lower() in artist.lower()
or artist.lower() in each["artistName"].lower()
):
return each["trackId"]
# Trying to match with the exact track name and the album name, in the case album name are different between Spotify and Apple Music
elif each["trackName"].lower() == title.lower() and (
each["collectionName"].lower() in album.lower()
or album.lower() in each["collectionName"].lower()
):
return each["trackId"]
# Trying to match with the exact track name
elif each["trackName"].lower() == title.lower():
return each["trackId"]
# Trying to match with the track name, in the case track name are different between Spotify and Apple Music
elif (
title.lower() in each["trackName"]
or each["trackName"].lower() in title.lower()
):
return each["trackId"]
try:
# If no result, return the first result
return data["results"][0]["trackId"]
except:
# If no result, return None
return None
except:
# The error is handled later in the code
return None
# Function to add a song to a playlist
def add_song_to_playlist(session, song_id, playlist_id, playlist_name):
try:
request = session.post(
f"https://amp-api.music.apple.com/v1/me/library/playlists/{playlist_id}/tracks",
json={"data": [{"id": f"{song_id}", "type": "songs"}]},
)
# Checking if the request is successful
if requests.codes.ok:
print(f"Song {song_id} added to playlist {playlist_name}!")
return True
# If not, print the error code
else:
print(
f"Error {request.status_code} while adding song {song_id} to playlist {playlist_name}!"
)
return False
except:
print(
f"HOST ERROR: Apple Music might have blocked the connection during the add of {song_id} to playlist {playlist_name}!\nPlease wait a few minutes and try again.\nIf the problem persists, please contact the developer."
)
return False
def get_playlist_track_ids(session, playlist_id):
# test if song is already in playlist
try:
response = session.get(
f"https://amp-api.music.apple.com/v1/me/library/playlists/{playlist_id}/tracks"
)
if response.status_code == 200:
# print(response.json()['data'])
return [
track["attributes"]["playParams"]["catalogId"]
for track in response.json()["data"]
]
elif response.status_code == 404:
return []
else:
raise Exception(
f"Error {response.status_code} while getting playlist {playlist_id}!"
)
return None
except:
raise Exception(f"Error while getting playlist {playlist_id}!")
return None
from datetime import datetime
def parse_date(date_string):
return datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%SZ")
# Opening session
def create_playlist_and_add_song(file):
with requests.Session() as s:
s.headers.update(
{
"Authorization": f"{token}",
"media-user-token": f"{media_user_token}",
"Cookie": f"{cookies}".encode("utf-8"),
"Host": "amp-api.music.apple.com",
"Accept-Encoding": "gzip, deflate, br",
"Referer": "https://music.apple.com/",
"Origin": "https://music.apple.com",
"Content-Length": "45",
"Connection": "keep-alive",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site",
"TE": "trailers",
}
)
# Getting the playlist name
playlist_name = os.path.basename(file)
playlist_name = playlist_name.split(".")
playlist_name = playlist_name[0]
playlist_name = playlist_name.replace("_", " ")
playlist_identifier = create_apple_music_playlist(s, playlist_name)
playlist_track_ids = get_playlist_track_ids(s, playlist_identifier)
print(playlist_track_ids)
# Opening the inputed CSV file
with open(str(file), encoding="utf-8") as file:
data = list(csv.DictReader(file))
data.sort(key=lambda row: parse_date(row["Added At"]))
converted = 0
failed = 0
n = 0
for n, row in enumerate(data):
# Trying to get the iTunes ID of the song
title, artist, album = (
escape_apostrophes(row["Track Name"]),
escape_apostrophes(row["Artist Name(s)"]),
escape_apostrophes(row["Album Name"]),
)
track_id = get_itunes_id(title, artist, album)
# If the song is found, add it to the playlist
if track_id:
if str(track_id) in playlist_track_ids:
print(f"\nN°{n} | {title} | {artist} | {album} => {track_id}")
print(f"Song {track_id} already in playlist {playlist_name}!")
failed += 1
continue
print(f"\nN°{n} | {title} | {artist} | {album} => {track_id}")
sleep(0.5)
if add_song_to_playlist(s, track_id, playlist_identifier, playlist_name):
converted += 1
else:
failed += 1
# If not, write it in a file
else:
print(f"N°{n} | {title} | {artist} | {album} => NOT FOUND")
with open(f"{playlist_name}_noresult.txt", "a+", encoding="utf-8") as f:
f.write(f"{title} | {artist} | {album} => NOT FOUND")
f.write("\n")
failed += 1
sleep(1.5)
# Printing the stats report
converted_percentage = round(converted / n * 100) if n > 0 else 100
print(
f"\n - STAT REPORT -\nPlaylist Songs: {n}\nConverted Songs: {converted}\nFailed Songs: {failed}\nPlaylist converted at {converted_percentage}%"
)
if __name__ == "__main__":
if len(argv) > 1 and argv[1]:
if ".csv" in argv[1]:
create_playlist_and_add_song(argv[1])
else:
# get all csv files in the directory argv[1]
files = [
f
for f in os.listdir(argv[1])
if os.path.isfile(os.path.join(argv[1], f))
]
# loop through all csv files
for file in files:
if ".csv" in file:
create_playlist_and_add_song(os.path.join(argv[1], file))
# Developped by @therealmarius on GitHub
# Based on the work of @simonschellaert on GitHub
# Github project page: https://github.com/therealmarius/Spotify-2-AppleMusic
Hi,
I don't find any X-Guid line under Charles anymore in iTunes version 12.2.1.
Anyone else having these problems?
i get an error trying to run "retrieve-identifiers.py"
~/Desktop/spotify2am-master/retrieve-identifiers.py:3: ImportError: No module named parse
I run the script in TextWrangler
Looks like apple has update their api, does it still work for you guys?
When adding a song I am seeing this:
Here are my headers:
headers = {
"X-Apple-Store-Front" : "143446-10,32 ab:rSwnYxS0",
"Client-iTunes-Sharing-Version" : "3.12",
"Accept-Language" : "nl-nl, nl;q=0.83, fr-fr;q=0.67, fr;q=0.50, en-us;q=0.33, en;q=0.17",
"Client-Cloud-DAAP-Version" : "1.0/iTunes-12.2.0.145",
"Accept-Encoding" : "gzip",
"X-Apple-itre" : "0",
"Client-DAAP-Version" : "3.13",
"User-Agent" : "iTunes/12.2.2 (Macintosh; OS X 10.10.5) AppleWebKit/600.8.9",
"Connection" : "keep-alive",
"Content-Type" : "application/x-dmap-tagged",
# Replace the values of the next three headers with the values you intercepted
"X-Dsid": "...",
"Cookie": "...",
"X-Guid": "...",
"Content-Length" : "77"
}
Looks like there is a newer version of the api out there already?
"Client-Cloud-DAAP-Version": "1.0/iTunes-12.2.2.25",
Seems to stop over night. Would this script be attempting to add songs it's already added if i quit and try again? I'm not getting any error messages. It just seems to stop over night, as if it's timed out.
That would be nice to support multiple playlist file identifying in ‘retrieve-identifier.py’
Personally I managed to do it this way !
def findFiles (path, filter):
for root, dirs, files in os.walk(path):
for file in fnmatch.filter(files, filter):
yield os.path.join(root, file)
itunes_identifiers = []
itunes_dir = "itunes"
if not os.path.exists(itunes_dir):
os.makedirs(itunes_dir)
execution_dir = os.path.dirname(os.path.realpath(__file__))
for csv_files in findFiles(execution_dir, '*.csv'):
csv_file_name = os.path.basename(csv_files)
playlist_name, playlist_extension = os.path.splitext(csv_file_name)
print("Found a playlist file named {}".format(playlist_name))
with open(csv_file_name, encoding='utf-8') as playlist_file:
playlist_reader = csv.reader(playlist_file)
next(playlist_reader)
for row in playlist_reader:
title, artist = row[1], row[2]
itunes_identifier = retrieve_itunes_identifier(title, artist)
if itunes_identifier:
itunes_identifiers.append(itunes_identifier)
print("{} - {} => {}".format(title, artist, itunes_identifier))
else:
print("{} - {} => Not Found".format(title, artist))
with open(execution_dir+'/itunes/'+csv_file_name, 'w', encoding='utf-8') as output_file:
for itunes_identifier in itunes_identifiers:
output_file.write(str(itunes_identifier) + "\n")
There is 2 steps more in order to completely recreate playlists in iTunes after importing all the songs in "my music" (with your scripts)
That would be great to merge the scripts to provide a complete import process like this
I'm getting HTTP Error 401: Apple WebObjects.
If I delete the entries, they are reimported within seconds.
The STAMP application is using your Python script in their paid app (at least the Windows version).
Download the Windows installer and unpack it in 7zip.
Just thought you'd like to know.
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 524: ordinal not in range(128)
I got the cookie, x-dsid and x-guid values but I get the HTTP error 403. I played with the request time.
Any ideas?
I am getting a 404 error... Any idea what's wrong?
Ben@Bens-MacBook-Pro ~/N/spotify2am> python3 retrieve-identifiers.py
New Americana - Halsey => 1013141957
Ben@Bens-MacBook-Pro ~/N/spotify2am> python3 insert-songs.py
Something went wrong while inserting 1013141957 : HTTP Error 404: Apple WebObjects
headers = {
"Host": "ld-4.itunes.apple.com:443",
"Proxy-Connection": "keep-alive",
"X-Apple-Store-Front": "143441-1,32",
"Client-iTunes-Sharing-Version": "3.12",
"Accept-Language": "en-us, en;q=0.50",
"Client-Cloud-DAAP-Version": "1.0/iTunes-12.2.2.25",
"Accept-Encoding": "gzip",
"X-Apple-itre": "0",
"Content-Length": "77",
"Client-DAAP-Version": "3.13",
"User-Agent": "iTunes/12.2.2 (Macintosh; OS X 10.10.5) AppleWebKit/600.8.9",
"Connection": "keep-alive",
"Content-Type": "application/x-dmap-tagged",
"X-Dsid": "160719XXXX",
"Cookie": "a=QQABAAAAXXXX=; isPpuOptOut=1; itspod=56; mz_at0-1607190730=AwQAAAEBAAHVXXXXXwMCI=; ns-mzf-inst=183-84-443-220-12-8188-562333-56-st13; s_fid=528F01B78XXXXXXXXXX; s_vi=[CS]v1|XXXX[CE]; s_vnum_n2_us=4|1; TrPod=4; xp_ci=3z14G3xXXXXXXXXX",
"X-Guid": "6003089XXXX"
}
This is great, I was able to add a song to My Music. Thanks!
Is there any way to add a song to a playlist or love a song? Charles shows this when I love a song - /WebObjects/MZDaap.woa/daap/databases/1/edit but when I tried that URI it gave me a 403.
Also, for search, Apple's official API works well too - https://itunes.apple.com/search?media=music&term=get lucky daft punk
Docs here - https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html
Anyone have an idea why I'm only getting "blank" songs inserted into my library after running the "insert-songs.py" script? The correct number of songs is inserted but every song has a blank artist & title.
Everything goes smoothly with Exportify creating a nice csv of my playlist, then "retrieve-identifiers.py" seems to create a nice file of matched identifiers.
As a test, I created a simple Playlist of Katy Perry's Prism album (Deluxe version - 16 tracks). 16 identifiers show up in the "spotify.csv" file and after running "retrieve-identifiers.py", I have 16 identifiers in the "itunes.csv" file. I then run the "insert-songs.py" script and end up with 16 blank songs... Not sure what is happening. The only difference I see compared to Simon's instructions is that the Headers came from ID-7 not ID-4 (as viewed from within Charles).
Any ideas?
Thanks,
Scott
Change in readme.md "Next, select a random song on Apple Music you don't have in your library yet, right click and choose 'Add to library'. " to "Next, select a random song on Apple Music you don't have in your library yet, right click and choose 'Add to My music'. " to make it more clear what to do.
Hello,
Got this going relatively easily but when inserting the playlist into Apple Music the first song inserted successfully but then the remaining ones gave an Errno 61. See below. Not sure what's up. The only deviating from your instructions was that the Headers came from ID-7 not ID-4 as in your description. Pretty sure SSL Proxy is working properly too. I was able to extract the needed headers from ID-7 and place in the insert-songs.py source. Any ideas what went wrong? Thanks for everything so far!
-Scott
Successfuly inserted a song!
Something went wrong while inserting 319136607 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 505043366 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 505043409 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 582961193 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 925559305 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 418606885 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 690820139 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 714615837 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 605378882 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 342312349 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 829910927 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 829910927 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 829910927 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 829910927 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 257374174 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 200923494 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 884219013 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 153444846 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 153444846 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 947421436 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 941583036 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 291917674 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 944020126 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 700689873 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 309858759 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 850380096 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 850380091 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 497410040 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 301537185 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 928428141 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 891452572 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 891452577 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 891452572 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 672805896 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 350322526 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 699607089 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 573885278 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 971828530 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 936832279 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 964545871 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 928428141 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 928428141 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 928428141 : <urlopen error [Errno 61] Connection refused>
Something went wrong while inserting 928428141 : <urlopen error [Errno 61] Connection refused>
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.