class GMusicPodcastSyncDestination(PodcastSyncDestination): def __init__(self): super(GMusicPodcastSyncDestination, self).__init__() self.serviceIdentifier = "GOO" self.serviceName = "Google Music" self.musicManager = Musicmanager() self.mobileClient = Mobileclient() oAuthFile = "gMusic.oauth" if not os.path.isfile(oAuthFile): if not self.musicManager.perform_oauth(oAuthFile, True): print "Failed to authenticate Music Manager." raise PodcastSyncDestinationException("Authentication Failure.") else: try: self.musicManagerauthenticated = self.musicManager.login(oAuthFile) except gmusicapi.exceptions.AlreadyLoggedIn: pass username = raw_input("Enter Google Username: "******"Enter Google Password: "******"Authentication Failure.") # perform a push task. should return a PodFastPodcastPushedEpisode instance def pushEpisode(self, podcastSyncTaskEpisodePush): (uploaded, matched, not_uploaded) = self.musicManager.upload([podcastSyncTaskEpisodePush.localFilename]) songGoogleMusicID = "" if not_uploaded: # If the track was not uploaded, it may have been uploaded in the past. p = re.compile("ALREADY_EXISTS\\((.*)\\)") m = p.findall(not_uploaded[podcastSyncTaskEpisodePush.localFilename]) songGoogleMusicID = m[0] else: songGoogleMusicID = uploaded[podcastSyncTaskEpisodePush.localFilename] print "Track uploaded Successfully. ID:" + songGoogleMusicID gmusicPlayLists = self.mobileClient.get_all_playlists() playListExists = False gmusicPlaylistID = "" for gmusicPlayList in gmusicPlayLists: if gmusicPlayList["name"] == podcastSyncTaskEpisodePush.playlistName: playListExists = True gmusicPlaylistID = gmusicPlayList["id"] break if not playListExists: print "Creating playlist..." gmusicPlaylistID = self.mobileClient.create_playlist(podcastSyncTaskEpisodePush.playlistName) addedEntries = self.mobileClient.add_songs_to_playlist(gmusicPlaylistID, [songGoogleMusicID]) print "Moved track to playlist." return songGoogleMusicID # Pull (deletes) an episode from the destination returns true on success, False on faiilure def pullEpisode(self, podcastSyncTaskEpisodePull): self.mobileClient.delete_songs([podcastSyncTaskEpisodePull.syncDestinationID]) # TODO: Error check here. return True
def main(): if len(sys.argv) != 2: print_help() sys.exit(1) else: username = sys.argv[1] password = getpass.getpass() mc = Mobileclient() mc.login(username, password, Mobileclient.FROM_MAC_ADDRESS) mm = Musicmanager() mm.perform_oauth() mm.login() uploaded_songs = mm.get_uploaded_songs() uploaded_ids = [track['id'] for track in uploaded_songs] for part in chunks(uploaded_ids, 100): complete = mc.delete_songs(part) if len(complete) != len(part): print("Something is wrong")
if track_txt_key not in uniq_tracks: uniq_tracks.append(track_txt_key) else: duplicatesTracks.append(track) return duplicatesTracks api = Mobileclient() logged_in = api.login('login', 'password') if logged_in: print "Successfully logged in. Beginning duplication removal process." all_tracks = api.get_all_songs() print "all tracks: ", len(all_tracks) duplicate_tracks = getDuplicatesSongInAlbums(all_tracks) print "duplication tracks count: %s" % len(duplicate_tracks) show_tracks = raw_input("Show list tracks to delete?:[y] Y/n ") if show_tracks.lower() == 'y' or show_tracks == '': for track in duplicate_tracks: name = u"%s - %s - %s" % (track['artist'], track['album'], track['title']) print name.encode('utf8') if len(duplicate_tracks) > 0: deleteConfirnation = raw_input("Are you sure you want to delete tracks (%d)? [y] Y/n" % len(duplicate_tracks)) if deleteConfirnation.lower() == 'y' or deleteConfirnation == '': duplicate_track_ids = get_track_ids(duplicate_tracks) deleted_track_ids = api.delete_songs(duplicate_track_ids) print "Successfully deleted " + str(len(deleted_track_ids)) + " of " + str(len(duplicate_track_ids)) + " queued songs for removal." else: print "I didn't find any duplicate tracks." print "Thank you!"
for song in all_songs: song_id = song.get('id') timestamp = song.get('recentTimestamp') key = "%s: %d-%02d %s" % ( song.get('album'), song.get('discNumber'), song.get('trackNumber'), song.get('title') ) if key in new_songs: if new_songs[key]['timestamp'] < timestamp: old_songs[key] = new_songs[key] new_songs[key] = { 'id': song_id, 'timestamp': timestamp } else: old_songs[key] = { 'id': song_id, 'timestamp': timestamp } new_songs[key] = { 'id': song_id, 'timestamp': timestamp } if len( old_songs ): print "Duplicate songs" old_song_ids = [] for key in sorted( old_songs.keys() ): old_song_ids.append( old_songs[key]['id'] ) print " " + key.encode('utf-8') if raw_input( "Delete duplicate songs? (y, n): ") is 'y': print "Deleting songs ..." client.delete_songs( old_song_ids ) else: print "No duplicate songs"
from pyItunes import * from gmusicapi import Mobileclient # iTunes songs l = Library('<full path to iTunes Music Library.xml>') songs = [(song.artist, song.name) for id,song in l.songs.items()] # Google Music songs api = Mobileclient() api.login('<account>', '<password>', Mobileclient.FROM_MAC_ADDRESS) library = api.get_all_songs() # Find songs to delete delete = filter(lambda song: (song['artist'], song['title']) not in songs, library) delete = [song['id'] for song in delete] if len(delete): # Display songs print "Duplicate songs" old_song_ids = [] for key in sorted(delete): key = '%s: %d-%02d %s' % (song.get('album'), song.get('discNumber'), song.get('trackNumber'), song.get('title')) print " " + key.encode('utf-8') # Delete songs if raw_input( "Delete duplicate songs? (y, n): ") is 'y': print "Deleting songs..." api.delete_songs(delete) else: print 'No deleted songs'
class GMusic(object): def __init__(self): self.mob_client = Mobileclient() self.web_client = Webclient() self.logfile = None self.logfile_open = False # logged_in is True if login was successful logged_in = self.mob_client.login(MY_GMAIL, MY_PASSWORD, Mobileclient.FROM_MAC_ADDRESS) if logged_in: print("GoogleMusic MobileClient Logged In") else: raise Exception("Couldn't log in, exiting...") logged_in = self.web_client.login(MY_GMAIL, MY_PASSWORD) if logged_in: print("GoogleMusic WebClient Logged In") else: raise Exception("Couldn't log in, exiting...") def build_play_list_dummy(self): library = self.mob_client.get_all_songs() tracks = [track for track in library if track['artist'] == 'Adhesive Wombat' and "night shade" in track['title'].lower()] # for track in sweet_tracks: # print(track) playlist_id = self.mob_client.create_playlist('Test playlist') for track in tracks: self.mob_client.add_songs_to_playlist(playlist_id, track['id']) return playlist_id def _setlogfile(self, logfile): if self.logfile_open: self._print_and_log("logfile {} already opened! Not opening again!") else: self.logfile = logfile with open(self.logfile, "w") as logfile: logfile.write("LOGSTART: {}, script: {}\n".format(asctime(localtime()), __file__)) self.logfile_open = True def _print_and_log(self, msg): if self.logfile: with open(self.logfile, "a") as logfile: logfile.write(msg+"\n") print msg def find_duplicate_songs(self, outfile=None): duplicates = [] if outfile: if path.exists(path.dirname(outfile)): self._setlogfile(outfile) else: raise IOError("Output filename given: {} is in an none-existing dir".format(outfile)) library = self.mob_client.get_all_songs() tracks = [track for track in library] while tracks: track = tracks[0] dup_list = [] dup_idx_list = [] for idx, track_i in enumerate(tracks): if track['artist'].lower() == track_i['artist'].lower() and\ track['album'].lower() == track_i['album'].lower() and\ track['discNumber'] == track_i['discNumber'] and\ track['trackNumber'] == track_i['trackNumber'] and\ track['title'].lower() == track_i['title'].lower(): dup_idx_list.append(idx) dup_list.append(track_i) # Remove them: for index in sorted(dup_idx_list, reverse=True): del tracks[index] if len(dup_list) > 1: duplicates.append(dup_list) for idx, dup_list in enumerate(duplicates): self._print_and_log("{}: '{}' was found {} times!".format(idx+1, dup_list[0]['title'].encode("utf-8"), len(dup_list))) self._print_and_log("Found a total of {} duplicate songs!".format(len(duplicates))) # Display important stuff for idx, track_list in enumerate(duplicates): self._print_and_log("{}: BAND: {}, NAME: '{}'".format(idx+1, track_list[0]['artist'], track_list[0]['title'].encode("utf-8"))) for el in track_list[0]: for track in track_list: if el not in track: track[el] = "NO VALUE" if track[el] != track_list[0][el] and el not in ['id', 'url', 'recentTimestamp', 'storeId', 'nid', 'clientId']: # unicode? try: r_val = track_list[0][el].encode("utf-8") except: r_val = track_list[0][el] # unicode? try: l_val = track[el].encode("utf-8") except: l_val = track[el] self._print_and_log("track_id {}: {}='{}'".format(track_list[0]['id'], el, r_val)) self._print_and_log("track_id {}: {}='{}'".format(track['id'], el, l_val)) # raw_input("Press any key to continue...") return duplicates def delete_duplicates(self, duplicates): self._print_and_log("Cleaning duplicates [removing oldest of each duplicant]:") old_song_ids = [] for idx, dup_list in enumerate(duplicates): self._print_and_log("{}: BAND: {}, NAME: '{}'".format(idx+1, dup_list[0]['artist'], dup_list[0]['title'].encode("utf-8"))) track_timstamp = None oldest_id = None for el in dup_list: if track_timstamp is None and oldest_id is None: track_timstamp = el["timestamp"] oldest_id = el["id"] elif el["timestamp"] < track_timstamp: track_timstamp = el["timestamp"] oldest_id = el["id"] # finished with dup_list - log oldest id: self._print_and_log("Will delete {}, track_id: {}".format(el["title"], el["id"])) old_song_ids.append(oldest_id) self.mob_client.delete_songs(old_song_ids) self._print_and_log("track_ids deleted:\n{}".format(old_song_ids))
def _delete_duplicates(self, api: Mobileclient) -> None: """Delete duplicates.""" self._combine_play_counts(api) discard_ids = [song["id"] for song in self.get_discard_songs()] api.delete_songs(discard_ids)
# wanted_keys = ['artist', 'title', 'id', 'rating'] for song in songs_list: if song['rating'] == RATING_THUMBS_DOWN: songs_to_delete.append(song) # songs_to_delete.append({k: song[k] for k in wanted_keys}) if not songs_to_delete: print("No songs to delete so far") exit(0) # songs_to_delete = songs_to_delete[-2] ids_to_delete = [song['id'] for song in songs_to_delete] titles_to_delete = [song['title'] for song in songs_to_delete] deleted = api.delete_songs(ids_to_delete) # print(titles_to_delete) feedbackText = "" if (len(deleted) != len(songs_to_delete)): feedbackText = "Algo no ha anat bé" else: feedbackText = "<h1>Deleted all %d songs:<h1><br>" % len(deleted) feedbackText += '<br>'.join(titles_to_delete) print(feedbackText) destEmail = config['Genericos']['DestinationEmail'] titleEmail = 'GMusicAPI batch feedback'
i['artist'] = 'ARTIST-' + str(n) if i['title'] == '': i['title'] = 'TITLE-' + str(n) try: # try to add song to existing album all_albums[i['album']].add(i['artist'], i['genre'], i['title'], i['id']) # print('>>> added', i['title'], 'to album', i['album']) except: # no album exists -> create it, then add song all_albums[i['album']] = Album(i['album']) all_albums[i['album']].add(i['artist'], i['genre'], i['title'], i['id']) # print('>>> created', i['album'], '(' + i['artist'] + ')') n += 1 else: print('login for', USER, 'failed') print('read', n, 'songs in', len(all_albums), 'albums') for a in sorted(all_albums): # across all albums i = all_albums[a] print(i.artist[0], ':', i.album, '(' + i.genre[0] + ')') # Artist : Album (Genre) for t in i.title: # list all titles of the album print('\t', t) rm = input("Delete album? (y/n)? ") if rm == 'y': ret = api.delete_songs(i.ID) # delete all songs of the album print(ret, 'deleted') print()
i['album'] = 'ALBUM-' + str(n) if i['artist'] == '': i['artist'] = 'ARTIST-' + str(n) if i['title'] == '': i['title'] = 'TITLE-' + str(n) try: # try to add song to existing album all_albums[i['album']].add(i['artist'], i['genre'], i['title'], i['id']) # print('>>> added', i['title'], 'to album', i['album']) except: # no album exists -> create it, then add song all_albums[i['album']] = Album(i['album']) all_albums[i['album']].add(i['artist'], i['genre'], i['title'], i['id']) # print('>>> created', i['album'], '(' + i['artist'] + ')') n += 1 else: print('login for', USER, 'failed') print('read', n, 'songs in', len(all_albums), 'albums') for a in sorted(all_albums): # across all albums i = all_albums[a] print(i.artist[0], ':', i.album, '('+i.genre[0]+')') # Artist : Album (Genre) for t in i.title: # list all titles of the album print('\t', t) rm = input("Delete album? (y/n)? ") if rm == 'y': ret = api.delete_songs(i.ID) # delete all songs of the album print(ret, 'deleted') print()
class MusicSync(object): def __init__(self, email=None, password=None): self.mm = Musicmanager() self.wc = Webclient() self.mc = Mobileclient() if not email: email = raw_input("Email: ") if not password: password = getpass() self.email = email self.password = password self.logged_in = self.auth() print "Fetching playlists from Google..." self.playlists = self.mc.get_all_user_playlist_contents() #self.playlists = self.mc.get_all_playlists() #self.playlists = self.wc.get_all_playlist_ids(auto=False) self.all_songs = self.mc.get_all_songs() #print "Got %d playlists." % len(self.playlists['user']) print "Got %d playlists containing %d songs." % (len( self.playlists), len(self.all_songs)) print "" def auth(self): self.logged_in = self.mc.login(self.email, self.password) #self.logged_in = self.wc.login(self.email, self.password) if not self.logged_in: print "Login failed..." exit() print "" print "Logged in as %s" % self.email print "" if not os.path.isfile(OAUTH_FILEPATH): print "First time login. Please follow the instructions below:" self.mm.perform_oauth() self.logged_in = self.mm.login() if not self.logged_in: print "OAuth failed... try deleting your %s file and trying again." % OAUTH_FILEPATH exit() print "Authenticated" print "" def sync_playlist(self, filename, remove_missing): #def sync_playlist(self, filename, remove_missing=False): filename = self.get_platform_path(filename) os.chdir(os.path.dirname(filename)) title = os.path.splitext(os.path.basename(filename))[0] print "Syncing playlist: %s" % filename #if title not in self.playlists['user']: #print " didn't exist... creating..." #self.playlists['user'][title] = [self.wc.create_playlist(title)] print "" plid = "" for pl in self.playlists: if pl['name'] == title: plid = pl['id'] goog_songs = pl['tracks'] if plid == "": print " didn't exist... creating..." plid = self.mc.create_playlist(self, title) #plid = self.playlists['user'][title][0] #goog_songs = self.wc.get_playlist_songs(plid) print "%d songs already in Google Music playlist" % len(goog_songs) pc_songs = self.get_files_from_playlist(filename) print "%d songs in local playlist" % len(pc_songs) print "" # Sanity check max 1000 songs per playlist if len(pc_songs) > MAX_SONGS_IN_PLAYLIST: print " Google music doesn't allow more than %d songs in a playlist..." % MAX_SONGS_IN_PLAYLIST print " Will only attempt to sync the first %d songs." % MAX_SONGS_IN_PLAYLIST del pc_songs[MAX_SONGS_IN_PLAYLIST:] existing_files = 0 added_files = 0 failed_files = 0 removed_files = 0 fatal_count = 0 for fn in pc_songs: if self.file_already_in_list(fn, goog_songs, self.all_songs): existing_files += 1 continue print "" print "Adding: %s" % os.path.basename(fn).encode('cp1252') #print "Adding: %s" % os.path.basename(fn) #online = False online = self.find_song(fn, goog_songs, self.all_songs) #online = self.find_song(fn) song_id = None if online: song_id = online['id'] print " already uploaded [%s]" % song_id else: attempts = 0 result = [] while not result and attempts < MAX_UPLOAD_ATTEMPTS_PER_FILE: print " uploading... (may take a while)" attempts += 1 try: result = self.mm.upload(fn) except (BadStatusLine, CannotSendRequest): # Bail out if we're getting too many disconnects if fatal_count >= MAX_CONNECTION_ERRORS_BEFORE_QUIT: print "" print "Too many disconnections - quitting. Please try running the script again." print "" exit() print "Connection Error -- Reattempting login" fatal_count += 1 self.wc.logout() self.mc.logout() self.mm.logout() result = [] time.sleep(STANDARD_SLEEP) except: result = [] time.sleep(STANDARD_SLEEP) try: if result[0]: song_id = result[0].itervalues().next() else: song_id = result[1].itervalues().next() print " upload complete [%s]" % song_id except: print " upload failed - skipping" tag = self.get_id3_tag(fn) print " failed song:\t%s\t%s\t%s" % ( tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252')) if not song_id: failed_files += 1 continue added = self.mc.add_songs_to_playlist(plid, song_id) time.sleep(.3) # Don't spam the server too fast... print " done adding to playlist" added_files += 1 if remove_missing: for g in goog_songs: for s in self.all_songs: if g['trackId'] == s['id']: print "" print "Removing: %s" % s['title'].encode('cp1252') self.mc.remove_entries_from_playlist(g['id']) #self.wc.remove_songs_from_playlist(plid, s.id) time.sleep(.3) # Don't spam the server too fast... removed_files += 1 print "" print "---" print "%d songs unmodified" % existing_files print "%d songs added" % added_files print "%d songs failed" % failed_files print "%d songs removed" % removed_files def get_files_from_playlist(self, filename): files = [] f = codecs.open(filename, encoding='cp1252') #f = codecs.open(filename, encoding='utf-8') for line in f: line = line.rstrip().replace(u'\ufeff', u'') if line == "" or line[0] == "#": continue path = os.path.abspath(self.get_platform_path(line)) if not os.path.exists(path): print "File not found: %s" % line continue files.append(path) f.close() return files def file_already_in_list(self, filename, goog_songs, all_songs): tag = self.get_id3_tag(filename) print "Searching for\t%s\t%s\t%s" % (tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252')) i = 0 while i < len(goog_songs): for s in all_songs: if goog_songs[i]['trackId'] == s['id']: if self.tag_compare(s, tag): print "Found match\t%s\t%s\t%s" % ( s['title'].encode('cp1252'), s['artist'].encode('cp1252'), s['album'].encode('cp1252')) goog_songs.pop(i) return True i += 1 return False def get_id3_tag(self, filename): data = mutagen.File(filename, easy=True) r = {} if 'title' not in data: title = os.path.splitext(os.path.basename(filename))[0] print 'Found song with no ID3 title, setting using filename:' print ' %s' % title print ' (please note - the id3 format used (v2.4) is invisible to windows)' data['title'] = [title] data.save() r['title'] = data['title'][0] r['track'] = int(data['tracknumber'][0].split('/') [0]) if 'tracknumber' in data else 0 # If there is no track, try and get a track number off the front of the file... since thats # what google seems to do... # Not sure how google expects it to be formatted, for now this is a best guess if r['track'] == 0: m = re.match("(\d+) ", os.path.basename(filename)) if m: r['track'] = int(m.group(0)) r['artist'] = data['artist'][0] if 'artist' in data else '' r['album'] = data['album'][0] if 'album' in data else '' return r def find_song(self, filename, goog_songs, all_songs): tag = self.get_id3_tag(filename) print "Searching for\t%s\t%s\t%s" % (tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252')) #results = self.wc.search(tag['title']) # NOTE - diagnostic print here to check results if you're creating duplicates #print results['song_hits'] #for r in goog_songs: #for r in results['song_hits']: for s in all_songs: #if r['trackId'] == s['id']: if self.tag_compare(s, tag): # TODO: add rough time check to make sure its "close" print "Found match\t%s\t%s\t%s" % ( s['title'].encode('cp1252'), s['artist'].encode('cp1252'), s['album'].encode('cp1252')) return s return None def tag_compare(self, g_song, tag): # If a google result has no track, google doesn't return a field for it if 'title' not in g_song: g_song['title'] = "" if 'artist' not in g_song: g_song['artist'] = "" if 'album' not in g_song: g_song['album'] = "" if 'track' not in g_song: g_song['track'] = 0 if (g_song['title'].lower() == tag['title'].lower() and g_song['artist'].lower() == tag['artist'].lower()) or\ (g_song['album'].lower() == tag['album'].lower() and g_song['title'].lower() == tag['title'].lower()) or\ (g_song['artist'].lower() == tag['artist'].lower() and g_song['album'].lower() == tag['album'].lower() and g_song['track'] == tag['track']): print "Partial match\t%s\t%s\t%s" % ( g_song['title'].encode('cp1252'), g_song['artist'].encode('cp1252'), g_song['album'].encode('cp1252')) return g_song['title'].lower() == tag['title'].lower() and\ g_song['artist'].lower() == tag['artist'].lower() and\ g_song['album'].lower() == tag['album'].lower() #and\ #g_song['track'] == tag['track'] def delete_song(self, sid): self.mc.delete_songs(sid) print "Deleted song by id [%s]" % sid def get_platform_path(self, full_path): # Try to avoid messing with the path if possible if os.sep == '/' and '\\' not in full_path: return full_path if os.sep == '\\' and '\\' in full_path: return full_path if '\\' not in full_path: return full_path return os.path.normpath(full_path.replace('\\', '/'))
print(len(album_to_ids), "albums in library") # Find songs with duplicate (title, trackNumber) in same album total_dups, albums_with_dups = 0, 0 for album, ids in album_to_ids.items(): #if "HSBC" not in album: # exclude certain albums # continue songs = set() album_dups = 0 for id in ids: all_song_info = track_ids[id] song_tuple = (all_song_info["title"], all_song_info["trackNumber"]) if song_tuple in songs: #print("Deleting duplicate", song_tuple, album) api.delete_songs(id) # delete dupliacted album_dups +=1 songs.add(song_tuple) if album_dups > 0: total_dups += album_dups albums_with_dups += 1 print("Deleted", album_dups, "duplicates in", album, ", cumulative:", total_dups) print("Total duplicates deleted:", total_dups) print("Albums with duplicates:", albums_with_dups)
import io import os api = Mobileclient() logged_in = api.login('*****@*****.**', os.environ['PASSWORD'], Mobileclient.FROM_MAC_ADDRESS) if logged_in: print "Logged in successfully" else: print "Error logging in" sys.exit(1) delete = os.getenv('DELETE_BAD_SONGS', False) bad_ids = [] with io.open('/tmp/zero_length.csv', 'w') as csv: csv.write(u"Artist,Album,Title\n") library = api.get_all_songs() for track in library: millis = int(track['durationMillis']) if millis < 1000: line = u"{albumArtist:s},{album:s},{title:s}\n".format(**track) csv.write(line) bad_ids.append(track['id']) print "Found %d bad songs" % len(bad_ids) if delete: print "Deleting bad songs" api.delete_songs(bad_ids) api.logout()
class MusicSync(object): def __init__(self, email=None, password=None): self.mm = Musicmanager() self.wc = Webclient() self.mc = Mobileclient() if not email: email = raw_input("Email: ") if not password: password = getpass() self.email = email self.password = password self.logged_in = self.auth() print "Fetching playlists from Google..." self.playlists = self.mc.get_all_user_playlist_contents() #self.playlists = self.mc.get_all_playlists() #self.playlists = self.wc.get_all_playlist_ids(auto=False) self.all_songs = self.mc.get_all_songs() #print "Got %d playlists." % len(self.playlists['user']) print "Got %d playlists containing %d songs." % (len(self.playlists), len(self.all_songs)) print "" def auth(self): self.logged_in = self.mc.login(self.email, self.password) #self.logged_in = self.wc.login(self.email, self.password) if not self.logged_in: print "Login failed..." exit() print "" print "Logged in as %s" % self.email print "" if not os.path.isfile(OAUTH_FILEPATH): print "First time login. Please follow the instructions below:" self.mm.perform_oauth() self.logged_in = self.mm.login() if not self.logged_in: print "OAuth failed... try deleting your %s file and trying again." % OAUTH_FILEPATH exit() print "Authenticated" print "" def sync_playlist(self, filename, remove_missing): #def sync_playlist(self, filename, remove_missing=False): filename = self.get_platform_path(filename) os.chdir(os.path.dirname(filename)) title = os.path.splitext(os.path.basename(filename))[0] print "Syncing playlist: %s" % filename #if title not in self.playlists['user']: #print " didn't exist... creating..." #self.playlists['user'][title] = [self.wc.create_playlist(title)] print "" plid = "" for pl in self.playlists: if pl['name'] == title: plid = pl['id'] goog_songs = pl['tracks'] if plid == "": print " didn't exist... creating..." plid = self.mc.create_playlist(self, title) #plid = self.playlists['user'][title][0] #goog_songs = self.wc.get_playlist_songs(plid) print "%d songs already in Google Music playlist" % len(goog_songs) pc_songs = self.get_files_from_playlist(filename) print "%d songs in local playlist" % len(pc_songs) print "" # Sanity check max 1000 songs per playlist if len(pc_songs) > MAX_SONGS_IN_PLAYLIST: print " Google music doesn't allow more than %d songs in a playlist..." % MAX_SONGS_IN_PLAYLIST print " Will only attempt to sync the first %d songs." % MAX_SONGS_IN_PLAYLIST del pc_songs[MAX_SONGS_IN_PLAYLIST:] existing_files = 0 added_files = 0 failed_files = 0 removed_files = 0 fatal_count = 0 for fn in pc_songs: if self.file_already_in_list(fn, goog_songs, self.all_songs): existing_files += 1 continue print "" print "Adding: %s" % os.path.basename(fn).encode('cp1252') #print "Adding: %s" % os.path.basename(fn) #online = False online = self.find_song(fn, goog_songs, self.all_songs) #online = self.find_song(fn) song_id = None if online: song_id = online['id'] print " already uploaded [%s]" % song_id else: attempts = 0 result = [] while not result and attempts < MAX_UPLOAD_ATTEMPTS_PER_FILE: print " uploading... (may take a while)" attempts += 1 try: result = self.mm.upload(fn) except (BadStatusLine, CannotSendRequest): # Bail out if we're getting too many disconnects if fatal_count >= MAX_CONNECTION_ERRORS_BEFORE_QUIT: print "" print "Too many disconnections - quitting. Please try running the script again." print "" exit() print "Connection Error -- Reattempting login" fatal_count += 1 self.wc.logout() self.mc.logout() self.mm.logout() result = [] time.sleep(STANDARD_SLEEP) except: result = [] time.sleep(STANDARD_SLEEP) try: if result[0]: song_id = result[0].itervalues().next() else: song_id = result[1].itervalues().next() print " upload complete [%s]" % song_id except: print " upload failed - skipping" tag = self.get_id3_tag(fn) print " failed song:\t%s\t%s\t%s" % (tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252')) if not song_id: failed_files += 1 continue added = self.mc.add_songs_to_playlist(plid, song_id) time.sleep(.3) # Don't spam the server too fast... print " done adding to playlist" added_files += 1 if remove_missing: for g in goog_songs: for s in self.all_songs: if g['trackId'] == s['id']: print "" print "Removing: %s" % s['title'].encode('cp1252') self.mc.remove_entries_from_playlist(g['id']) #self.wc.remove_songs_from_playlist(plid, s.id) time.sleep(.3) # Don't spam the server too fast... removed_files += 1 print "" print "---" print "%d songs unmodified" % existing_files print "%d songs added" % added_files print "%d songs failed" % failed_files print "%d songs removed" % removed_files def get_files_from_playlist(self, filename): files = [] f = codecs.open(filename, encoding='cp1252') #f = codecs.open(filename, encoding='utf-8') for line in f: line = line.rstrip().replace(u'\ufeff',u'') if line == "" or line[0] == "#": continue path = os.path.abspath(self.get_platform_path(line)) if not os.path.exists(path): print "File not found: %s" % line continue files.append(path) f.close() return files def file_already_in_list(self, filename, goog_songs, all_songs): tag = self.get_id3_tag(filename) print "Searching for\t%s\t%s\t%s" % (tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252')) i = 0 while i < len(goog_songs): for s in all_songs: if goog_songs[i]['trackId'] == s['id']: if self.tag_compare(s, tag): print "Found match\t%s\t%s\t%s" % (s['title'].encode('cp1252'), s['artist'].encode('cp1252'), s['album'].encode('cp1252')) goog_songs.pop(i) return True i += 1 return False def get_id3_tag(self, filename): data = mutagen.File(filename, easy=True) r = {} if 'title' not in data: title = os.path.splitext(os.path.basename(filename))[0] print 'Found song with no ID3 title, setting using filename:' print ' %s' % title print ' (please note - the id3 format used (v2.4) is invisible to windows)' data['title'] = [title] data.save() r['title'] = data['title'][0] r['track'] = int(data['tracknumber'][0].split('/')[0]) if 'tracknumber' in data else 0 # If there is no track, try and get a track number off the front of the file... since thats # what google seems to do... # Not sure how google expects it to be formatted, for now this is a best guess if r['track'] == 0: m = re.match("(\d+) ", os.path.basename(filename)) if m: r['track'] = int(m.group(0)) r['artist'] = data['artist'][0] if 'artist' in data else '' r['album'] = data['album'][0] if 'album' in data else '' return r def find_song(self, filename, goog_songs, all_songs): tag = self.get_id3_tag(filename) print "Searching for\t%s\t%s\t%s" % (tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252')) #results = self.wc.search(tag['title']) # NOTE - diagnostic print here to check results if you're creating duplicates #print results['song_hits'] #for r in goog_songs: #for r in results['song_hits']: for s in all_songs: #if r['trackId'] == s['id']: if self.tag_compare(s, tag): # TODO: add rough time check to make sure its "close" print "Found match\t%s\t%s\t%s" % (s['title'].encode('cp1252'), s['artist'].encode('cp1252'), s['album'].encode('cp1252')) return s return None def tag_compare(self, g_song, tag): # If a google result has no track, google doesn't return a field for it if 'title' not in g_song: g_song['title'] = "" if 'artist' not in g_song: g_song['artist'] = "" if 'album' not in g_song: g_song['album'] = "" if 'track' not in g_song: g_song['track'] = 0 if (g_song['title'].lower() == tag['title'].lower() and g_song['artist'].lower() == tag['artist'].lower()) or\ (g_song['album'].lower() == tag['album'].lower() and g_song['title'].lower() == tag['title'].lower()) or\ (g_song['artist'].lower() == tag['artist'].lower() and g_song['album'].lower() == tag['album'].lower() and g_song['track'] == tag['track']): print "Partial match\t%s\t%s\t%s" % (g_song['title'].encode('cp1252'), g_song['artist'].encode('cp1252'), g_song['album'].encode('cp1252')) return g_song['title'].lower() == tag['title'].lower() and\ g_song['artist'].lower() == tag['artist'].lower() and\ g_song['album'].lower() == tag['album'].lower() #and\ #g_song['track'] == tag['track'] def delete_song(self, sid): self.mc.delete_songs(sid) print "Deleted song by id [%s]" % sid def get_platform_path(self, full_path): # Try to avoid messing with the path if possible if os.sep == '/' and '\\' not in full_path: return full_path if os.sep == '\\' and '\\' in full_path: return full_path if '\\' not in full_path: return full_path return os.path.normpath(full_path.replace('\\', '/'))
def main(): #### Requests user specifies update library and/or playlists if len(sys.argv) != 3: print('Specify T/F arguments for uploading and updating playlists') print('e.g. python ' + sys.argv[0] + ' 1 0') print('which would:\n--> upload new songs\n--> NOT update playlists') sys.exit(0) #### Parameters music_dir = '/home/conor/Music/' print('Local music directory set to:', music_dir) accepted = input('Type "y" if this is correct directory: ') if accepted.lower() != 'y': print('Edit music_dir variable in source to run script...') print('Ending music management.') sys.exit(0) #### Some general information needed for both tasks is collected here # Get mp3 file names from music folder local_song_paths = glob.glob(music_dir + '*.mp3') # Get individual song names local_song_names = set() for p in local_song_paths: _, song_name = os.path.split(p) local_song_names.add(song_name) # Authenticate mc = Mobileclient() mc.oauth_login('38e42c4b00ca0a10') # Authenticates using on-disk token print('Mobile client authentication complete...') # Create dict of gpm 'song'': 'id' pairs song_ids = {} gpm_songs = mc.get_all_songs() for song in gpm_songs: song_ids[song['title']] = song['id'] #### Manage upload/deletion of songs uploading = sys.argv[1] if uploading == '1': mm = Musicmanager() mm.login(uploader_id='EE:20:80:B4:17:A9' ) # Authenticates using on-disk token print('Music manager authentication complete...') # Delete songs that no longer exist locally to_delete = set() for song in song_ids: if song not in local_song_names: to_delete.add(song) if len(to_delete) == 0: print('No songs to delete.') else: print('{} songs to delete:'.format(len(to_delete))) print([s for s in to_delete]) # delete_songs() method requires a list as input to_delete_ids = [] for s in to_delete: song_id = song_ids[s] to_delete_ids.append(song_id) mc.delete_songs(to_delete_ids) print('Deleted songs.') #### Uploading to_upload = [] for s in local_song_names: if s not in song_ids: to_upload.append(music_dir + s) print('{} songs to upload.'.format(len(to_upload))) if len(to_upload) != 0: accepted = input('Type "y" to commence upload now: ') if accepted.lower() != 'y': print('Ending music management.') sys.exit(0) mm.upload(to_upload) #### Create and edit playlists as required # Works by deleting all playlists and then re-creating from scratch playlisting = sys.argv[2] if playlisting == '1': # Refresh song list # (since we have uploaded new songs since original list generated) song_ids = {} gpm_songs = mc.get_all_songs() for song in gpm_songs: song_ids[song['title']] = song['id'] # Flush old playlists print('Deleting old playlists...') gpm_playlists = mc.get_all_playlists() for pl in gpm_playlists: mc.delete_playlist(pl['id']) print('Playlists deleted.') # Keep a dictionary of created playlists to prevent duplication playlist_ids = {} total = len(song_ids) completed = 0 # Create and update playlists print('Organising songs:') for s in song_ids: sid = song_ids[s] req_pls = required_playlists(s) for pl in req_pls: if pl in playlist_ids: pid = playlist_ids[pl] else: pid = mc.create_playlist(name=pl) playlist_ids[pl] = pid mc.add_songs_to_playlist(pid, sid) completed += 1 # Console output for number of songs sorted sys.stdout.write("\r{}/{} processed".format(completed, total)) sys.stdout.flush() print()
if song.get('trackNumber') is None: tracknum = 0 else: tracknum = song.get('trackNumber') #key = "%s: %d-%02d %s" % ( song.get('album'), discnum, tracknum, song.get('title') ) key = "%s - %s" % ( song.get('title'), song.get('artist') ) if key in new_songs: if new_songs[key]['timestamp'] < timestamp: old_songs[key] = new_songs[key] new_songs[key] = { 'id': song_id, 'timestamp': timestamp } else: old_songs[key] = { 'id': song_id, 'timestamp': timestamp } new_songs[key] = { 'id': song_id, 'timestamp': timestamp } if len( old_songs ): print('Duplicate songs') old_song_ids = [] for key in sorted( old_songs.keys() ): old_song_ids.append( old_songs[key]['id'] ) print(' ' + str(key)) if input('Delete duplicate songs? (y, n): ') is 'y': print('Deleting songs ...') client.delete_songs( old_song_ids ) else: print('Finally. No duplicate songs.')