Exemplo n.º 1
0
			def create_playlist_file (name, songs, outpath):
				filename = os.path.join(outpath, name + '.m3u')

				if not cli['dry-run']:
					m3u = [u'#EXTM3U']
					for track in songs:
						id = songid(track)
						song = songs_dict[id]
						artist = song['artist']
						title = song['title']
						duration = str(int(int(song['durationMillis']) / 1000)) if 'durationMillis' in song else '0'
						metadata = metadata_from_mobile_client_song(song)
						songpath = template_to_filepath(cli['output'], metadata) + '.mp3'
						m3u.append(u'#EXTINF,' + duration + ',' + song['artist'] + ' - ' + song['title'])
						m3u.append(os.path.relpath(songpath, outpath))
						# track this song as a local song to keep
						add_to_local_songs(songpath)
					# write m3u file
					contentstr = u'\n'.join(m3u)
					# write to temp file
					with tempfile.NamedTemporaryFile(suffix='.m3u', delete=False) as temp:
						temp.write(contentstr.encode('UTF-8-SIG'))
					# move tempfile into place
					utils.make_sure_path_exists(os.path.dirname(filename), 0o700)
					shutil.move(temp.name, filename)

				logger.log(QUIET, "Playlist ({0} tracks): {1}".format(len(songs), filename))
				return filename
Exemplo n.º 2
0
    def _oauth_login(self, oauth_credentials):
        """Auth ourselves to the MM oauth endpoint.

        Return True on success; see :py:func:`login` for params.
        """

        if isinstance(oauth_credentials, basestring):
            oauth_file = oauth_credentials
            if oauth_file == OAUTH_FILEPATH:
                utils.make_sure_path_exists(os.path.dirname(OAUTH_FILEPATH),
                                            0o700)
            storage = oauth2client.file.Storage(oauth_file)

            oauth_credentials = storage.get()
            if oauth_credentials is None:
                self.logger.warning(
                    "could not retrieve oauth credentials from '%s'",
                    oauth_file)
                return False

        if not self.session.login(oauth_credentials):
            self.logger.warning("failed to authenticate")
            return False

        self.logger.info("oauth successful")

        return True
Exemplo n.º 3
0
def login_mobile_client_from_cache(mcw, oauth_filename='oauth'):
	# mcw is MobileClientWrapper
	creds_filepath = os.path.join(os.path.dirname(OAUTH_FILEPATH), 'mc' + oauth_filename + '.cred')
	# fetch credentials from file
	try:
		logger.info("Trying stored credentials to log in to Mobile Client")
		with open(creds_filepath) as creds_file:
			creds = json.load(creds_file)
		logger.info(creds);
		# fake login
		mcw.api.session._master_token = creds['masterToken']
		mcw.api.session._authtoken = creds['authToken']
		mcw.api.android_id = creds['androidId']
		mcw.api.session.is_authenticated = True
	except:
		logger.info("Unable to load credentials file...")

	try:
		# test fake login with get_devices call
		devices = mcw.api.get_registered_devices()
		# logger.info(devices);
	except:
		creds = login_mobile_client(mcw);

	utils.make_sure_path_exists(os.path.dirname(creds_filepath), 0o700)
	with open(creds_filepath, 'w') as outfile:
		json.dump(creds, outfile)
	
	logger.info("Stored Mobile Client credentials")
Exemplo n.º 4
0
 def set_oauth_code(self, oauth_code, storage_filepath):
     credentials = Musicmanager.flow.step2_exchange(oauth_code)
     if storage_filepath is not None:
         if storage_filepath == OAUTH_FILEPATH:
             utils.make_sure_path_exists(os.path.dirname(OAUTH_FILEPATH), 0o700)
         storage = oauth2client.file.Storage(storage_filepath)
         storage.put(credentials)
