Example #1
0
 def create_music_manager(self):
     music_manager = Musicmanager()
     success = music_manager.login(
         oauth_credentials=config.OAUTH_CREDENTIAL_PATH)
     if not success:
         raise Exception("Failed to login music manager")
     print("Logged in music manager")
     return music_manager
Example #2
0
def no_client_auth_initially():
    # wc = Webclient()
    # assert_false(wc.is_authenticated())

    mc = Mobileclient()
    assert_false(mc.is_authenticated())

    mm = Musicmanager()
    assert_false(mm.is_authenticated())
Example #3
0
def no_client_auth_initially():
    # wc = Webclient()
    # assert_false(wc.is_authenticated())

    mc = Mobileclient()
    assert_false(mc.is_authenticated())

    mm = Musicmanager()
    assert_false(mm.is_authenticated())
Example #4
0
def login():
    """Login with oauth. This is required for Musicmanager."""
    manager = Musicmanager()
    path = os.path.expanduser('~/.accessall')
    if os.path.isfile(path):
        creds = path
    else:
        creds = manager.perform_oauth(storage_filepath=path)
    manager.login(oauth_credentials=creds)
    return manager
	def __init__(self, log=False):
		"""

		:param log: Enable gmusicapi's debug_logging option.
		"""

		self.api = Musicmanager(debug_logging=log)
		self.api.logger.addHandler(logging.NullHandler())
Example #6
0
    def __init__(self):
        super(GooglePlayMusicPlugin, self).__init__()

        # Get configurations
        self.config.add({
            'auto-upload': True,
            #'auto-delete': True,
            'enable-matching': True,
            'uploader-id': None
        })
        self.auto_upload = self.config['auto-upload'].get(bool)
        #self.auto_delete = self.config['auto-delete'].get(bool)
        self.enable_matching = self.config['enable-matching'].get(bool)
        self.uploader_id = self.config['uploader-id'].get()

        # Initialize gmusicapi
        self.mm = Musicmanager(debug_logging=False)
        self.mm.logger.addHandler(logging.NullHandler())
        try:
            if not self.mm.login(oauth_credentials=OAUTH_FILEPATH, uploader_id=self.uploader_id):
                try:
                    self.mm.perform_oauth()
                except e:
                    self._log.error("Error: Unable to login with specified oauth code.")
                    raise e
                self.mm.login(oauth_credentials=OAUTH_FILEPATH, uploader_id=self.uploader_id)
        except (OSError, ValueError) as e:
            self._log.error("Error: Couldn't log in.")
            raise e

        if not self.authenticated:
            self._log.error("Error: Couldn't log in.")

        # Register listeners
        if self.auto_upload:
            self.register_listener('item_imported', self.on_item_imported)
            self.register_listener('album_imported', self.on_album_imported)
def gm_login(credentials):
    if not hasattr(gm_login, 'api'):
        # Stored api connection for multiple upload support without re-authing every time
        gm_login.api = Musicmanager(debug_logging=VERBOSE)
    # Credential pass through is working here
    if not gm_login.api.is_authenticated():
        if not gm_login.api.login(
                OAUTH_FILEPATH if not credentials else credentials):
            try:
                gm_login.api.perform_oauth()
            except:
                print("Unable to login with specified oauth code.")
            gm_login.api.login(
                OAUTH_FILEPATH if not credentials else credentials)
    return gm_login.api
