示例#1
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())
示例#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())
示例#3
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()
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)
示例#5
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")
示例#6
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")