Last active
November 26, 2020 17:20
-
-
Save srli/66196d65709b73b69940c8313de9ece3 to your computer and use it in GitHub Desktop.
Playlist_Exporter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(*This is the AppleScript component to the iTunes -> Spotify playlist sync program | |
Written by Sophie Li: 12/29/16*) | |
try | |
tell application "Finder" | |
delete (every item of folder (((path to home folder) as text) & "Scripts:playlist_sync:playlists") whose name ends with ".xml") | |
end tell | |
on error errMsg number errNum | |
display dialog "Error " & errNum & return & return & errMsg buttons {"Cancel"} default button 1 | |
end try | |
tell application "iTunes" | |
set playlist_names to (get name of playlists) | |
set cleaned_pl to items 13 thru end of playlist_names | |
end tell | |
repeat with pl in cleaned_pl | |
if pl begins with "+" then | |
tell application "iTunes" to set pl_names to name of tracks of playlist pl | |
tell application "iTunes" to set pl_durations to time of tracks of playlist pl | |
set song_info to {} | |
repeat with n from 1 to count of pl_names | |
set song_info to song_info & ((item n of pl_names) & ":::" & (item n of pl_durations)) | |
end repeat | |
set {text item delimiters, TID} to {"+++", text item delimiters} | |
set {text item delimiters, song_info_str} to {TID, song_info as text} | |
set xml_file to "/Users/sophie/Scripts/playlist_sync/playlists/" & pl & ".xml" | |
try | |
open for access xml_file with write permission | |
set fileRef to result | |
write song_info_str to fileRef | |
close access fileRef | |
on error errMsg number errNum | |
try | |
close access fileRef | |
end try | |
display dialog "Error " & errNum & return & return & errMsg buttons {"Cancel"} default button 1 | |
end try | |
end if | |
end repeat |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
This is the Python component to the iTunes -> Spotify playlist sync program | |
Written by Sophie Li: 12/29/16 | |
""" | |
from os import listdir | |
import sys | |
import spotipy | |
import spotipy.util as util | |
import unicodedata | |
class SpotifyExporter: | |
def __init__(self): | |
scope = 'user-library-read playlist-modify-public' | |
self.username = "12159015810" | |
self.token = util.prompt_for_user_token(self.username, scope, client_id='YOUR ID', client_secret='YOUR SECRET' , redirect_uri='http://www.example.com/callback') | |
self.sp = spotipy.Spotify(auth=self.token) | |
self.path = "/Users/sophie/Scripts/playlist_sync/playlists/" #YOUR PATH HERE | |
self.raw_itunes_playlists = listdir(self.path) | |
def format_itunes_playlists(self): | |
itunes_playlists = {} | |
for pl in self.raw_itunes_playlists: | |
if pl.startswith("."): | |
continue | |
if not pl.endswith(".xml"): | |
continue | |
this_pl_songs = [] | |
with open(self.path+pl, 'r') as f: | |
songs = f.read().split("+++") | |
for song in songs: | |
if "??" in song: | |
continue | |
else: | |
try: | |
if "(" in song: | |
s1 = song.split("(")[0] | |
s2 = song.split(")")[1] | |
song = s1 + s2 | |
if "[" in song: | |
s1 = song.split("[")[0] | |
s2 = song.split("]")[1] | |
song = s1 + s2 | |
except IndexError: | |
pass | |
this_pl_songs.append(song) | |
if len(this_pl_songs) > 70: | |
print "playlist " + pl + " has too many songs" | |
continue | |
else: | |
itunes_playlists[pl.replace(".xml", "")] = this_pl_songs | |
return itunes_playlists | |
def return_spotify_tracks(self,tracks,all_tracks=None): | |
if not all_tracks: | |
all_tracks = [] | |
for item in tracks['items']: | |
track = item['track'] | |
song = unicodedata.normalize('NFKD', track['name']).encode('ascii','ignore') | |
try: | |
if "(" in song: | |
s1 = song.split("(")[0] | |
s2 = song.split(")")[1] | |
song = s1 + s2 | |
if "[" in song: | |
s1 = song.split("[")[0] | |
s2 = song.split("]")[1] | |
song = s1 + s2 | |
except IndexError: | |
pass | |
all_tracks.append(song) | |
return all_tracks | |
def get_spotify_playlists(self): | |
playlists = self.sp.user_playlists(self.username) | |
all_spotify_playlists = {} | |
for playlist in playlists['items']: | |
if playlist['owner']['id'] == self.username: | |
results = self.sp.user_playlist(self.username, playlist['id'], | |
fields="tracks,next") | |
tracks = results['tracks'] | |
playlist_tracks = self.return_spotify_tracks(tracks) | |
while tracks['next']: | |
tracks = self.sp.next(tracks) | |
playlist_tracks = self.return_spotify_tracks(tracks, playlist_tracks) | |
playlist_name = unicodedata.normalize('NFKD', playlist['name']).encode('ascii','ignore') | |
playlist_tracks.append(playlist['id']) | |
all_spotify_playlists[playlist_name] = playlist_tracks | |
return all_spotify_playlists | |
def diff_playlists(self, itunes, spotify): | |
diff_dict = {} | |
for pl_name in itunes.keys(): | |
itunes_songs = itunes.get(pl_name) | |
spotify_songs = spotify.get(pl_name, None) | |
if not spotify_songs: | |
diff_dict[pl_name + "__NEW__"] = itunes_songs | |
else: | |
spotify_songs_stripped = [s.replace(" ", "").lower() for s in spotify_songs] | |
songs_to_add = [] | |
for i, song in enumerate(itunes_songs): | |
song_name = song.split(":::")[0] | |
if song_name.replace(" ", "").lower() not in spotify_songs_stripped: | |
songs_to_add.append(song) | |
songs_to_add.append(spotify_songs[-1]) #we track pl ID by last elem of song list | |
diff_dict[pl_name] = songs_to_add | |
return diff_dict | |
def minsec_2_millis(self, time): | |
"""converts string time to integer ms | |
i.e 1:30 goes to 90000 | |
""" | |
try: | |
minutes = int(time.split(":")[0]) | |
seconds = int(time.split(":")[1]) | |
return minutes*60 + seconds | |
except ValueError: | |
print "Trying to convert none time value: ", time | |
return 0 | |
def update_spotify(self, playlists): | |
searched_songs = [] | |
passed_songs = [] #songs that have already been previously searched | |
try: | |
with open(self.path + "searched_songs.txt", "r") as f: | |
for line in f: | |
line = line.rstrip() | |
passed_songs.append(line.split("-- ")[1]) | |
except IOError: | |
print "Previously searched songs list is unavailable, using blank entry" | |
for pl_name in playlists.keys(): | |
print "***updating playlist " + pl_name | |
if "__NEW__" in pl_name: | |
playlist = self.sp.user_playlist_create(self.username, pl_name.replace("__NEW__", "")) | |
pl_id = playlist['id'] | |
songs = playlists[pl_name] | |
else: | |
songs = playlists[pl_name][:-1] | |
pl_id = playlists[pl_name][-1] | |
song_ids = [] | |
for song in songs: | |
if song in passed_songs: | |
print "PASSING OVER: ", song | |
continue | |
try: | |
song_name = song.split(":::")[0] | |
song_duration = self.minsec_2_millis(song.split(":::")[1]) | |
print "ADDING: ", song_name | |
results = self.sp.search(song_name, limit=10, type="track") | |
track = results['tracks']['items'] | |
if len(track) == 0: #song not found | |
searched_songs.append("NOT FOUND-- " + song) | |
else: | |
matched_track = next((t for t in track if int(t['duration_ms'])/1000 == song_duration), None) | |
if matched_track == None: | |
searched_songs.append("DUR MATCH NONE-- " + song) | |
song_ids.append(track[0]['id']) | |
else: | |
song_ids.append(matched_track['id']) | |
searched_songs.append("ADDED TO " + pl_name.upper() + "-- " + song) | |
except IndexError: | |
print "Song name is malformed: ", song | |
searched_songs.append("MALFORMED-- " + song) | |
continue | |
if len(song_ids) > 0: | |
print "adding songs to playlist..." | |
self.sp.user_playlist_add_tracks(self.username, pl_id, song_ids) | |
else: | |
print "no songs to update" | |
print "--------------------\n" | |
return searched_songs | |
def test_search(self, song): | |
results = self.sp.search(song, limit=10, type="track") | |
track = results['tracks']['items'] | |
print "GOT TRACKS: ", len(track) | |
matched_track = next(t for t in track if int(t['duration_ms']) == 117054) | |
print "MATCHED_TRACK: ", matched_track['id'] | |
def run(self): | |
print "unpacked itunes..." | |
itunes_playlists = self.format_itunes_playlists() | |
print "retriving spotify..." | |
spotify_playlists = self.get_spotify_playlists() | |
print "calculating diff dict..." | |
diff_dict = self.diff_playlists(itunes_playlists, spotify_playlists) | |
searched_songs = self.update_spotify(diff_dict) | |
with open(self.path + "searched_songs.txt", 'a') as f: | |
for item in searched_songs: | |
f.write("%s\n" % item) | |
print "done!" | |
if __name__ == '__main__': | |
se = SpotifyExporter() | |
se.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment