def download_by_id(self, key, upload=False): if self.logstack.has_key(key) and self.logstack[key]["state"] != "fail": return url = "http://www.youtube.com/watch?v=%s" % (key) if upload: print "upload google music!!!" self.current_key = key self.logstack[key] = {"path": "", "process": "", "state": "downloading"} result = None try: self.download([url]) except: self.logstack[key]["state"] = "fail" return if upload: self.logstack[key]["state"] = "uploading" print self.logstack[key]["path"] path = self.logstack[key]["path"].encode("utf-8") api = Api() api.login(gpass.id, gpass.passwd) ids = api.get_all_playlist_ids(auto=False, instant=False) playlist_id = ids["user"]["youtube"] try: result = api.upload([path]) except: print "upload error" self.logstack[key]["state"] = "fail" return if len(result): api.add_songs_to_playlist(playlist_id, result.values()[0]) self.logstack[key]["state"] = "complete"
def apiinit(email,password): api = Api() logged_in = False attempts = 0 api.login(email, password) return api
def get_google_music_api(user_id): api = Api() try: result = api.login(self.email, self.profile.google_music_password) return api, result except: user = User.objects.get(pk=user_id) result = api.login(user.email, user.profile.google_music_password) return api, result
def init(): api = Api() logged_in = False attempts = 0 while not logged_in and attempts < 3: email = raw_input("Google username or email: ") # Try to read the password from a file # If file doesn't exist, ask for password # This is useful for 2-step authentication only # Don't store your regular password in plain text try: pw_file = open("pass.txt") password = pw_file.readline() print "Reading password from pass.txt." except IOError: password = getpass() print "\nLogging in..." logged_in = api.login(email, password) if not logged_in: print "Log in failed." attempts += 1 return api
def importGmusicApi(email, password, gmusicCollection, keysCollection): api = Api() api.login(email, password) allSongs = api.get_all_songs() api.logout() gmusicCollection.remove() keys = set() for song in allSongs: gmusicCollection.insert(song) for key in song.iterkeys(): keys.add(key) doc = { '_id' : 'gmusic', 'keys' : list(keys) } keysCollection.save(doc) print 'done importing', len(allSongs), 'gmusic songs into db'
def init(): api = Api() (email, account, password) = netrc.netrc().hosts['google.com'] logged_in = api.login(email, password) if not logged_in: api = None return api
def init(args): api = Api() if args.user_name == None: email = raw_input("Email: ") else : email = args.user_name if args.password == None: password = getpass() else : password = args.password print "Signing in : " + email logged_in = api.login(email, password) return api
class AlbumArtist(object): def __init__(self, email=None, password=None): self.api = Api() if not email: email = raw_input("Email: ") if not password: password = getpass() self.email = email self.password = password self.logged_in = self.auth() def auth(self): self.logged_in = self.api.login(self.email, self.password) if not self.logged_in: print "Login failed..." exit() print "" print "Logged in as %s" % self.email print "" def fix_album_artist(self): def noAlbumArtist(x): return len(x['albumArtist']) == 0 def fixSong(song): song['albumArtist'] = song['artist'] return song def chunks(l, n): """ Yield successive n-sized chunks from l. """ for i in xrange(0, len(l), n): yield l[i:i+n] songs = filter(noAlbumArtist, self.api.get_all_songs()) print "Found %d songs that have no albumArtist" % len(songs) fixedSongs = map(fixSong, songs) fixedSongChunks = chunks(fixedSongs, 100) map(self.api.change_song_metadata, fixedSongChunks) print "Fixed %d songs" % len(fixedSongs)
def download_by_id(self, key, upload=False): if self.logstack.has_key( key) and self.logstack[key]['state'] != 'fail': return url = 'http://www.youtube.com/watch?v=%s' % (key) if upload: print 'upload google music!!!' self.current_key = key self.logstack[key] = { 'path': '', 'process': '', 'state': 'downloading' } result = None try: self.download([url]) except: self.logstack[key]['state'] = 'fail' return if upload: self.logstack[key]['state'] = 'uploading' print self.logstack[key]['path'] path = self.logstack[key]['path'].encode('utf-8') api = Api() api.login(gpass.id, gpass.passwd) ids = api.get_all_playlist_ids(auto=False, instant=False) playlist_id = ids['user']['youtube'] try: result = api.upload([path]) except: print 'upload error' self.logstack[key]['state'] = 'fail' return if len(result): api.add_songs_to_playlist(playlist_id, result.values()[0]) self.logstack[key]['state'] = 'complete'
def init(): """Makes an instance of the api and attempts to login with it. Returns the authenticated api. """ api = Api() logged_in = False attempts = 0 while not logged_in and attempts < 3: logged_in = api.login(args.email, args.password) attempts += 1 return api
def init(email=None, password=None): api = Api() logged_in = False attempts = 0 while not logged_in and attempts < 3: if email is None: email = raw_input("Email: ") if password is None or attempts > 0: password = getpass() logged_in = api.login(email, password) attempts += 1 return api, logged_in
def init(): """Makes an instance of the api and attempts to login with it. Returns the authenticated api. """ api = Api() logged_in = False attempts = 0 while not logged_in and attempts < 3: email = raw_input("Email: ") password = getpass() logged_in = api.login(email, password) attempts += 1 return api
def init(): """Makes an instance of the api and attempts to login with it. Returns the authenticated api. """ api = Api() logged_in = False attempts = 0 while not logged_in and attempts < 3: email = "*****@*****.**" password = "******" logged_in = api.login(email, password) attempts += 1 return api
def run(self): api = Api() if len(self.username) == 0 or len(self.password) == 0: print "Credentials not supplied, cannot get information from Google Music" else: logged_in = False attempts = 0 while not logged_in and attempts < 3: logged_in = api.login(self.username, self.password) attempts += 1 if not api.is_authenticated(): print "Could not log in to Google Music with the supplied credentials." else: print "Logged in to Google Music" self.callback(api)
class Login: def __init__(self): loginGuiPath = "guis/logingui.glade" self.builder = gtk.Builder() self.builder.add_from_file(loginGuiPath) self.builder.connect_signals(self) self.builder.get_object("loginGUI").show_all() self.builder.get_object("loginGUI").connect("delete_event", self.delete_event) def delete_event(self, widget, event, data=None): gtk.main_quit() return False def on_loginButton_clicked(self, widget): self.api = Api() logged_in = False attempts = 0 email = self.builder.get_object("user").get_text() password = self.builder.get_object("password").get_text() logged_in = self.api.login(email, password) attempts += 1 if(logged_in): self.builder.get_object("loginGUI").destroy() player = Player(self.api) pass else: diag = gtk.Dialog(title="Login Fehler !", parent=self.builder.get_object("loginGUI"), flags= gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) label = gtk.Label("Login Fehler !") diag.vbox.pack_start(label) label.show() diag.run() diag.destroy()
class MusicLibrary(object): 'Read information about your Google Music library' def __init__(self, username=None, password=None, true_file_size=False): self.__artists = {} # 'artist name' -> {'album name' : Album(), ...} self.__albums = [] # [Album(), ...] self.__login(username, password) self.__aggregate_albums() self.true_file_size = true_file_size def __login(self, username=None, password=None): # If credentials are not specified, get them from $HOME/.gmusicfs if not username or not password: cred_path = os.path.join(os.path.expanduser('~'), '.gmusicfs') if not os.path.isfile(cred_path): raise NoCredentialException( 'No username/password was specified. No config file could ' 'be found either. Try creating %s and specifying your ' 'username/password there. Make sure to chmod 600.' % cred_path) if not oct(os.stat(cred_path)[os.path.stat.ST_MODE]).endswith('00'): raise NoCredentialException( 'Config file is not protected. Please run: ' 'chmod 600 %s' % cred_path) config = ConfigParser.ConfigParser() config.read(cred_path) username = config.get('credentials','username') password = config.get('credentials','password') if not username or not password: raise NoCredentialException( 'No username/password could be read from config file' ': %s' % cred_path) self.api = GoogleMusicAPI() log.info('Logging in...') self.api.login(username, password, perform_upload_auth=False) log.info('Login successful.') def __aggregate_albums(self): 'Get all the tracks in the library, parse into artist and album dicts' all_artist_albums = {} # 'Artist|||Album' -> Album() log.info('Gathering track information...') tracks = self.api.get_all_songs() for track in tracks: # Get the Album object if it already exists: key = '%s|||%s' % (formatNames(track['artistNorm']), formatNames(track['albumNorm'])) album = all_artist_albums.get(key, None) if not album: # New Album artist = formatNames(track['artistNorm']) if artist == '': artist = 'unknown' album = all_artist_albums[key] = Album( self, formatNames(track['albumNorm'])) self.__albums.append(album) artist_albums = self.__artists.get(artist, None) if artist_albums: artist_albums[formatNames(album.normtitle)] = album else: self.__artists[artist] = {album.normtitle: album} artist_albums = self.__artists[artist] album.add_track(track) log.debug('%d tracks loaded.' % len(tracks)) log.debug('%d artists loaded.' % len(self.__artists)) log.debug('%d albums loaded.' % len(self.__albums)) def get_artists(self): return self.__artists def get_albums(self): return self.__albums def get_artist_albums(self, artist): log.debug(artist) return self.__artists[artist]
def main(argv): '''Farm out work to task-based methods. :param argv: list of command line arguments ''' # process command line options usage = """%prog [OPTIONS]... [COMMAND] [ARGS]... %prog [OPTIONS]... diff %prog [OPTIONS]... sync %prog [OPTIONS]... fs (not working) %prog [OPTIONS]... track UPDATE_KEYS[...] %prog [OPTIONS]... playlist [PLAYLIST]... %prog [OPTIONS]... delete [PLAYLIST]... %prog [OPTIONS]... validate %prog [OPTIONS]... dump TRACK_KEY[...]""" version_str = "{0} {1}".format(pkg, __version__) parser = OptionParser(usage=usage, version=version_str) # default banshee database banshee_db_def = os.environ['HOME'] + '/.config/banshee-1/banshee.db' banshee_db_help = "use Banshee database BANSHEE_DB (default {0})".format(banshee_db_def) parser.add_option("-b", "--banshee-db", default=banshee_db_def, help=banshee_db_help) parser.add_option("-d", "--dry-run", action="store_true", default=False, help="perform no action, just report what would be done") parser.add_option("-q", "--quiet", action="store_true", help="do not print status messages") # default minimum rating rating_def = 3 rating_help = "only consider Banshee songs with rating >= RATING (default {0})".format(rating_def) parser.add_option("-r", "--rating", type="int", default=rating_def, help=rating_help) (options, args) = parser.parse_args() # set "globals" global dryrun dryrun = options.dry_run logmsg.quiet = options.quiet # open log file logmsg.log_f = codecs.open(pkg + '.log', mode='w', encoding='utf-8') # determine action command = 'diff' if len(args): command = args[0] # save the rest args = args[1:] # log in to Google Music (gm) api = Api() gm_tracks = {} # sync and fs do not need connection to gm if command != 'sync' and command != 'fs': logged_in = False attempts = 0 while not logged_in and attempts < 3: email = raw_input("Email: ") password = getpass() logged_in = api.login(email, password) attempts += 1 if not api.is_authenticated(): logmsg('google credentials were not accepted', True) return logmsg("successfully logged in to google") # get the google music library gm_tracks = get_gm_library(api) # connect to banshee database banshee_conn = sqlite3.connect(options.banshee_db) if not banshee_conn: logmsg('unable to connect to banshee: {0}'.format(options.banshee_db), True) return # get the banshee library b_tracks = get_b_library(banshee_conn, options.rating) # dispatch rv = 0 if command == 'diff': # create files not in google music rv = diff(gm_tracks, b_tracks) elif command == 'sync': # create all files with sufficient rating rv = sync(b_tracks) elif command == 'fs': # check banshee database and file system for consistency rv = fs(b_tracks) elif command == 'track': # update track metadata rv = track(api, gm_tracks, b_tracks, args) elif command == 'playlist': # get banshee playlists b_playlists = get_b_playlists(banshee_conn, args) # upload banshee playlists to google music rv = playlist(api, gm_tracks, b_playlists) elif command == 'validate': # make sure the gm track metadata does not have bad characters rv = validate(gm_tracks) elif command == 'delete': # get banshee playlists b_playlists = get_b_playlists(banshee_conn, args) # delete tracks on banshee playlists from google music rv = delete(api, gm_tracks, b_playlists) elif command == 'dump': rv = dump(gm_tracks, args) else: logmsg('unknown command: {0}'.format(command), True) return # logout of gm api.logout() # disconnect from banshee database banshee_conn.close() # close log file logmsg.log_f.close if rv: sys.exit(0) # else sys.exit(1) # just in case return
class GMusicRater: def __init__(self, files): self.__api = Api() self.__by_rating = {} self.__needs_rating_update = [] # fill list of songs lib = [] cachefile = "gmusic.pickle" if os.path.isfile(cachefile): print "Loading cached library..." infile = open(cachefile, "rb") lib = cPickle.load(infile) else: self.__log_in() print "Getting music..." lib = self.__api.get_all_songs() print "Writing..." outfile = open(cachefile, "wb+") cPickle.dump(lib, outfile) # order list of songs by rating notfound = [] total_found = 0 for s in lib: r = files.find_rating(s) # error finding file: if r < 0: notfound.append(s) continue # print "Got rating for %s: %s" % (s["title"], s["rating"]) # file rating is different from cloud rating: if not r == s["rating"]: s["rating"] = r self.__needs_rating_update.append(s) if not self.__by_rating.has_key(r): self.__by_rating[r] = [] self.__by_rating[r].append(s) total_found += 1 print "Found %d cloud songs, %d of which need rating updates." % (total_found, len(self.__needs_rating_update)) print "Not found on disk: %d" % len(notfound) for s in notfound: print " %s - %s" % (s["artist"], s["title"]) def __log_in(self): if self.__api.is_authenticated(): return print "Logging in..." email = raw_input("Email: ") password = getpass.getpass() if not self.__api.login(email, password): print "couldnt log in" sys.exit(1) def reset_playlists(self): self.__log_in() playlists = self.__api.get_all_playlist_ids(auto=False, user=True)["user"] print "Got %d playlists:" % len(playlists) for k, v in playlists.iteritems(): print " Deleting %s (%s)" % (k, v) for playlistid in v: self.__api.delete_playlist(playlistid) def get_ids(slist): ret = [] for s in slist: ret.append(s["id"]) return ret awesome_songids = get_ids(self.__by_rating.get(5, [])) good_songids = awesome_songids + get_ids(self.__by_rating.get(4, [])) unrated_songids = get_ids(self.__by_rating.get(0, [])) awesome_pid = self.__api.create_playlist("Awesome") print "Awesome %s -> %d songs" % (awesome_pid, len(awesome_songids)) self.__api.add_songs_to_playlist(awesome_pid, awesome_songids) good_pid = self.__api.create_playlist("Good") print "Good %s -> %d songs" % (good_pid, len(good_songids)) self.__api.add_songs_to_playlist(good_pid, good_songids) unrated_pid = self.__api.create_playlist("Unrated") print "Unrated %s -> %d songs" % (unrated_pid, len(unrated_songids)) self.__api.add_songs_to_playlist(unrated_pid, unrated_songids) def update_ratings(self): total = len(self.__needs_rating_update) if total == 0: return self.__log_in() print "Updating %d songs..." % total # divide updates into chunks. start = 0 chunksz = 100 # could probably be larger, wasn't tested for max possible while start < total: end = start + chunksz if end >= total: end = total print "%d - %d" % (start, end) self.__api.change_song_metadata(self.__needs_rating_update[start:end]) start = end def logout(self): if self.__api.is_authenticated(): print "Logging out..." self.__api.logout()
class Serenade(): def __init__(self): self.app = QtGui.QApplication(sys.argv) self.app.setApplicationName("Serenade") signal.signal(signal.SIGINT, signal.SIG_DFL) self.position = 0 self.player = MultimediaKit.QMediaPlayer(self.app) self.player.positionChanged.connect(self.positionChanged) self.signals = Signals() self.client = gconf.client_get_default() self.api = Api() self.artists = [] self.library = {} self.cacheDir = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.CacheLocation) if not os.path.exists(self.cacheDir): os.mkdir(self.cacheDir) self.artistModel = ArtistModel() self.songModel = SongModel() self.signals.onDoneWorking.connect(self.doneWorking) self.signals.onError.connect(self.error) self.view = QtDeclarative.QDeclarativeView() self.view.setSource("/opt/serenade/qml/Main.qml") self.rootObject = self.view.rootObject() self.context = self.view.rootContext() self.context.setContextProperty('songModel', self.songModel) self.context.setContextProperty('artistModel', self.artistModel) self.rootObject.openFile("MenuPage.qml") self.rootObject.refresh.connect(self.updateSongs) self.rootObject.newLogin.connect(self.newLogin) self.rootObject.play.connect(self.play) self.rootObject.togglePause.connect(self.togglePause) self.login() self.view.showFullScreen() sys.exit(self.app.exec_()) def login(self): email = self.client.get_string('/apps/serenade/email') passwd = self.client.get_string('/apps/serenade/passwd') if not email or not passwd: self.rootObject.openFile("LoginPage.qml") else: logged_in = self.api.login(email, passwd) if not logged_in: self.rootObject.openFile("LoginPage.qml") self.rootObject.showMessage("Login failed", "Please check your email and password and try again. If you're using two-factor authentication you may need to set an application specific password in your Google account.") else: self.rootObject.openFile("MenuPage.qml") self.updateSongs() def newLogin(self, email, passwd): self.client.set_string('/apps/serenade/email', email) self.client.set_string('/apps/serenade/passwd', passwd) self.login() def updateSongs(self): self.rootObject.startWorking() thread = threading.Thread(target=self._updateSongs) thread.start() def _updateSongs(self): self.library = sorted(self.api.get_all_songs(), key=lambda x: x['title']) self.library.reverse() for s in self.library: if "albumArtUrl" in s: art = self.getImage(s['albumArtUrl']) else: art = "/opt/serenade/no-art.png" if s['artist'] not in self.artists: artist = Artist(s['artist'], art) self.artists.append(s['artist']) self.artistModel.add(artist) song = Song(s['id'], s['title'], s['album'], s['artist'], False, art) self.songModel.add(song) self.artistModel.sort() self.signals.onDoneWorking.emit() def doneWorking(self): self.rootObject.stopWorking() def error(self, title, message): self.rootObject.showMessage(title, message) def getImage(self, url): filename = url.split("/")[-1] imagePath = os.path.join(self.cacheDir, filename) imagePath = imagePath.replace("?", "") if not os.path.exists(imagePath): try: out = open(imagePath, 'wb') out.write(urllib2.urlopen("http:" + url).read()) out.close() except Exception, err: return "/opt/serenade/no-art.png" return imagePath
class GoogleMusic(): """This class handles the communication with Google Music.""" def __init__(self, email, password): if not email or not password: raise Exception("Username and password needs to be given") self._library = None self._artists = None self._albums = None self._playlists = None self._email = email self._password = password # Initialize the Google Music API self._api_init(self._email, self._password) if not self._api.is_authenticated(): raise Exception("Credentials were not accepted!") # Initial load of the library self.load_library() def _api_init(self, email, password): """Inits the API object and login to Google Music""" self._api = Api() self._api.login(email, password) def load_library(self): """ This function downloads the music library from Google Music and processes it. """ self._library = self._api.get_all_songs() # Generate artists and albums trees self._gen_trees() def _gen_trees(self): """ This function generates trees of artists and albums from the library. Parts of this function are taken from: https://github.com/mstill/thunner/blob/master/thunner => Thanks for the great code :) """ # Use defaultdict to group song dictionaries by artists artists_dict = collections.defaultdict(list) for i in self._library: artists_dict[i['artist']].append(i) artists = [] albums = [] for artist, songs_of_artist in artists_dict.iteritems(): albums_of_artists_dict = collections.defaultdict(list) for i in songs_of_artist: albums_of_artists_dict[i['album']].append(i) albums_of_artist = [] for album, tracks in albums_of_artists_dict.iteritems(): album_name = album if album == "": album_name = "Untitled album" albums_of_artist.append({ "name": album_name, "subtree": sorted(tracks, key=itemgetter('track')), "subtreeline": 0 }) albums = albums + albums_of_artist artists.append({ "name": artist, "subtree": sorted(albums_of_artist, key=lambda x: x['name'].lower()), "subtreeline": 0 }) self._artists = sorted(artists, key=lambda x: x['name'].lower()) self._albums = sorted(albums, key=lambda x: x['name'].lower()) def get_songs(self): """This function returns all songs from the music library.""" songs = [] for entry in self._library: song = { 'id': entry['id'], 'title': entry['title'], 'artist': entry['artist'], 'album': entry['album'] } songs.append(song) return songs def get_artists(self): pass def get_albums(self): pass def get_stream_url(self, id): return self._api.get_stream_url(id)
class GoogleMusic(): """This class handles the communication with Google Music.""" def __init__(self, email, password): if not email or not password: raise Exception("Username and password needs to be given") self._library = None self._artists = None self._albums = None self._playlists = None self._email = email self._password = password # Initialize the Google Music API self._api_init(self._email, self._password) if not self._api.is_authenticated(): raise Exception("Credentials were not accepted!") # Initial load of the library self.load_library() def _api_init(self, email, password): """Inits the API object and login to Google Music""" self._api = Api() self._api.login(email, password) def load_library(self): """ This function downloads the music library from Google Music and processes it. """ self._library = self._api.get_all_songs() # Generate artists and albums trees self._gen_trees() def _gen_trees(self): """ This function generates trees of artists and albums from the library. Parts of this function are taken from: https://github.com/mstill/thunner/blob/master/thunner => Thanks for the great code :) """ # Use defaultdict to group song dictionaries by artists artists_dict = collections.defaultdict(list) for i in self._library: artists_dict[i['artist']].append(i) artists = [] albums = [] for artist, songs_of_artist in artists_dict.iteritems(): albums_of_artists_dict = collections.defaultdict(list) for i in songs_of_artist: albums_of_artists_dict[i['album']].append(i) albums_of_artist = [] for album,tracks in albums_of_artists_dict.iteritems(): album_name = album if album == "": album_name = "Untitled album" albums_of_artist.append({ "name": album_name, "subtree": sorted(tracks, key=itemgetter('track')), "subtreeline": 0 }) albums = albums + albums_of_artist artists.append({ "name": artist, "subtree": sorted(albums_of_artist, key=lambda x: x['name'].lower()), "subtreeline": 0 }) self._artists = sorted(artists, key=lambda x: x['name'].lower()) self._albums = sorted(albums, key=lambda x: x['name'].lower()) def get_songs(self): """This function returns all songs from the music library.""" songs = [] for entry in self._library: song = { 'id': entry['id'], 'title': entry['title'], 'artist': entry['artist'], 'album': entry['album'] } songs.append(song) return songs def get_artists(self): pass def get_albums(self): pass def get_stream_url(self, id): return self._api.get_stream_url(id)
class MusicLibrary(object): 'Read information about your Google Music library' def __init__(self, username=None, password=None, true_file_size=False): self.__artists = {} # 'artist name' -> {'album name' : Album(), ...} self.__albums = [] # [Album(), ...] self.__login(username, password) self.__aggregate_albums() self.true_file_size = true_file_size def __login(self, username=None, password=None): # If credentials are not specified, get them from $HOME/.gmusicfs if not username or not password: cred_path = os.path.join(os.path.expanduser('~'), '.gmusicfs') if not os.path.isfile(cred_path): raise NoCredentialException( 'No username/password was specified. No config file could ' 'be found either. Try creating %s and specifying your ' 'username/password there. Make sure to chmod 600.' % cred_path) if not oct( os.stat(cred_path)[os.path.stat.ST_MODE]).endswith('00'): raise NoCredentialException( 'Config file is not protected. Please run: ' 'chmod 600 %s' % cred_path) config = ConfigParser.ConfigParser() config.read(cred_path) username = config.get('credentials', 'username') password = config.get('credentials', 'password') if not username or not password: raise NoCredentialException( 'No username/password could be read from config file' ': %s' % cred_path) self.api = GoogleMusicAPI() log.info('Logging in...') self.api.login(username, password, perform_upload_auth=False) log.info('Login successful.') def __aggregate_albums(self): 'Get all the tracks in the library, parse into artist and album dicts' all_artist_albums = {} # 'Artist|||Album' -> Album() log.info('Gathering track information...') tracks = self.api.get_all_songs() for track in tracks: # Get the Album object if it already exists: key = '%s|||%s' % (formatNames( track['artistNorm']), formatNames(track['albumNorm'])) album = all_artist_albums.get(key, None) if not album: # New Album artist = formatNames(track['artistNorm']) if artist == '': artist = 'unknown' album = all_artist_albums[key] = Album( self, formatNames(track['albumNorm'])) self.__albums.append(album) artist_albums = self.__artists.get(artist, None) if artist_albums: artist_albums[formatNames(album.normtitle)] = album else: self.__artists[artist] = {album.normtitle: album} artist_albums = self.__artists[artist] album.add_track(track) log.debug('%d tracks loaded.' % len(tracks)) log.debug('%d artists loaded.' % len(self.__artists)) log.debug('%d albums loaded.' % len(self.__albums)) def get_artists(self): return self.__artists def get_albums(self): return self.__albums def get_artist_albums(self, artist): log.debug(artist) return self.__artists[artist]
class MusicSync(object): def __init__(self, email=None, password=None): self.api = Api() 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.api.get_all_playlist_ids(auto=False) print "Got %d playlists." % len(self.playlists['user']) print "" def auth(self): self.logged_in = self.api.login(self.email, self.password) if not self.logged_in: print "Login failed..." exit() print "" print "Logged in as %s" % self.email print "" 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 "Synching playlist: %s" % filename if title not in self.playlists['user']: print " didn't exist... creating..." self.playlists['user'][title] = [self.api.create_playlist(title)] print "" plid = self.playlists['user'][title][0] goog_songs = self.api.get_playlist_songs(plid) print "%d songs already in Google Music playlist" % len(goog_songs) local_songs = self.get_songs_from_playlist(filename) print "%d songs in local playlist" % len(local_songs) # Sanity check max 1000 songs per playlist if len(local_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 local_songs[MAX_SONGS_IN_PLAYLIST:] existing_files = 0 added_files = 0 failed_files = 0 removed_files = 0 fatal_count = 0 for fn, song in local_songs: if self.file_already_in_list(song, goog_songs): existing_files += 1 continue print "" print "Adding: %s" % os.path.basename(fn) online = self.find_song(song) song_id = None if online: song_id = online['id'] print " already uploaded [%s]" % song_id else: if not os.path.isfile(fn): print " Attempted to upload non-existent file", song continue attempts = 0 result = [] while not result and attempts < MAX_UPLOAD_ATTEMPTS_PER_FILE: print " uploading... (may take a while)" attempts += 1 try: result = self.api.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.api.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" if not song_id: failed_files += 1 continue added = self.api.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 s in goog_songs: print "" print "Removing: %s" % s['title'] self.api.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_songs_from_playlist(self, filename): ext = os.path.splitext(filename)[1].lower() if ext == '.m3u': return list(self._get_songs_from_m3u_playlist(filename)) elif ext == '.xml': return list(self._get_songs_from_itunes_playlist(filename)) def _get_songs_from_m3u_playlist(self, filename): with codecs.open(filename, encoding='utf-8') as f: 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 yield (path, self.get_id3_tag(path)) def _get_songs_from_itunes_playlist(self, filename): plist = plistlib.readPlist(filename) for track_id, track in plist['Tracks'].iteritems(): p = urlparse.urlparse(track['Location']) path = os.path.abspath(os.path.join(p.netloc, p.path)) yield (path, { 'name': track['Name'], #'artist': track['Artist'], 'album': track['Album'], 'track': track['Track Number'] if 'Track Number' in track else 0, 'disc': track['Disc Number'] if 'Disc Number' in track else 1 }) def file_already_in_list(self, song, goog_songs): i = 0 while i < len(goog_songs): if self.tag_compare(goog_songs[i], song): 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['name'] = 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, song): results = self.api.search(song['name']) # NOTE - dianostic print here to check results if you're creating duplicates #print results['song_hits'] #print "%s ][ %s ][ %s ][ %s" % (tag['title'], tag['artist'], tag['album'], tag['track']) return find_in_list(results['song_hits'], song) def tag_compare(self, g_song, tag): # If a google result has no track, google doesn't return a field for it if 'track' not in g_song: g_song['track'] = 0 #g_song['artist'].lower() == tag['artist'].lower() and\ return g_song['name'].lower() == tag['name'].lower() and\ g_song['album'].lower() == tag['album'].lower() and\ g_song['track'] == tag['track'] def delete_song(self, sid): self.api.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('\\', '/'))