class MusicManagerWrapper(_Base):
	def __init__(self, log=False, quiet=False):
		self.api = Musicmanager(debug_logging=log)
		self.api.logger.addHandler(logging.NullHandler())

		self.print_ = safe_print if not quiet else lambda *args, **kwargs: None

	def login(self, oauth_file="oauth", uploader_id=None):
		"""Authenticate the gmusicapi Musicmanager instance."""

		oauth_cred = os.path.join(os.path.dirname(OAUTH_FILEPATH), oauth_file + '.cred')

		try:
			if not self.api.login(oauth_credentials=oauth_cred, uploader_id=uploader_id):
				try:
					self.api.perform_oauth(storage_filepath=oauth_cred)
				except:
					self.print_("\nUnable to login with specified oauth code.")

				self.api.login(oauth_credentials=oauth_cred, uploader_id=uploader_id)
		except (OSError, ValueError) as e:
			self.print_(e.args[0])
			return False

		if not self.api.is_authenticated():
			self.print_("Sorry, login failed.")

			return False

		self.print_("Successfully logged in.\n")

		return True

	def logout(self, revoke_oauth=False):
		"""Log out the gmusicapi Musicmanager instance."""

		self.api.logout(revoke_oauth=revoke_oauth)

	def get_google_songs(self, filters=None, filter_all=False):
		"""Load song list from Google Music library."""

		self.print_("Loading Google Music songs...")

		google_songs = []
		filter_songs = []

		songs = self.api.get_uploaded_songs()

		google_songs, filter_songs = match_filters_google(songs, filters, filter_all)

		self.print_("Filtered {0} Google Music songs".format(len(filter_songs)))
		self.print_("Loaded {0} Google Music songs\n".format(len(google_songs)))

		return google_songs

	@gm_utils.accept_singleton(basestring)
	def download(self, songs, template):
		"""Download songs from Google Music."""

		songnum = 0
		total = len(songs)
		errors = {}
		pad = len(str(total))

		for song in songs:
			songnum += 1

			try:
				self.print_("Downloading  {0} by {1}".format(song['title'], song['artist']), end="\r")
				sys.stdout.flush()
				suggested_filename, audio = self.api.download_song(song['id'])
			except CallFailure as e:
				self.print_("({num:>{pad}}/{total}) Failed to download  {file} | {error}".format(num=songnum, total=total, file=suggested_filename, error=e, pad=pad))
				errors[suggested_filename] = e
			else:
				with tempfile.NamedTemporaryFile(delete=False) as temp:
					temp.write(audio)

				metadata = mutagen.File(temp.name, easy=True)

				if template != os.getcwd():
					filename = template_to_file_name(template, suggested_filename, metadata)
				else:
					filename = suggested_filename

				shutil.move(temp.name, filename)

				self.print_("({num:>{pad}}/{total}) Successfully downloaded  {file}".format(num=songnum, total=total, file=filename, pad=pad))

		if errors:
			self.print_("\n\nThe following errors occurred:\n")
			for filename, e in errors.iteritems():
				self.print_("{file} | {error}".format(file=filename, error=e))
			self.print_("\nThese files may need to be synced again.\n")

	@gm_utils.accept_singleton(basestring)
	def upload(self, files, enable_matching=False):
		"""Upload files to Google Music."""

		filenum = 0
		total = len(files)
		errors = {}
		pad = len(str(total))

		for file in files:
			filenum += 1

			try:
				self.print_("Uploading  {0}".format(file), end="\r")
				sys.stdout.flush()
				uploaded, matched, not_uploaded = self.api.upload(file, transcode_quality="320k", enable_matching=enable_matching)
			except CallFailure as e:
				self.print_("({num:>{pad}}/{total}) Failed to upload  {file} | {error}".format(num=filenum, total=total, file=file, error=e, pad=pad))
				errors[file] = e
			else:
				if uploaded:
					self.print_("({num:>{pad}}/{total}) Successfully uploaded  {file}".format(num=filenum, total=total, file=file, pad=pad))
				elif matched:
					self.print_("({num:>{pad}}/{total}) Successfully scanned and matched  {file}".format(num=filenum, total=total, file=file, pad=pad))
				else:
					check_strings = ["ALREADY_EXISTS", "this song is already uploaded"]
					if any(check_string in not_uploaded[file] for check_string in check_strings):
						response = "ALREADY EXISTS"
					else:
						response = not_uploaded[file]
					self.print_("({num:>{pad}}/{total}) Failed to upload  {file} | {response}".format(num=filenum, total=total, file=file, response=response, pad=pad))

		if errors:
			self.print_("\n\nThe following errors occurred:\n")
			for file, e in errors.iteritems():
				self.print_("{file} | {error}".format(file=file, error=e))
			self.print_("\nThese files may need to be synced again.\n")
Example #9
0
    '--quiet',
    action='store_true',
    default=False,
    help=
    'Don\'t output status messages\n-l,--log will display gmusicapi warnings\n-d,--dry-run will display song list'
)
parser.add_argument(
    'input',
    nargs='*',
    default='.',
    help=
    'Files, directories, or glob patterns to upload\nDefaults to current directory if none given'
)
opts = parser.parse_args()

MM = Musicmanager(debug_logging=opts.log)

_print = print if not opts.quiet else lambda *a, **k: None


def do_auth():
    """
	Authenticates the MM client.
	"""

    attempts = 0

    oauth_file = os.path.join(os.path.dirname(OAUTH_FILEPATH),
                              opts.cred + '.cred')

    # Attempt to login. Perform oauth only when necessary.
Example #10
0
class GooglePlayMusicPlugin(BeetsPlugin):
    def __init__(self):
        super(GooglePlayMusicPlugin, self).__init__()

        # Get configurations
        self.config.add({
            'auto-upload': True,
            #'auto-delete': True,
            'enable-matching': True,
            'uploader-id': None
        })
        self.auto_upload = self.config['auto-upload'].get(bool)
        #self.auto_delete = self.config['auto-delete'].get(bool)
        self.enable_matching = self.config['enable-matching'].get(bool)
        self.uploader_id = self.config['uploader-id'].get()

        # Initialize gmusicapi
        self.mm = Musicmanager(debug_logging=False)
        self.mm.logger.addHandler(logging.NullHandler())
        try:
            if not self.mm.login(oauth_credentials=OAUTH_FILEPATH, uploader_id=self.uploader_id):
                try:
                    self.mm.perform_oauth()
                except e:
                    self._log.error("Error: Unable to login with specified oauth code.")
                    raise e
                self.mm.login(oauth_credentials=OAUTH_FILEPATH, uploader_id=self.uploader_id)
        except (OSError, ValueError) as e:
            self._log.error("Error: Couldn't log in.")
            raise e

        if not self.authenticated:
            self._log.error("Error: Couldn't log in.")

        # Register listeners
        if self.auto_upload:
            self.register_listener('item_imported', self.on_item_imported)
            self.register_listener('album_imported', self.on_album_imported)


    def on_item_imported(self, lib, item):
        if not self.authenticated:
            self._log.warning("Warning: not logged in")
        else:
            uploaded, matched, not_uploaded = \
                self.mm.upload(item.path, enable_matching=self.enable_matching)
            if uploaded:
                self._log.info('Successfully uploaded "{0}"'.format(item.path))
            elif matched:
                self._log.info('Successfully scanned and matched "{0}"'.format(item.path))
            else:
                self._log.warning('Warning: {0}'.format(not_uploaded[item.path]))


    def on_album_imported(self, lib, album):
        for item in album.items():
            self.on_item_imported(lib, item)


    @property
    def authenticated(self):
        return self.mm.is_authenticated()
Example #11
0
)
parser.add_argument(
    'input',
    nargs='*',
    default='.',
    help=
    'Files, directories, or glob patterns to upload\nDefaults to current directory if none given'
)
opts = parser.parse_args()

# Pre-compile regex for exclude option.
excludes = re.compile("|".join(
    pattern.decode('utf8')
    for pattern in opts.exclude)) if opts.exclude else None

mm = Musicmanager(debug_logging=opts.log)

_print = print if not opts.quiet else lambda *a, **k: None


def do_auth():
    """
	Authenticates the mm client.
	"""

    attempts = 0

    oauth_file = os.path.join(os.path.dirname(OAUTH_FILEPATH),
                              opts.cred + '.cred')

    # Attempt to login. Perform oauth only when necessary.
# Parse command line for arguments.
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('-c', '--cred', default='oauth', help='Specify oauth credential file name to use/create\n(Default: "oauth" -> ' + OAUTH_FILEPATH + ')')
parser.add_argument('-l', '--log', action='store_true', default=False, help='Enable gmusicapi logging')
parser.add_argument('-m', '--match', action='store_true', default=False, help='Enable scan and match')
parser.add_argument('-e', '--exclude', action='append', help='Exclude file paths matching a Python regex pattern\nThis option can be set multiple times', metavar="PATTERN")
parser.add_argument('-d', '--dry-run', action='store_true', default=False, help='Output list of songs that would be uploaded and excluded')
parser.add_argument('-q', '--quiet', action='store_true', default=False, help='Don\'t output status messages\n-l,--log will display gmusicapi warnings\n-d,--dry-run will display song list')
parser.add_argument('input', nargs='*', default='.', help='Files, directories, or glob patterns to upload\nDefaults to current directory if none given')
opts = parser.parse_args()

# Pre-compile regex for exclude option.
excludes = re.compile("|".join(pattern.decode('utf8') for pattern in opts.exclude)) if opts.exclude else None

mm = Musicmanager(debug_logging=opts.log)

_print = print if not opts.quiet else lambda *a, **k: None


def do_auth():
	"""
	Authenticates the mm client.
	"""

	attempts = 0

	oauth_file = os.path.join(os.path.dirname(OAUTH_FILEPATH), opts.cred + '.cred')

	# Attempt to login. Perform oauth only when necessary.
	while attempts < 3:
Example #13
0
# Parse command line for arguments.
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('-c', '--cred', default='oauth', help='Specify oauth credential file name to use/create\n(Default: "oauth" -> ' + OAUTH_FILEPATH + ')')
parser.add_argument('-l', '--log', action='store_true', default=False, help='Enable gmusicapi logging')
parser.add_argument('-m', '--match', action='store_true', default=False, help='Enable scan and match')
parser.add_argument('-e', '--exclude', action='append', help='Exclude file paths matching a Python regex pattern\nThis option can be set multiple times', metavar="PATTERN")
parser.add_argument('-d', '--dry-run', action='store_true', default=False, help='Output list of songs that would be uploaded and excluded')
parser.add_argument('-q', '--quiet', action='store_true', default=False, help='Don\'t output status messages\n-l,--log will display gmusicapi warnings\n-d,--dry-run will display song list')
parser.add_argument('input', nargs='*', default='.', help='Files, directories, or glob patterns to upload\nDefaults to current directory if none given')
opts = parser.parse_args()

# Pre-compile regex for exclude option.
excludes = re.compile("|".join(pattern.decode('utf8') for pattern in opts.exclude)) if opts.exclude else None

MM = Musicmanager(debug_logging=opts.log)

_print = print if not opts.quiet else lambda *a, **k: None


def do_auth():
	"""
	Authenticates the MM client.
	"""

	attempts = 0

	oauth_file = os.path.join(os.path.dirname(OAUTH_FILEPATH), opts.cred + '.cred')

	# Attempt to login. Perform oauth only when necessary.
	while attempts < 3:
Example #14
0
def fix(path):
    # OSX:   : -> FULLWIDTH COLON (U+FF1A)
    # OSX:   / -> : (translated as / in Cocoa)
    # LINUX: / -> DIVISION SLASH (U+2215)

    path.replace(':', "\uFF1A")
    path.replace('/', ':')
    # path.replace('/', "\u2215")

    return path


if os.path.exists('oauth.cred'):
    pass
else:
    Musicmanager.perform_oauth('oauth.cred', True)

manager = Musicmanager()

manager.login('oauth.cred')

columns = int(subprocess.check_output(['stty', 'size']).split()[1])

logging.info('starting download')
count = 0
for songs in manager.get_uploaded_songs(True):
    for song in songs:
        count = count + 1

        dartist = 'dl/%s' % fix(song['album_artist'] or song['artist'])
        if not os.path.exists(dartist):
Example #15
0
	def __init__(self, log=False, quiet=False):
		self.api = Musicmanager(debug_logging=log)
		self.api.logger.addHandler(logging.NullHandler())

		self.print_ = safe_print if not quiet else lambda *args, **kwargs: None
Example #16
0
class PlaylistSync:

    def __init__(self, root, playlist_name):
        self.root = root
        self.playlist_name = playlist_name
 
    def _login_mc(self):
        APP_NAME = 'gmusic-sync-playlist'
        CONFIG_FILE = 'auth.cfg'

        config = SafeConfigParser({
            'username': '',
            'device_id': ''
        })
        config.read(CONFIG_FILE)
        if not config.has_section('auth'):
            config.add_section('auth')
    
        username = config.get('auth','username')
        password = None
        if username != '':
            password = keyring.get_password(APP_NAME, username)
    
        if password == None or not self.mc.login(username, password):
            while 1:
                username = raw_input("Username: "******"Password: "******"Sign-on failed."
    
            config.set('auth', 'username', username)
            with open(CONFIG_FILE, 'wb') as f:
                config.write(f)
    
            keyring.set_password(APP_NAME, username, password)


        device_id = config.get('auth', 'device_id')

        if device_id == '':
            wc = Webclient()
            if not wc.login(username, password):
                raise Exception('could not log in via Webclient')
            devices = wc.get_registered_devices()
            mobile_devices = [d for d in devices if d[u'type'] in (u'PHONE', u'IOS')]
            if len(mobile_devices) < 1:
                raise Exception('could not find any registered mobile devices')
            device_id = mobile_devices[0][u'id']
            if device_id.startswith(u'0x'):
                device_id = device_id[2:]
            
            config.set('auth', 'device_id', device_id)
            with open(CONFIG_FILE, 'wb') as f:
                config.write(f)

        print('Device ID: {}'.format(device_id))
        self.mc.device_id = device_id


    def login(self):
        self.mc = Mobileclient()
        self._login_mc()
        self.mm = Musicmanager()
        #self.mm.perform_oauth()
        self.mm.login()

    def track_file_name(self, track):
        if 'albumArtist' in track:
            albumartist = track['albumArtist']
        else:
            albumartist = 'Various'
        if not albumartist:
            albumartist = 'Various'
        file_name = escape_path(u'{trackNumber:02d} {title}.mp3'.format(**track))
        if track.get('totalDiscCount', 1) > 1:
            file_name = u'{discNumber}-'.format(**track) + file_name
        return unicodedata.normalize('NFD', os.path.join(self.root, escape_path(albumartist), escape_path(track['album']), file_name))

    def get_local_tracks(self):
        # return (metadata, file_name) of all files in root
        tracks = []
        for root, dirs, files in os.walk(self.root):
            for f in files:
                if os.path.splitext(f)[1].lower() == '.mp3':
                    file_name = os.path.join(root, f)
                    #id3 = EasyID3(file_name)
                    track = {}
                    #track = {
                    #  'name': id3['title'],
                    #  'album': id3['album'],
                    #  'track': id3['tracknumber'],
                    #  'disc': id3['discnumber']
                    #}
                    yield unicodedata.normalize('NFD', file_name.decode('utf-8')), track

    def get_playlist_tracks(self):
        # return (metadata, local_file_name) for each track in playlist
        all_playlists = self.mc.get_all_playlists()
        try:
            playlist = next(p for p in all_playlists if p['name'] == self.playlist_name)
        except StopIteration:
            raise Exception('playlist "{0}" not found'.format(self.playlist_name))
        contents = self.mc.get_shared_playlist_contents(playlist['shareToken'])
        for t in contents:
            track = t[u'track']
            #pprint(track)
            #raw_input()
            yield (self.track_file_name(track), track)

        #for p in all_playlists:
        #    shared = self.mc.get_shared_playlist_contents(p['shareToken'])
        #    pprint(shared)
        #for p in self.mc.get_all_user_playlist_contents():
        #    del p['tracks']
        #    pprint(p)
        #    raw_input()

        return

        all_songs = self.mc.get_all_songs()
        pprint(all_songs[0])
        for p in self.mc.get_all_user_playlist_contents():
            if p['name'] == self.playlist_name:
                for track in p['tracks']:
                    song = next(s for s in all_songs if s['id'] == track['trackId'])
                    print(u'{album} - {title}'.format(**song))
                    #pprint(song)
                    yield self.track_file_name(song), song

    def add_track(self, track, file_name):
        # download track from gmusic, write to file_name
        if not os.path.exists(os.path.dirname(file_name)):
            os.makedirs(os.path.dirname(file_name))
        if track[u'kind'] != u'sj#track' or u'id' not in track:
            if u'id' not in track:
                track[u'id'] = track[u'storeId']
            url = self.mc.get_stream_url(track[u'id'], self.mc.device_id)
            r = requests.get(url)
            data = r.content
            #data = self.wc.get_stream_audio(track['id'])
            with open(file_name, 'wb') as f:
                f.write(data)
            _copy_track_metadata(file_name, track)
        else:
            fn, audio = self.mm.download_song(track['id'])
            with open(file_name, 'wb') as f:
                f.write(audio)
        
    def remove_track(self, file_name):
        """Removes the track and walks up the tree deleting empty folders
        """
        os.remove(file_name)
        rel = os.path.relpath(file_name, self.root)
        dirs = os.path.split(rel)[0:-1]
        for i in xrange(1, len(dirs) + 1):
            dir_path = os.path.join(self.root, *dirs[0:i])
            if not os.listdir(dir_path):
                os.unlink(dir_path)


    def sync(self, confirm=True, remove=False):
        print 'Searching for local tracks ...'
        local = dict(self.get_local_tracks())
        print 'Getting playlist ...'
        playlist = OrderedDict(self.get_playlist_tracks())

        to_add = []
        to_remove = []
        to_rename = []

        for file_name, track in playlist.iteritems():
            if file_name not in local and file_name.encode('ascii', 'replace').replace('?','_') not in local:
                to_add.append((track, file_name))
            elif file_name not in local and file_name.encode('ascii', 'replace').replace('?','_') in local:
                to_rename.append((file_name.encode('ascii', 'replace').replace('?','_'), file_name))

        if remove:
            for file_name, track in sorted(local.iteritems()):
                if file_name not in playlist:
                    to_remove.append((track, file_name))

        if to_remove:
            print 'Deleting tracks:'
            for track, file_name in to_remove:
                print '  ' + file_name
            print ''
        if to_add:
            to_add = list(reversed(to_add))
            print 'Adding tracks:'
            for track, file_name in to_add:
                print '  ' + file_name
            print ''
        if to_rename:
            print 'Renaming tracks:'
            for src, dst in to_rename:
                print '  {0} to {1}'.format(src, dst)
            print ''
        if not (to_add or to_remove):
            print 'Nothing to do.'
            print ''

        if confirm:
            raw_input('Press enter to proceed')

        for src, dst in to_rename:
            if not os.path.exists(os.path.dirname(dst)):
                os.makedirs(os.path.dirname(dst))
            shutil.move(src, dst)
        for track, file_name in to_remove:
            print 'removing track ' + file_name
            self.remove_track(file_name)
        for track, file_name in to_add:
            print u'adding track: {album} / \n  {title}'.format(**track).encode('utf-8', 'replace')
            self.add_track(track, file_name)
Example #17
0
 def login(self):
     self.mc = Mobileclient()
     self._login_mc()
     self.mm = Musicmanager()
     #self.mm.perform_oauth()
     self.mm.login()
class MusicManagerWrapper(_Base):
	"""Wraps gmusicapi's Musicmanager client interface to provide extra functionality and conveniences."""

	def __init__(self, log=False):
		"""

		:param log: Enable gmusicapi's debug_logging option.
		"""

		self.api = Musicmanager(debug_logging=log)
		self.api.logger.addHandler(logging.NullHandler())

	def login(self, oauth_filename="oauth", uploader_id=None):
		"""Authenticate the gmusicapi Musicmanager instance.

		Returns ``True`` on successful login or ``False`` on unsuccessful login.

		:param oauth_filename: The filename of the oauth credentials file to use/create for login.
		  Default: ``oauth``

		:param uploader_id: A unique id as a MAC address (e.g. ``'00:11:22:33:AA:BB'``).
		  This should only be provided in cases where the default (host MAC address incremented by 1)
		  won't work.
		"""

		oauth_cred = os.path.join(os.path.dirname(OAUTH_FILEPATH), oauth_filename + '.cred')

		try:
			if not self.api.login(oauth_credentials=oauth_cred, uploader_id=uploader_id):
				try:
					self.api.perform_oauth(storage_filepath=oauth_cred)
				except:
					logger.info("\nUnable to login with specified oauth code.")

				self.api.login(oauth_credentials=oauth_cred, uploader_id=uploader_id)
		except (OSError, ValueError) as e:
			logger.info(e.args[0])
			return False

		if not self.api.is_authenticated():
			logger.info("Sorry, login failed.")

			return False

		logger.info("Successfully logged in.\n")

		return True

	def logout(self, revoke_oauth=False):
		"""Log out the gmusicapi Musicmanager instance.

		Returns ``True`` on success.

		:param revoke_oauth: If ``True``, oauth credentials will be revoked and
		  the corresponding oauth file will be deleted.
		"""

		return self.api.logout(revoke_oauth=revoke_oauth)

	def get_google_songs(self, include_filters=None, exclude_filters=None, all_include_filters=False, all_exclude_filters=False):
		"""Create song list from user's Google Music library using gmusicapi's Musicmanager.get_uploaded_songs().

		Returns a list of Google Music song dicts matching criteria and
		a list of Google Music song dicts filtered out using filter criteria.

		:param include_filters: A list of ``(field, pattern)`` tuples.
		  Fields are any valid Google Music metadata field available to the Musicmanager client.
		  Patterns are Python regex patterns.

		  Google Music songs are filtered out if the given metadata field values don't match any of the given patterns.

		:param exclude_filters: A list of ``(field, pattern)`` tuples.
		  Fields are any valid Google Music metadata field available to the Musicmanager client.
		  Patterns are Python regex patterns.

		  Google Music songs are filtered out if the given metadata field values match any of the given patterns.

		:param all_include_filters: If ``True``, all include_filters criteria must match to include a song.

		:param all_exclude_filters: If ``True``, all exclude_filters criteria must match to exclude a song.
		"""

		logger.info("Loading Google Music songs...")

		google_songs = self.api.get_uploaded_songs()

		if include_filters or exclude_filters:
			matched_songs, filtered_songs = filter_google_songs(
				google_songs, include_filters, exclude_filters, all_include_filters, all_exclude_filters
			)
		else:
			matched_songs = google_songs
			filtered_songs = []

		logger.info("Filtered {0} Google Music songs".format(len(filtered_songs)))
		logger.info("Loaded {0} Google Music songs".format(len(matched_songs)))

		return matched_songs, filtered_songs

	@accept_singleton(basestring)
	def _download(self, songs, template=os.getcwd()):
		"""Download the given songs one-by-one.

		Yields a 2-tuple ``(download, error)`` of dictionaries.

		    (
		        {'<server id>': '<filepath>'},  # downloaded
                {'<filepath>': '<exception>'}   # error
		    )

		:param songs: A list of Google Music song dicts.

		:param template: A filepath which can include template patterns as definied by
		  :const gmusicapi_wrapper.utils.TEMPLATE_PATTERNS:.
		"""

		for song in songs:
			song_id = song['id']

			try:
				title = song.get('title', "<empty>")
				artist = song.get('artist', "<empty>")
				album = song.get('album', "<empty>")

				if artist == "" :
					artist = u'no_artist'
				if album == "" :
					album = u'no_album'

				logger.debug(
					"Downloading {title} -- {artist} -- {album} ({song_id})".format(
						title=title, artist=artist, album=album, song_id=song_id
					)
				)

				suggested_filename, audio = self.api.download_song(song_id)

				with tempfile.NamedTemporaryFile(delete=False) as temp:
					temp.write(audio)

				metadata = mutagen.File(temp.name, easy=True)

				metadata['artist'] =  [artist]
				metadata['album'] =  [album]

				if "%suggested%" in template:
					template = template.replace("%suggested%", suggested_filename.replace('.mp3', ''))
				
				if os.name == 'nt' and cygpath_re.match(template):
					template = convert_cygwin_path(template)

				if template != os.getcwd():
					filepath = template_to_filepath(template, metadata) + '.mp3'

					dirname, basename = os.path.split(filepath)

					if basename == '.mp3':
						filepath = os.path.join(dirname, suggested_filename)
				else:
					filepath = suggested_filename

				dirname = os.path.dirname(filepath)

				if dirname:
					try:
						os.makedirs(dirname)
					except OSError:
						if not os.path.isdir(dirname):
							raise

				dirname, basename = os.path.split(filepath)
				if len(basename) > 254 :
					filepath = filepath.replace(basename,basename[0:220]+'.mp3')

				shutil.move(temp.name, filepath)

				result = ({song_id: filepath}, {})
			except CallFailure as e:
				result = ({}, {song_id: e})

			yield result

	@accept_singleton(basestring)
	def download(self, songs, template=os.getcwd()):
		"""Download the given songs one-by-one.

		Yields a 2-tuple ``(download, error)`` of dictionaries.

		    (
		        {'<server id>': '<filepath>'},  # downloaded
                {'<filepath>': '<exception>'}   # error
		    )

		:param songs: A list of Google Music song dicts.

		:param template: A filepath which can include template patterns as definied by
		  :const gmusicapi_wrapper.utils.TEMPLATE_PATTERNS:.
		"""

		songnum = 0
		total = len(songs)
		errors = {}
		pad = len(str(total))
		results = []

		for result in self._download(songs, template):
			song_id = songs[songnum]['id']
			songnum += 1

			downloaded, error = result

			if downloaded:
				logger.info(
					"({num:>{pad}}/{total}) Successfully downloaded -- {file} ({song_id})".format(
						num=songnum, pad=pad, total=total, file=downloaded[song_id], song_id=song_id
					)
				)
			elif error:
				title = songs[songnum].get('title', "<empty>")
				artist = songs[songnum].get('artist', "<empty>")
				album = songs[songnum].get('album', "<empty>")

				logger.info(
					"({num:>{pad}}/{total}) Error on download -- {title} -- {artist} -- {album} ({song_id})".format(
						num=songnum, pad=pad, total=total, title=title, artist=artist, album=album, song_id=song_id
					)
				)

				errors.update(error)

			results.append(result)

		if errors:
			logger.info("\n\nThe following errors occurred:\n")
			for filepath, e in errors.items():
				logger.info("{file} | {error}".format(file=filepath, error=e))
			logger.info("\nThese files may need to be synced again.\n")

		return results

	@accept_singleton(basestring)
	def _upload(self, filepaths, enable_matching=False, transcode_quality='320k'):
		"""Upload the given filepaths one-by-one.

		Yields a 4-tuple ``(uploaded, matched, not_uploaded, error)`` of dictionaries.

		    (
		        {'<filepath>': '<new server id>'},                 # uploaded
                {'<filepath>': '<new server id>'},                 # matched
                {'<filepath>': '<reason (e.g. ALREADY_EXISTS)>'},  # not_uploaded
                {'<filepath>': '<exception>'}                      # error
		    )

		:param filepaths: A list of filepaths or a single filepath.

		:param enable_matching: If ``True`` attempt to use `scan and match
		  <http://support.google.com/googleplay/bin/answer.py?hl=en&answer=2920799&topic=2450455>`__
		  to avoid uploading every song. This requieres ffmpeg or avconv.

		:param transcode_quality: If int, pass to ffmpeg/avconv ``-q:a`` for libmp3lame `VBR quality
		  <http://trac.ffmpeg.org/wiki/Encode/MP3#VBREncoding>'__.
		  If string, pass to ffmpeg/avconv ``-b:a`` for libmp3lame `CBR quality
		  <http://trac.ffmpeg.org/wiki/Encode/MP3#CBREncoding>'__.
		  Default: '320k'
		"""

		for filepath in filepaths:
			try:
				logger.debug("Uploading -- {}".format(filepath))
				uploaded, matched, not_uploaded = self.api.upload(filepath, enable_matching=enable_matching, transcode_quality=transcode_quality)
				result = (uploaded, matched, not_uploaded, {})
			except CallFailure as e:
				result = ({}, {}, {}, {filepath: e})

			yield result

	@accept_singleton(basestring)
	def upload(self, filepaths, enable_matching=False, transcode_quality='320k', delete_on_success=False):
		"""Upload local filepaths to Google Music.

		Returns a list of 4-tuples ``(uploaded, matched, not_uploaded, error)`` of dictionaries.

		    (
		        {'<filepath>': '<new server id>'},                 # uploaded
                {'<filepath>': '<new server id>'},                 # matched
                {'<filepath>': '<reason (e.g. ALREADY_EXISTS)>'},  # not_uploaded
                {'<filepath>': '<exception>'}                      # error
		    )

		:param filepaths: A list of filepaths or a single filepath.

		:param enable_matching: If ``True`` attempt to use `scan and match
		  <http://support.google.com/googleplay/bin/answer.py?hl=en&answer=2920799&topic=2450455>`__
		  to avoid uploading every song. This requieres ffmpeg or avconv.

		:param transcode_quality: If int, pass to ffmpeg/avconv ``-q:a`` for libmp3lame `VBR quality
		  <http://trac.ffmpeg.org/wiki/Encode/MP3#VBREncoding>'__.
		  If string, pass to ffmpeg/avconv ``-b:a`` for libmp3lame `CBR quality
		  <http://trac.ffmpeg.org/wiki/Encode/MP3#CBREncoding>'__.
		  Default: '320k'

		:param delete_on_success: Delete successfully uploaded local files.
		  Default: False
		"""

		filenum = 0
		total = len(filepaths)
		uploaded_songs = {}
		matched_songs = {}
		not_uploaded_songs = {}
		errors = {}
		pad = len(str(total))
		exist_strings = ["ALREADY_EXISTS", "this song is already uploaded"]

		for result in self._upload(filepaths, enable_matching=enable_matching, transcode_quality=transcode_quality):
			filepath = filepaths[filenum]
			filenum += 1

			uploaded, matched, not_uploaded, error = result

			if uploaded:
				logger.info(
					"({num:>{pad}}/{total}) Successfully uploaded -- {file} ({song_id})".format(
						num=filenum, pad=pad, total=total, file=filepath, song_id=uploaded[filepath]
					)
				)

				uploaded_songs.update(uploaded)

				if delete_on_success:
					try:
						os.remove(filepath)
					except:
						logger.warning("Failed to remove {} after successful upload".format(filepath))
			elif matched:
				logger.info(
					"({num:>{pad}}/{total}) Successfully scanned and matched -- {file} ({song_id})".format(
						num=filenum, pad=pad, total=total, file=filepath, song_id=matched[filepath]
					)
				)

				matched_songs.update(matched)

				if delete_on_success:
					try:
						os.remove(filepath)
					except:
						logger.warning("Failed to remove {} after successful upload".format(filepath))
			elif error:
				logger.warning("({num:>{pad}}/{total}) Error on upload -- {file}".format(num=filenum, pad=pad, total=total, file=filepath))

				errors.update(error)
			else:
				if any(exist_string in not_uploaded[filepath] for exist_string in exist_strings):
					response = "ALREADY EXISTS"
				else:
					response = not_uploaded[filepath]

				logger.info(
					"({num:>{pad}}/{total}) Failed to upload -- {file} | {response}".format(
						num=filenum, pad=pad, total=total, file=filepath, response=response
					)
				)

				not_uploaded_songs.update(not_uploaded)

		if errors:
			logger.info("\n\nThe following errors occurred:\n")

			for filepath, e in errors.items():
				logger.info("{file} | {error}".format(file=filepath, error=e))
			logger.info("\nThese filepaths may need to be synced again.\n")

		return (uploaded_songs, matched_songs, not_uploaded_songs, errors)
Example #19
0
    def __init__(self, log=False, quiet=False):
        self.api = Musicmanager(debug_logging=log)
        self.api.logger.addHandler(logging.NullHandler())

        self.print_ = safe_print if not quiet else lambda *args, **kwargs: None
Example #20
0
class MusicManagerWrapper(_Base):
    def __init__(self, log=False, quiet=False):
        self.api = Musicmanager(debug_logging=log)
        self.api.logger.addHandler(logging.NullHandler())

        self.print_ = safe_print if not quiet else lambda *args, **kwargs: None

    def login(self, oauth_file="oauth", uploader_id=None):
        """Authenticate the gmusicapi Musicmanager instance."""

        oauth_cred = os.path.join(os.path.dirname(OAUTH_FILEPATH),
                                  oauth_file + '.cred')

        try:
            if not self.api.login(oauth_credentials=oauth_cred,
                                  uploader_id=uploader_id):
                try:
                    self.api.perform_oauth(storage_filepath=oauth_cred)
                except:
                    self.print_("\nUnable to login with specified oauth code.")

                self.api.login(oauth_credentials=oauth_cred,
                               uploader_id=uploader_id)
        except (OSError, ValueError) as e:
            self.print_(e.args[0])
            return False

        if not self.api.is_authenticated():
            self.print_("Sorry, login failed.")

            return False

        self.print_("Successfully logged in.\n")

        return True

    def logout(self, revoke_oauth=False):
        """Log out the gmusicapi Musicmanager instance."""

        self.api.logout(revoke_oauth=revoke_oauth)

    def get_google_songs(self, filters=None, filter_all=False):
        """Load song list from Google Music library."""

        self.print_("Loading Google Music songs...")

        google_songs = []
        filter_songs = []

        songs = self.api.get_uploaded_songs()

        google_songs, filter_songs = match_filters_google(
            songs, filters, filter_all)

        self.print_("Filtered {0} Google Music songs".format(
            len(filter_songs)))
        self.print_("Loaded {0} Google Music songs\n".format(
            len(google_songs)))

        return google_songs

    @gm_utils.accept_singleton(basestring)
    def download(self, songs, template):
        """Download songs from Google Music."""

        songnum = 0
        total = len(songs)
        errors = {}
        pad = len(str(total))

        for song in songs:
            songnum += 1

            try:
                self.print_("Downloading  {0} by {1}".format(
                    song['title'], song['artist']),
                            end="\r")
                sys.stdout.flush()
                suggested_filename, audio = self.api.download_song(song['id'])
            except CallFailure as e:
                self.print_(
                    "({num:>{pad}}/{total}) Failed to download  {file} | {error}"
                    .format(num=songnum,
                            total=total,
                            file=suggested_filename,
                            error=e,
                            pad=pad))
                errors[suggested_filename] = e
            else:
                with tempfile.NamedTemporaryFile(delete=False) as temp:
                    temp.write(audio)

                metadata = mutagen.File(temp.name, easy=True)

                if template != os.getcwd():
                    filename = template_to_file_name(template,
                                                     suggested_filename,
                                                     metadata)
                else:
                    filename = suggested_filename

                shutil.move(temp.name, filename)

                self.print_(
                    "({num:>{pad}}/{total}) Successfully downloaded  {file}".
                    format(num=songnum, total=total, file=filename, pad=pad))

        if errors:
            self.print_("\n\nThe following errors occurred:\n")
            for filename, e in errors.iteritems():
                self.print_("{file} | {error}".format(file=filename, error=e))
            self.print_("\nThese files may need to be synced again.\n")

    @gm_utils.accept_singleton(basestring)
    def upload(self, files, enable_matching=False):
        """Upload files to Google Music."""

        filenum = 0
        total = len(files)
        errors = {}
        pad = len(str(total))

        for file in files:
            filenum += 1

            try:
                self.print_("Uploading  {0}".format(file), end="\r")
                sys.stdout.flush()
                uploaded, matched, not_uploaded = self.api.upload(
                    file,
                    transcode_quality="320k",
                    enable_matching=enable_matching)
            except CallFailure as e:
                self.print_(
                    "({num:>{pad}}/{total}) Failed to upload  {file} | {error}"
                    .format(num=filenum,
                            total=total,
                            file=file,
                            error=e,
                            pad=pad))
                errors[file] = e
            else:
                if uploaded:
                    self.print_(
                        "({num:>{pad}}/{total}) Successfully uploaded  {file}".
                        format(num=filenum, total=total, file=file, pad=pad))
                elif matched:
                    self.print_(
                        "({num:>{pad}}/{total}) Successfully scanned and matched  {file}"
                        .format(num=filenum, total=total, file=file, pad=pad))
                else:
                    check_strings = [
                        "ALREADY_EXISTS", "this song is already uploaded"
                    ]
                    if any(check_string in not_uploaded[file]
                           for check_string in check_strings):
                        response = "ALREADY EXISTS"
                    else:
                        response = not_uploaded[file]
                    self.print_(
                        "({num:>{pad}}/{total}) Failed to upload  {file} | {response}"
                        .format(num=filenum,
                                total=total,
                                file=file,
                                response=response,
                                pad=pad))

        if errors:
            self.print_("\n\nThe following errors occurred:\n")
            for file, e in errors.iteritems():
                self.print_("{file} | {error}".format(file=file, error=e))
            self.print_("\nThese files may need to be synced again.\n")