Exemplo n.º 5
0
    def perform_oauth(storage_filepath=OAUTH_FILEPATH, open_browser=False):
        """Provides a series of prompts for a user to follow to authenticate.
        Returns ``oauth2client.client.OAuth2Credentials`` when successful.

        In most cases, this should only be run once per machine to store
        credentials to disk, then never be needed again.

        If the user refuses to give access,
        ``oauth2client.client.FlowExchangeError`` is raised.

        :param storage_filepath: a filepath to write the credentials to,
          or ``None``
          to not write the credentials to disk (which is not recommended).

          `Appdirs <https://pypi.python.org/pypi/appdirs/1.2.0>`__
          ``user_data_dir`` is used by default. Users can run::

              import gmusicapi.clients
              print gmusicapi.clients.OAUTH_FILEPATH

          to see the exact location on their system.

        :param open_browser: if True, attempt to open the auth url
          in the system default web browser. The url will be printed
          regardless of this param's setting.

        This flow is intentionally very simple.
        For complete control over the OAuth flow, pass an
        ``oauth2client.client.OAuth2Credentials``
        to :func:`login` instead.
        """

        flow = OAuth2WebServerFlow(*musicmanager.oauth)

        auth_uri = flow.step1_get_authorize_url()
        print
        print "Visit the following url:\n %s" % auth_uri

        if open_browser:
            print
            print 'Opening your browser to it now...',
            webbrowser.open(auth_uri)
            print 'done.'
            print "If you don't see your browser, you can just copy and paste the url."
            print

        code = raw_input("Follow the prompts,"
                         " then paste the auth code here and hit enter: ")

        credentials = flow.step2_exchange(code)

        if storage_filepath is not None:
            if storage_filepath == OAUTH_FILEPATH:
                utils.make_sure_path_exists(os.path.dirname(OAUTH_FILEPATH),
                                            0o700)
            storage = oauth2client.file.Storage(storage_filepath)
            storage.put(credentials)

        return credentials
Exemplo n.º 6
0
    def perform_oauth(storage_filepath=OAUTH_FILEPATH, open_browser=False):
        """Provides a series of prompts for a user to follow to authenticate.
        Returns ``oauth2client.client.OAuth2Credentials`` when successful.

        In most cases, this should only be run once per machine to store
        credentials to disk, then never be needed again.

        If the user refuses to give access,
        ``oauth2client.client.FlowExchangeError`` is raised.

        :param storage_filepath: a filepath to write the credentials to,
          or ``None``
          to not write the credentials to disk (which is not recommended).

          `Appdirs <https://pypi.python.org/pypi/appdirs>`__
          ``user_data_dir`` is used by default. Users can run::

              import gmusicapi.clients
              print gmusicapi.clients.OAUTH_FILEPATH

          to see the exact location on their system.

        :param open_browser: if True, attempt to open the auth url
          in the system default web browser. The url will be printed
          regardless of this param's setting.

        This flow is intentionally very simple.
        For complete control over the OAuth flow, pass an
        ``oauth2client.client.OAuth2Credentials``
        to :func:`login` instead.
        """

        flow = OAuth2WebServerFlow(*musicmanager.oauth)

        auth_uri = flow.step1_get_authorize_url()
        print
        print "Visit the following url:\n %s" % auth_uri

        if open_browser:
            print
            print 'Opening your browser to it now...',
            webbrowser.open(auth_uri)
            print 'done.'
            print "If you don't see your browser, you can just copy and paste the url."
            print

        code = raw_input("Follow the prompts,"
                         " then paste the auth code here and hit enter: ")

        credentials = flow.step2_exchange(code)

        if storage_filepath is not None:
            if storage_filepath == OAUTH_FILEPATH:
                utils.make_sure_path_exists(os.path.dirname(OAUTH_FILEPATH), 0o700)
            storage = oauth2client.file.Storage(storage_filepath)
            storage.put(credentials)

        return credentials
Exemplo n.º 7
0
    def get_auth_token (self, code, storage_filepath=OAUTH_FILEPATH):
        credentials = self.__flow.step2_exchange(code)

        if storage_filepath is not None:
            if storage_filepath == OAUTH_FILEPATH:
                utils.make_sure_path_exists(os.path.dirname(OAUTH_FILEPATH), 0o700)
            storage = oauth2client.file.Storage(storage_filepath)
            storage.put(credentials)

        return credentials
Exemplo n.º 8
0
    def _oauth_login(self, oauth_credentials):
        """Return True on success."""

        if isinstance(oauth_credentials, basestring):
            oauth_file = oauth_credentials
            if oauth_file == self.OAUTH_FILEPATH:
                utils.make_sure_path_exists(os.path.dirname(self.OAUTH_FILEPATH), 0o700)
            storage = oauth2client.file.Storage(oauth_file)

            oauth_credentials = storage.get()
            if oauth_credentials is None:
                self.logger.warning("could not retrieve oauth credentials from '%r'", oauth_file)
                return False

        if not self.session.login(oauth_credentials):
            self.logger.warning("failed to authenticate")
            return False

        self.logger.info("oauth successful")

        return True
Exemplo n.º 9
0
    def _oauth_login(self, oauth_credentials):
        """Auth ourselves to the MM oauth endpoint.

        Return True on success; see :py:func:`login` for params.
        """

        if isinstance(oauth_credentials, str):
            oauth_file = oauth_credentials
            if oauth_file == OAUTH_FILEPATH:
                utils.make_sure_path_exists(os.path.dirname(OAUTH_FILEPATH), 0o700)
            storage = oauth2client.file.Storage(oauth_file)

            oauth_credentials = storage.get()
            if oauth_credentials is None:
                self.logger.warning("could not retrieve oauth credentials from '%s'", oauth_file)
                return False

        if not self.session.login(oauth_credentials):
            self.logger.warning("failed to authenticate")
            return False

        self.logger.info("oauth successful")

        return True
Exemplo n.º 10
0
    def _oauth_login(self, oauth_credentials):
        """Return True on success."""

        if isinstance(oauth_credentials, str):
            oauth_file = oauth_credentials
            if oauth_file == self.OAUTH_FILEPATH:
                utils.make_sure_path_exists(
                    os.path.dirname(self.OAUTH_FILEPATH), 0o700)
            storage = oauth2client.file.Storage(oauth_file)

            oauth_credentials = storage.get()
            if oauth_credentials is None:
                self.logger.warning(
                    "could not retrieve oauth credentials from '%r'",
                    oauth_file)
                return False

        if not self.session.login(oauth_credentials):
            self.logger.warning("failed to authenticate")
            return False

        self.logger.info("oauth successful")

        return True
Exemplo n.º 11
0
import httplib2  # included with oauth2client
from oauth2client.client import OAuth2WebServerFlow, TokenRevokeError
import oauth2client.file

import gmusicapi
from gmusicapi.gmtools import tools
from gmusicapi.exceptions import CallFailure, NotLoggedIn
from gmusicapi.protocol import webclient, musicmanager, upload_pb2, locker_pb2
from gmusicapi.utils import utils
import gmusicapi.session

OAUTH_FILEPATH = os.path.join(utils.my_appdirs.user_data_dir, 'oauth.cred')

# oauth client breaks if the dir doesn't exist
utils.make_sure_path_exists(os.path.dirname(OAUTH_FILEPATH), 0o700)


class _Base(object):
    """Factors out common client setup."""

    __metaclass__ = utils.DocstringInheritMeta

    num_clients = 0  # used to disambiguate loggers

    def __init__(self, logger_basename, debug_logging):
        """

        :param debug_logging: each Client has a ``logger`` member.
          The logger is named ``gmusicapi.<client class><client number>`` and
          will propogate to the ``gmusicapi`` root logger.
Exemplo n.º 12
0
def main():
	cli = dict((key.lstrip("-<").rstrip(">"), value) for key, value in docopt(__doc__).items())

	if cli['no-recursion']:
		cli['max-depth'] = 0
	else:
		cli['max-depth'] = int(cli['max-depth']) if cli['max-depth'] else float('inf')

	if cli['quiet']:
		logger.setLevel(QUIET)
	else:
		logger.setLevel(logging.INFO)

	if not cli['input']:
		cli['input'] = [os.getcwd()]

	if not cli['output']:
		cli['output'] = os.getcwd()

	include_filters = [tuple(filt.split(':', 1)) for filt in cli['include-filter']]
	exclude_filters = [tuple(filt.split(':', 1)) for filt in cli['exclude-filter']]

	mmw = MusicManagerWrapper(enable_logging=cli['log'])
	mmw.login(oauth_filename=cli['cred'], uploader_id=cli['uploader-id'])

	if not mmw.is_authenticated:
		sys.exit()

	mcw = MobileClientWrapper(enable_logging=cli['log'])

	if cli['playlists']:
		login_mobile_client_from_cache(mcw, oauth_filename=cli['cred'])

		if not mcw.is_authenticated:
			sys.exit()

	if cli['down']:
		matched_google_songs, filtered_google_songs = mmw.get_google_songs(
			include_filters=include_filters, exclude_filters=exclude_filters,
			all_includes=cli['all-includes'], all_excludes=cli['all-excludes']
		)

		cli['input'] = [template_to_base_path(cli['output'], matched_google_songs)]

		matched_local_songs, filtered_local_songs, excluded_local_songs = mmw.get_local_songs(cli['input'], exclude_patterns=cli['exclude'])

		# keep track of all local songs
		all_local_songs_dict = dict()

		def add_to_local_songs (filepath):
			all_local_songs_dict[os.path.abspath(filepath)] = True

		def all_local_songs ():
			return list(all_local_songs_dict.keys())

		for filepath in matched_local_songs + filtered_local_songs + excluded_local_songs:
			metadata = _get_mutagen_metadata(filepath);
			songpath = template_to_filepath(cli['output'], metadata) + '.mp3'
			if filepath != songpath:
				utils.make_sure_path_exists(os.path.dirname(songpath), 0o700)
				print("{0} ~> {1}".format(filepath, songpath))
				shutil.move(filepath, songpath)

			add_to_local_songs(songpath)
			# add_to_local_songs(filepath)


		# for filepath in matched_local_songs + filtered_local_songs + excluded_local_songs:
		# 	add_to_local_songs(filepath)


		def download_songs(songs_to_download):
			if songs_to_download:
				if cli['dry-run']:
					logger.info("\nFound {0} song(s) to download".format(len(songs_to_download)))
					for song in songs_to_download:
						title = song.get('title', "<title>")
						artist = song.get('artist', "<artist>")
						album = song.get('album', "<album>")
						song_id = song['id']
						metadata = metadata_from_mobile_client_song(song)
						songpath = template_to_filepath(cli['output'], metadata) + '.mp3'
						logger.log(QUIET, "{0} -- {1} -- {2} ({3})".format(title, artist, album, song_id))
						add_to_local_songs(songpath)
				else:
					logger.info("\nDownloading {0} song(s) from Google Music\n".format(len(songs_to_download)))
					results = mmw.download(songs_to_download, template=cli['output'])
					# keep track of new filepaths
					for res in results:
						if res['result'] == 'downloaded':
							add_to_local_songs(res['filepath'])
			else:
				logger.info("\nNo songs to download")

		def download_missing_google_songs(mmw_songs):
			# recheck the local songs after any previous sync
			logger.info("\nFinding missing songs...")
			songs_to_download = compare_song_collections(mmw_songs, all_local_songs())
			songs_to_download.sort(key=lambda song: (song.get('artist'), song.get('album'), song.get('track_number')))
			return download_songs(songs_to_download)

		logger.info("\nFetching Library songs...")
		download_missing_google_songs(matched_google_songs)

		if cli['playlists']:
			logger.info("Syncing playlists...")
			# get all songs from mobileClient api (to include ratings)
			all_songs = mcw.api.get_all_songs()
			# get id, prioritize trackId over id
			def songid (track):
				return track['trackId'] if 'trackId' in track else track['id']
			# create a dictionary of all fetched google songs, indexed by id
			def songs_to_dict (songs, song):
				id = songid(song)
				songs[id] = song
				return songs
			# create lookup dicts for tracks (mmw) and songs (mcw)
			songs_dict = reduce(songs_to_dict, all_songs, {})
			tracks_dict = reduce(songs_to_dict, matched_google_songs + filtered_google_songs, {})

			# returns music manager wrapper tracks for list of objects with song id or trackId
			#  also removes duplicates
			def get_mmw_tracks (songs):
				tracks = []
				seen = {}
				for song in songs:
					id = songid(song)
					if id not in seen:
						tracks.append(tracks_dict[id])
						seen[id] = True
				return tracks;

			# path to save playlists
			playlists_dir = os.path.abspath(cli['playlists'])
			# ensure directory is there
			utils.make_sure_path_exists(playlists_dir, 0o700)

			def create_playlist_file (name, songs, outpath):
				filename = os.path.join(outpath, name + '.m3u')

				if not cli['dry-run']:
					m3u = [u'#EXTM3U']
					for track in songs:
						id = songid(track)
						song = songs_dict[id]
						artist = song['artist']
						title = song['title']
						duration = str(int(int(song['durationMillis']) / 1000)) if 'durationMillis' in song else '0'
						metadata = metadata_from_mobile_client_song(song)
						songpath = template_to_filepath(cli['output'], metadata) + '.mp3'
						m3u.append(u'#EXTINF,' + duration + ',' + song['artist'] + ' - ' + song['title'])
						m3u.append(os.path.relpath(songpath, outpath))
						# track this song as a local song to keep
						add_to_local_songs(songpath)
					# write m3u file
					contentstr = u'\n'.join(m3u)
					# write to temp file
					with tempfile.NamedTemporaryFile(suffix='.m3u', delete=False) as temp:
						temp.write(contentstr.encode('UTF-8-SIG'))
					# move tempfile into place
					utils.make_sure_path_exists(os.path.dirname(filename), 0o700)
					shutil.move(temp.name, filename)

				logger.log(QUIET, "Playlist ({0} tracks): {1}".format(len(songs), filename))
				return filename


			# get playlists with ordered lists of tracks
			playlists = mcw.api.get_all_user_playlist_contents()
			# concatenate all the playlist tracks into a single list
			playlist_tracks = reduce(lambda x, y: x + y, map(lambda x: x['tracks'], playlists)) if len(playlists) else []
			# remove duplicates and get mmw tracks to download
			playlist_tracks = get_mmw_tracks(playlist_tracks)
			# download any missing songs
			logger.info("\nFetching Playlist songs...")
			download_missing_google_songs(playlist_tracks)
			# create the m3u files for the playlists
			created_playlists = [];
			for playlist in playlists:
				playlist_filename = create_playlist_file(playlist['name'], playlist['tracks'], playlists_dir)
				created_playlists.append(playlist_filename)

			# create an m3u file for all favorited songs
			favorites_playlist_name = cli['favorites'] if 'favorites' in cli else '___auto_favorites___'
			# filter mobile client songs into favorites list
			thumbs_up = [t for t in all_songs if int(t['rating']) > 3]
			# most recent first
			thumbs_up.sort(key=lambda song: (int(song.get('lastModifiedTimestamp')) * -1))
			# get music_manager_wrapper style tracks to compare
			thumbs_up_tracks = get_mmw_tracks(thumbs_up)
			# download any missing favorited songs
			logger.info("\nFetching Favorited songs...")
			download_missing_google_songs(thumbs_up_tracks)
			# create favorites playlist
			created_playlist_filename = create_playlist_file(favorites_playlist_name, thumbs_up, playlists_dir)
			created_playlists.append(created_playlist_filename)


		if cli['removed']:
			logger.info("Moving Removed songs...")
			# path to move songs removed from google music
			removed_dir = os.path.abspath(cli['removed'])
			# ensure directory is there
			utils.make_sure_path_exists(removed_dir, 0o700)
			# local songs after sync
			all_google_songs = matched_google_songs + filtered_google_songs
			songs_to_move = compare_song_collections(all_local_songs(), all_google_songs)

			for filepath in songs_to_move:
				rel_file_path = os.path.relpath(filepath, cli['input'][0])
				removed_filepath = os.path.join(removed_dir, rel_file_path)
				utils.make_sure_path_exists(os.path.dirname(removed_filepath), 0o700)
				logger.info("Removing {0} ~> {1}".format(filepath, removed_filepath))
				if not cli['dry-run']:
					shutil.move(filepath, removed_filepath)

			# clean up empty folders
			logger.info(" ")
			if not cli['dry-run']:
				removeEmptyFolders(cli['input'][0], False)

			if cli['playlists']:
				logger.info("Moving Removed playlists...")
				# path to move songs removed from google music
				removed_playlists_dir = os.path.join(os.path.abspath(cli['removed']), cli['playlists'])
				# local playlists after sync
				local_playlists = [os.path.join(dp, f) for dp, dn, fn in os.walk(playlists_dir) for f in fn]
				playlists_to_remove = list(set(local_playlists) - set(created_playlists))

				for filepath in playlists_to_remove:
					rel_file_path = os.path.relpath(filepath, playlists_dir)
					removed_filepath = os.path.join(removed_playlists_dir, rel_file_path)
					utils.make_sure_path_exists(os.path.dirname(removed_filepath), 0o700)
					logger.info("Removing {0}".format(rel_file_path))
					if not cli['dry-run']:
						shutil.move(filepath, removed_filepath)

				# clean up empty folders
				logger.info(" ")
				if not cli['dry-run']:
					removeEmptyFolders(playlists_dir, False)

	else:
		matched_google_songs, _ = mmw.get_google_songs()

		logger.info("")

		matched_local_songs, songs_to_filter, songs_to_exclude = mmw.get_local_songs(
			cli['input'], include_filters=include_filters, exclude_filters=exclude_filters,
			all_includes=cli['all-includes'], all_excludes=cli['all-excludes'],
			exclude_patterns=cli['exclude'], max_depth=cli['max-depth']
		)

		logger.info("\nFinding missing songs...")

		songs_to_upload = compare_song_collections(matched_local_songs, matched_google_songs)

		# Sort lists for sensible output.
		songs_to_upload.sort()
		songs_to_exclude.sort()

		if cli['dry-run']:
			logger.info("\nFound {0} song(s) to upload".format(len(songs_to_upload)))

			if songs_to_upload:
				logger.info("\nSongs to upload:\n")

				for song in songs_to_upload:
					logger.log(QUIET, song)
			else:
				logger.info("\nNo songs to upload")

			if songs_to_filter:
				logger.info("\nSongs to filter:\n")

				for song in songs_to_filter:
					logger.log(QUIET, song)
			else:
				logger.info("\nNo songs to filter")

			if songs_to_exclude:
				logger.info("\nSongs to exclude:\n")

				for song in songs_to_exclude:
					logger.log(QUIET, song)
			else:
				logger.info("\nNo songs to exclude")
		else:
			if songs_to_upload:
				logger.info("\nUploading {0} song(s) to Google Music\n".format(len(songs_to_upload)))

				mmw.upload(songs_to_upload, enable_matching=cli['match'], delete_on_success=cli['delete-on-success'])
			else:
				logger.info("\nNo songs to upload")

				# Delete local files if they already exist on Google Music.
				if cli['delete-on-success']:
					for song in matched_local_songs:
						try:
							os.remove(song)
						except:
							logger.warning("Failed to remove {} after successful upload".format(song))

	mmw.logout()
	mcw.logout()
	logger.info("\nAll done!")