Example #1
0
def prompt_for_mc_auth():
    """Return a valid (user, pass, android_id) tuple by continually
    prompting the user."""

    print("These tests will never delete or modify your music."
          "\n\n"
          "If the tests fail, you *might* end up with a test"
          " song/playlist in your library, though."
          "\n")

    mclient = Mobileclient()
    valid_mc_auth = False

    while not valid_mc_auth:
        print()
        email = input("Email: ")
        passwd = getpass()

        try:
            android_id = os.environ['GM_AA_D_ID']
        except KeyError:
            android_id = input("Device ID ('mac' for FROM_MAC_ADDRESS): ")

        if android_id == "mac":
            android_id = Mobileclient.FROM_MAC_ADDRESS

        if not android_id:
            print('a device id must be provided')
            sys.exit(1)

        valid_mc_auth = mclient.login(email, passwd, android_id)

    return email, passwd, android_id
Example #2
0
def prompt_for_mc_auth():
    """Return a valid (user, pass, android_id) tuple by continually
    prompting the user."""

    print("These tests will never delete or modify your music."
          "\n\n"
          "If the tests fail, you *might* end up with a test"
          " song/playlist in your library, though."
          "\n")

    mclient = Mobileclient()
    valid_mc_auth = False

    while not valid_mc_auth:
        print()
        email = input("Email: ")
        passwd = getpass()

        try:
            android_id = os.environ['GM_AA_D_ID']
        except KeyError:
            android_id = input("Device ID ('mac' for FROM_MAC_ADDRESS): ")

        if android_id == "mac":
            android_id = Mobileclient.FROM_MAC_ADDRESS

        if not android_id:
            print('a device id must be provided')
            sys.exit(1)

        valid_mc_auth = mclient.login(email, passwd, android_id)

    return email, passwd, android_id
Example #3
0
 def create_api_client(self):
     client = Mobileclient()
     success = client.login(config.EMAIL, config.PASSWORD,
                            Mobileclient.FROM_MAC_ADDRESS)
     if not success:
         raise Exception("Failed to login API client")
     print("Logged in API client")
     return client
Example #4
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 #5
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 #6
0
 def login(self):
     # log in to google music suppressing it's debugging messages
     oldloglevel = logging.getLogger().level
     logging.getLogger().setLevel(logging.ERROR)
     self.api = Mobileclient(debug_logging=False)
     logging.getLogger("gmusicapi.Mobileclient1").setLevel(logging.WARNING)
     rv = self.api.oauth_login(self.gmusic_client_id)
     logging.getLogger().setLevel(oldloglevel)
     if not rv:
         raise ExBpmCrawlGeneric(
             f"Please login to Google Music first (run gmusic-login.py)")
Example #7
0
    def init_client(self):
        for cred in self.cred_dir.glob("*.auth"):
            cli = Mobileclient()
            if cli.oauth_login(Mobileclient.FROM_MAC_ADDRESS, str(cred)):
                self.gpm[cred.stem] = cli
                log.info(f"Authorized: {cred.stem}")
            else:
                log.error(f"Failed to authorize: {cred.stem}")

        # get subscribed user
        for name, cli in self.gpm.items():
            if cli.is_subscribed:
                log.info(f'Subscribed account: {name}')
                self.subscribed = cli
Example #8
0
    def __init__(self):
        assert self.__class__.instance is None, 'Can be created only once!'
        self.is_debug = os.getenv('CLAY_DEBUG')
        self.mobile_client = Mobileclient()
        if self.is_debug:
            self.mobile_client._make_call = self._make_call_proxy(
                self.mobile_client._make_call)
            self.debug_file = open('/tmp/clay-api-log.json', 'w')
            self._last_call_index = 0
        self.cached_tracks = None
        self.cached_playlists = None

        self.invalidate_caches()

        self.auth_state_changed = EventHook()
Example #9
0
File: gp.py Project: vale981/clay
    def __init__(self):
        # self.is_debug = os.getenv('CLAY_DEBUG')
        self.mobile_client = Mobileclient()
        self.mobile_client._make_call = self._make_call_proxy(
            self.mobile_client._make_call)
        # if self.is_debug:
        #     self.debug_file = open('/tmp/clay-api-log.json', 'w')
        #     self._last_call_index = 0
        self.cached_tracks = None
        self.cached_liked_songs = LikedSongs()
        self.cached_playlists = None
        self.cached_stations = None

        self.invalidate_caches()

        self.auth_state_changed = EventHook()
	def __init__(self, log=False):
		"""

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

		self.api = Mobileclient(debug_logging=log)
		self.api.logger.addHandler(logging.NullHandler())
Example #11
0
def exportlib(user, password):
    """Logs into Google Music and exports the user's
    library to a file called 'export.json'.

    """
    keys = ['comment', 'rating', 'composer', 'year', 'album',
            'albumArtist', 'title', 'totalDiscCount', 'trackNumber',
            'discNumber', 'totalTrackCount', 'estimatedSize',
            'beatsPerMinute', 'genre', 'playCount', 'artist',
            'durationMillis']

    client = Mobileclient()
    client.login(user, password)
    with open('export.json', 'w+') as out:
        for songs in client.get_all_songs(incremental=True):
            for song in songs:
                pruned = select_keys(song, keys)
                print(json.dumps(pruned), file=out)
Example #12
0
class MobileClientWrapper(_Base):
	def __init__(self, log=False, quiet=False):
		self.api = Mobileclient(debug_logging=log)
		self.api.logger.addHandler(logging.NullHandler())

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

	def login(self, *args):
		"""Authenticate the gmusicapi Mobileclient instance."""

		if len(args) == 2:
			username, password = args[0], args[1]
		elif len(args) == 1:
			username, password = args[0], None
		else:
			username, password = None, None

		if not self.api.login(username, password):
			if not username:
				username = raw_input("Enter your Google username or email address: ")

			if not password:
				password = getpass.getpass(b"Enter your Google Music password: "******"Sorry, login failed.")

			return False

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

		return True

	def logout(self):
		"""Log out the gmusicapi Mobileclient instance."""

		self.api.logout()

	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_all_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
Example #13
0
class GMusic(object):
    def __init__(self, user, password):
        self.client = Mobileclient()
        self.client.login(user, password, Mobileclient.FROM_MAC_ADDRESS)

    def genTracks(self):
        for chunk in self.client.get_all_songs(incremental=True):
            for track in chunk:
                yield track

    def findTrack(self, rdio_track, keys=('artist', 'album', 'name',)):
        if not keys:
            return

        results = self.client.search_all_access(' '.join(rdio_track[k] for k in keys))['song_hits']
        if not results:
            return self.findTrack(rdio_track, keys[1:])

        # FIXME: is the best match always first?
        best_match = results[0]
        return best_match['track']

    def addTrack(self, google_track):
        self.client.add_aa_track(google_track['nid'])
Example #14
0
class MobileClientWrapper(_Base):
    def __init__(self, log=False, quiet=False):
        self.api = Mobileclient(debug_logging=log)
        self.api.logger.addHandler(logging.NullHandler())

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

    def login(self, *args):
        """Authenticate the gmusicapi Mobileclient instance."""

        if len(args) == 2:
            username, password = args[0], args[1]
        elif len(args) == 1:
            username, password = args[0], None
        else:
            username, password = None, None

        if not self.api.login(username, password):
            if not username:
                username = raw_input(
                    "Enter your Google username or email address: ")

            if not password:
                password = getpass.getpass(
                    b"Enter your Google Music password: "******"Sorry, login failed.")

            return False

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

        return True

    def logout(self):
        """Log out the gmusicapi Mobileclient instance."""

        self.api.logout()

    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_all_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
Example #15
0
    def __init__(self, log=False, quiet=False):
        self.api = Mobileclient(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 _GP(object):
    """
    Interface to :class:`gmusicapi.Mobileclient`. Implements
    asynchronous API calls, caching and some other perks.

    Singleton.
    """
    # TODO: Switch to urwid signals for more explicitness?
    caches_invalidated = EventHook()
    parsed_songs = EventHook()

    def __init__(self):
        # self.is_debug = os.getenv('CLAY_DEBUG')
        self.mobile_client = Mobileclient()
        self.mobile_client._make_call = self._make_call_proxy(
            self.mobile_client._make_call)
        # if self.is_debug:
        #     self.debug_file = open('/tmp/clay-api-log.json', 'w')
        #     self._last_call_index = 0
        self.cached_tracks = None
        self.cached_playlists = None
        self.cached_stations = None
        self.cached_artists = {}
        self.cached_albums = {}
        self.liked_songs = LikedSongs()

        self.invalidate_caches()

        self.auth_state_changed = EventHook()

    def _make_call_proxy(self, func):
        """
        Return a function that wraps *fn* and logs args & return values.
        """
        def _make_call(protocol, *args, **kwargs):
            """
            Wrapper function.
            """
            logger.debug('GP::{}(*{}, **{})'.format(protocol.__name__, args,
                                                    kwargs))
            result = func(protocol, *args, **kwargs)
            # self._last_call_index += 1
            # call_index = self._last_call_index
            # self.debug_file.write(json.dumps([
            #     call_index,
            #     protocol.__name__, args, kwargs,
            #     result
            # ]) + '\n')
            # self.debug_file.flush()
            return result

        return _make_call

    def invalidate_caches(self):
        """
        Clear cached tracks & playlists & stations.
        """
        self.cached_tracks = None
        self.cached_playlists = None
        self.cached_stations = None
        self.cached_artist = None
        self.caches_invalidated.fire()

    @synchronized
    def login(self, email, password, device_id, **_):
        """
        Log in into Google Play Music.
        """
        self.mobile_client.logout()
        self.invalidate_caches()
        from os.path import exists
        CRED_FILE = "/home/thor/.config/clay/google_auth.cred"
        if not exists(CRED_FILE):
            from oauth2client.client import FlowExchangeError
            try:
                self.mobile_client.perform_oauth(CRED_FILE, open_browser=True)
            except FlowExchangeError:
                raise RuntimeError("OAuth authentication failed, try again")
        result = self.mobile_client.oauth_login(
            self.mobile_client.FROM_MAC_ADDRESS, CRED_FILE)
        self.auth_state_changed.fire(self.is_authenticated)
        return result

    login_async = asynchronous(login)

    @synchronized
    def get_artist_info(self, artist_id):
        """
        Get the artist info
        """
        return self.mobile_client.get_artist_info(artist_id,
                                                  max_rel_artist=0,
                                                  max_top_tracks=15)

    @synchronized
    def get_album_tracks(self, album_id):
        """
        Get album tracks
        """
        return self.mobile_client.get_album_info(album_id,
                                                 include_tracks=True)['tracks']

    @synchronized
    def add_album_song(self, id_, album_name, track):
        """
        Adds an album to an artist and adds the specified track to it

        Args:
            id_ (`str`): the album ID (currently the same as the album title)
            album_name (`str`): the name of the album
            track (`clay.gp.Track`): the track in the album
        """
        if album_name == '':
            id_ = track.artist
            album_name = "Unknown Album"

        if id_ not in self.cached_albums:
            self.cached_albums[id_] = Album(track.album_artist, {
                'albumId': id_,
                'name': album_name
            })

        self.cached_albums[id_].add_track(track)

        return self.cached_albums[id_]

    @synchronized
    def add_artist(self, artist_id, name):
        """
        Creates or lookup an artist object and return it.

        Args:
           artist_id (`str`): The Artist id given by Google Play Music

        Returns:
           The artist class
        """
        name = ("Unknown Artist" if name == '' else name)
        lname = name.lower()
        if lname not in self.cached_artists:
            self.cached_artists[lname] = Artist(artist_id, name)

        return self.cached_artists[lname]

    @synchronized
    def use_authtoken(self, authtoken, device_id):
        """
        Try to use cached token to log into Google Play Music.
        """
        self.mobile_client.session._authtoken = authtoken
        self.mobile_client.session.is_authenticated = True
        self.mobile_client.android_id = device_id
        del self.mobile_client.is_subscribed
        if self.mobile_client.is_subscribed:
            self.auth_state_changed.fire(True)
            return True
        del self.mobile_client.is_subscribed
        self.mobile_client.android_id = None
        self.mobile_client.session.is_authenticated = False
        self.auth_state_changed.fire(False)
        return False

    use_authtoken_async = asynchronous(use_authtoken)

    def get_authtoken(self):
        """
        Return currently active auth token.
        """
        return self.mobile_client.session._authtoken

    @synchronized
    def get_all_tracks(self):
        """
        Cache and return all tracks from "My library".

        Each track will have "id" and "storeId" keys.
        """
        if self.cached_tracks:
            return self.cached_tracks
        data = self.mobile_client.get_all_songs()
        self.cached_tracks = Track.from_data(data, Source.library, True)
        self.parsed_songs.fire()

        return self.cached_tracks

    get_all_tracks_async = asynchronous(get_all_tracks)

    def get_stream_url(self, stream_id):
        """
        Returns playable stream URL of track by id.
        """
        return self.mobile_client.get_stream_url(stream_id)

    get_stream_url_async = asynchronous(get_stream_url)

    def increment_song_playcount(self, track_id):
        """
        increments the playcount of a song with a given `track_id` by one

        Args:
           track_id (`int`): The track id of the song to increment the playcount

        Returns:
           Nothing
        """
        gp.mobile_client.increment_song_playcount(track_id)

    increment_song_playcount_async = asynchronous(increment_song_playcount)

    @synchronized
    def get_all_user_station_contents(self, **_):
        """
              Return list of :class:`.Station` instances.
              """
        if self.cached_stations:
            return self.cached_stations
        self.get_all_tracks()

        self.cached_stations = Station.from_data(
            self.mobile_client.get_all_stations(), True)
        self.cached_stations.insert(0, IFLStation())
        return self.cached_stations

    get_all_user_station_contents_async = (
        asynchronous(get_all_user_station_contents))

    @synchronized
    def get_all_user_playlist_contents(self, **_):
        """
        Return list of :class:`.Playlist` instances.
        """
        if self.cached_playlists:
            return self.cached_playlists

        self.get_all_tracks()

        self.cached_playlists = Playlist.from_data(
            self.mobile_client.get_all_user_playlist_contents(), True)
        self.refresh_liked_songs()
        self.cached_playlists.insert(0, self.liked_songs)
        return self.cached_playlists

    get_all_user_playlist_contents_async = (
        asynchronous(get_all_user_playlist_contents))

    def refresh_liked_songs(self, **_):
        """
        Refresh the liked songs playlist
        """
        self.liked_songs.refresh_tracks(self.mobile_client.get_top_songs())

    refresh_liked_songs_async = asynchronous(refresh_liked_songs)

    def get_cached_tracks_map(self):
        """
        Return a dictionary of tracks where keys are strings with track IDs
        and values are :class:`.Track` instances.
        """
        return {track.id: track for track in self.cached_tracks}

    def get_track_by_id(self, any_id):
        """
        Return track by id or store_id.
        """
        for track in self.cached_tracks:
            if any_id in (track.id_, track.nid, track.store_id):
                return track
        return None

    def search(self, query):
        """
        Find tracks and return an instance of :class:`.SearchResults`.
        """
        results = self.mobile_client.search(query)
        return SearchResults.from_data(results)

    search_async = asynchronous(search)

    def add_to_my_library(self, track):
        """
        Add a track to my library.
        """
        result = self.mobile_client.add_store_tracks(track.id)
        if result:
            self.invalidate_caches()
        return result

    def remove_from_my_library(self, track):
        """
        Remove a track from my library.
        """
        result = self.mobile_client.delete_songs(track.id)
        if result:
            self.invalidate_caches()
        return result

    @property
    def is_authenticated(self):
        """
        Return True if user is authenticated on Google Play Music, false otherwise.
        """
        return self.mobile_client.is_authenticated()

    @property
    def is_subscribed(self):
        """
        Return True if user is subscribed on Google Play Music, false otherwise.
        """
        return self.mobile_client.is_subscribed
Example #17
0
class GoogleClient(object):
    def __init__(self):

        self.api = Mobileclient()

    def _get_playlist_id(self):
        """
        Gets user input for the playlist URL and attempts to parse it
        to get the Playlist ID
        """
        playlist_shared_url = input(
            "Enter the shared URL for the Google playlist:  ")
        try:
            playlist_id = parse.unquote(
                playlist_shared_url.split('playlist/')[1])
        except:
            playlist_id = None
        return playlist_id

    def _search_for_track(self, album, artist, track_name):
        """
        Searches Google for a track matching the provided album, artist, and name
        Returns the track ID if found, else None
        """
        query = track_name + ',' + artist + ',' + album
        result = self.api.search(query)
        song_hits = result['song_hits']
        # TODO this search has gotta get better...
        for h in song_hits:
            if h['track']['title'] == track_name:
                if h['track']['album'] == album or h['track'][
                        'artist'] == artist:
                    return h['track']['storeId']
        # TODO Return the best match if no full match is made
        return None

    def _delete_playlist(self, playlist_id):
        """
        Unfollow a playlist so that it does not appear in your account
        Mostly useful for testing, but maybe this should be used if 
        an exception occurs in the track add process?
        Return playlist ID
        """
        return (self.api.delete_playlist(playlist_id) == playlist_id)

    def authenticate(self):

        email = input("Enter your Google email address:  ")
        password = getpass.getpass(
            "Enter the password for your Google account:  ")
        # TODO store email locally
        return self.api.login(email, password, Mobileclient.FROM_MAC_ADDRESS)

    def get_playlist_tracks(self, playlist_id=None):
        # TODO Get playlist namea s well!!!

        if not playlist_id:
            playlist_id = self._get_playlist_id()

        tracks = self.api.get_shared_playlist_contents(playlist_id)
        tracks = [{
            "track": t['track']['title'],
            "album": t['track']['album'],
            "artist": t['track']['artist']
        } for t in tracks]
        return {"name": None, "tracks": tracks}

    def search_for_tracklist(self, tracklist):
        """
        Searches Google for a provided list of tracks
        Track list should be the format provided by indexing 'tracks' in the 
        dict returned from get_playlist_tracks()
        """
        found, not_found = [], []
        for t in tracklist:
            result = self._search_for_track(album=t['album'],
                                            artist=t['artist'],
                                            track_name=t['track'])
            if result:
                found.append(result)
            else:
                not_found.append(t)

        return {"found": found, "not_found": not_found}

    def create_playlist(self, playlist_name):
        """
        Create a new playlist with the provided name
        "prompt" param is mostly just here for testing
        Return the generated playlist ID
        """
        return self.api.create_playlist(name=playlist_name)

    def add_tracks_to_playlist(self, playlist_id, track_list):
        """
        Add all track in provided track_list to desired playlist
        track_list should be a list of Google track IDs, 
        provided by the 'found' index of search_for_tracks()
        """
        return self.api.add_songs_to_playlist(playlist_id=playlist_id,
                                              song_ids=track_list)
Example #18
0
from pathlib import Path

from gmusicapi.clients import Mobileclient

auth_dir = Path.cwd() / 'config' / 'gpm'
auth_dir.mkdir(parents=True, exist_ok=True)

name = input("Enter name: ")
# sanitize
name = Path(name).name

auth_file = auth_dir / f"{name}.auth"
auth_file.touch()

try:
    Mobileclient.perform_oauth(storage_filepath=str(auth_file),
                               open_browser=True)
    print('Logged in to Google successfully!')
except:
    print('Failed to login. Try again later...')
Example #19
0
import codecs
import os
import subprocess
from django.shortcuts import render
from gmusicapi.clients import Webclient, Mobileclient
import mutagen
from mutagen.easyid3 import EasyID3
import TTR.settings
from django.http import HttpResponse
import yaml
from game.models import DownloadedTrack
import json
from django.core.serializers.json import DjangoJSONEncoder


api = Mobileclient()
webCli = Webclient()
api.login(TTR.settings.UN, TTR.settings.P)
webCli.login(TTR.settings.UN, TTR.settings.P)




# TODO: Will want to separate these files.
# ==============================================================================
# Page Renderings
# 1) Search Page Rendering
# =============================================================================
# def index(request):
#
  src = sys.argv[1]
  dst = sys.argv[2]

  with open(path.join(siteDir, 'lyrics.html')) as f:
    lyricsLines = f.readlines()

  headerLines = find(lyricsLines, '<div id="header">')
  contentLines = find(lyricsLines, '<div id="content">')
  previousLine = find(lyricsLines, PREVIOUS_FILE)
  nextLine = find(lyricsLines, NEXT_FILE)

  links = []
  songs = sorted(listdir(src))

  # Play Music Client
  mc = Mobileclient()
  mc.oauth_login(Mobileclient.FROM_MAC_ADDRESS)

  tracks = get_playlist_tracks(mc, 'Spanish')

  sorted_songs = [None] * len(songs)

  for song in songs:
    order = get_song_order(song, tracks)
    sorted_songs[order] = song

  for (songNumber, song) in enumerate(sorted_songs):
    dir = path.join(src, song)

    with open(path.join(dir, "original.txt")) as f:
      originalLines = f.readlines()
Example #21
0
def downAlbum(search):
    sr = mc.search(search)
    if len(sr['album_hits']) == 0:
        print('no albums found for ' + search)
        return
    album = mc.get_album_info(sr['album_hits'][0]['album']['albumId'])
    query_yes_no('Download album ' + album['name'] + ' by ' + album['artist'] +
                 '?')
    for track in album['tracks']:
        downResource(track)


parser = argparse.ArgumentParser(description='GPM Download Tool')
parser.add_argument('search', type=str, help='search term')
parser.add_argument('-a',
                    '--album',
                    dest='album',
                    action='store_true',
                    help='treat the search term as an album')
args = parser.parse_args()

mc = Mobileclient()
# username, password, deviceid
mc.login('aa', 'aa', 'aa')
print('Logged in')

if args.album:
    downAlbum(args.search)
else:
    downSong(args.search)
Example #22
0
	def __init__(self, log=False, quiet=False):
		self.api = Mobileclient(debug_logging=log)
		self.api.logger.addHandler(logging.NullHandler())

		self.print_ = safe_print if not quiet else lambda *args, **kwargs: None
Example #23
0
#!/usr/bin/env python
#import gmusicapi
from gmusicapi.clients import Mobileclient
from config import *

import logging
from logging import debug, info, warning, error

logging.basicConfig(level=logging.DEBUG)

api = Mobileclient()
if not api.oauth_login(gmusic_client_id):
    print(f"oauth: {api.perform_oauth()}")
else:
    print()
    print(
        f"already logged in (you may remove ~/.local/share/gmusicapi/mobileclient.cred to relogin, or modify this script to handle logoffs more correctly)"
    )
Example #24
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 #25
0
 def login(self):
     self.mc = Mobileclient()
     self._login_mc()
     self.mm = Musicmanager()
     #self.mm.perform_oauth()
     self.mm.login()
Example #26
0
 def __init__(self, user, password):
     self.client = Mobileclient()
     self.client.login(user, password, Mobileclient.FROM_MAC_ADDRESS)
Example #27
0
class MusicProviderGoogle(MusicproviderBase):
    music_service = 'gmusic'
    gmusic_client_id = gmusic_client_id
    api = None
    station_current = None  # current station
    station_recently_played = None  # list of tracks already seen from current station
    station_current_tracks = None  # list of tracks got last time from station
    station_current_unseen = None  # count of tracks in current tracks list that were unseen in this playback session
    station_now_playing = None  # index of 'currently playing' track (points to track returned by last station_get_next_track call)

    def login(self):
        # log in to google music suppressing it's debugging messages
        oldloglevel = logging.getLogger().level
        logging.getLogger().setLevel(logging.ERROR)
        self.api = Mobileclient(debug_logging=False)
        logging.getLogger("gmusicapi.Mobileclient1").setLevel(logging.WARNING)
        rv = self.api.oauth_login(self.gmusic_client_id)
        logging.getLogger().setLevel(oldloglevel)
        if not rv:
            raise ExBpmCrawlGeneric(
                f"Please login to Google Music first (run gmusic-login.py)")

    def get_playlist(self, playlist_id_uri_name):
        if re.match('^https?://', playlist_id_uri_name, re.I):
            # this is uri of shared playlist
            try:
                self.api.get_shared_playlist_contents(
                    urllib.parse.unquote(playlist_id_uri_name))
            except Exception as e:
                debug(f"{self.whoami()}: got exception:", exc_info=True)
                raise ExBpmCrawlGeneric(
                    f"failed to get shared playlist: {e}, enable debug for more"
                )
        # find previously created playlist with specified name
        playlists = self.api.get_all_user_playlist_contents()
        playlist = None
        for pl in playlists:
            if pl["name"] == playlist_id_uri_name:
                playlist = pl
                break
        return playlist

    def get_or_create_my_playlist(self, playlist_name):
        playlist = self.get_playlist(playlist_name)
        if not playlist:
            debug(
                f"{whoami}: playlist {playlist_name} not found, creating it..."
            )
            id = self.api.create_playlist(playlist_name)
            debug(f"{whoami}: created playlist, id {id}")
            playlists = self.api.get_all_playlists()
            for pl in playlists:
                if pl["id"] == id:
                    playlist = pl
                    break
        if not playlist:
            raise ExBpmCrawlGeneric(
                f"Failed to find or create playlist {playlist_name}")
        return playlist

    def get_playlist_tracks(self, playlist):
        # get track ids of playlist
        tracks_in_playlist = []
        if "tracks" in playlist:
            for track in playlist["tracks"]:
                if "track" in track:  # it is google play music's track, not some local crap
                    tracks_in_playlist.append(track["track"])
        return tracks_in_playlist

    def add_track_to_playlist(self, playlist, track_id):
        added = self.api.add_songs_to_playlist(playlist["id"], track_id)
        if len(added):
            return True
        else:
            return False

    def get_station_from_url(self, url):
        if url == "IFL":
            return {"id": "IFL", "name": "I'm Feeling Lucky"}
        station_id_str = urllib.parse.unquote(url).rsplit("/", 1)[-1].split(
            "?", 1)[0].split('#', 1)[0]
        stations = self.api.get_all_stations()
        for station in stations:
            if 'seed' in station and 'curatedStationId' in station['seed']:
                if station['seed']['curatedStationId'] == station_id_str:
                    debug(f"{whoami()}: found station {station['id']}")
                    return station
        raise ExBpmCrawlGeneric(
            f"Failed to find station by string '{station_id_str}' (from url '{url}')"
        )

    def get_station_name(self, station):
        return station['name']

    def station_prepare(self, station):
        self.station_recently_played = []
        self.station_current_tracks = None
        self.station_current_unseen = 0
        self.station_now_playing = None
        self.station_current = station

    def station_get_next_track(self):
        need_new_tracks = False
        if self.station_current_tracks is None:
            need_new_tracks = True
        else:
            while not need_new_tracks:
                self.station_now_playing += 1
                if self.station_now_playing >= len(
                        self.station_current_tracks):
                    if self.station_current_unseen == 0:
                        # all played tracks already seen, so probably we've seen all tracks of station, let's stop it
                        debug(
                            f"{self.whoami()}: all played tracks were already seen, stopping this playback cycle"
                        )
                        return None
                    else:
                        need_new_tracks = True
                else:
                    track = self.station_current_tracks[
                        self.station_now_playing]
                    track_id = self.get_track_id(track)
                    if track_id in self.station_recently_played:
                        # try to get next track
                        debug(
                            f"{self.whoami()}: (level 1) skipping already seen track {track_id}"
                        )
                        continue
                    else:
                        self.station_current_unseen += 1
                        self.station_recently_played.append(track_id)
                        debug(
                            f"{self.whoami()}: (level 1) returning next track {track_id}, station_current_unseen=={self.station_current_unseen}"
                        )
                        return track
        # here we are only if we need more tracks from station
        debug(f"{self.whoami()}: getting new set of tracks")
        self.station_current_tracks = self.api.get_station_tracks(
            self.station_current['id'],
            num_tracks=25,
            recently_played_ids=self.station_recently_played)
        self.station_current_unseen = 0
        self.station_now_playing = 0
        if not self.station_current_tracks or len(
                self.station_current_tracks) == 0:
            debug(
                f"{self.whoami()}: got no tracks, stopping this playback cycle"
            )
            return None
        debug(
            f"{self.whoami()}: got {len(self.station_current_tracks)} tracks")

        while not (self.station_now_playing >= len(
                self.station_current_tracks)):
            track = self.station_current_tracks[self.station_now_playing]
            track_id = self.get_track_id(track)
            if track_id in self.station_recently_played:
                # try to get next track
                debug(
                    f"{self.whoami()}: (level 2) skipping already seen track {track_id}"
                )
                self.station_now_playing += 1
                continue
            else:
                self.station_current_unseen += 1
                self.station_recently_played.append(track_id)
                debug(
                    f"{self.whoami()}: (level 2) returning next track {track_id}, station_current_unseen=={self.station_current_unseen}"
                )
                return track
        debug(
            f"{self.whoami()}: (level 2) reached end of list, stopping this playback cycle"
        )
        return None

    def get_track_id(self, track):
        return track['storeId']

    def download_track(self, track):
        file = tempfile.NamedTemporaryFile(mode='w+b',
                                           dir=temp_dir,
                                           prefix='track',
                                           suffix='.mp3')
        stream_url = self.api.get_stream_url(self.get_track_id(track),
                                             quality='low')
        with requests.get(stream_url, stream=True) as r:
            r.raise_for_status()
            for chunk in r.iter_content(chunk_size=8192):
                # If you have chunk encoded response uncomment if
                # and set chunk_size parameter to None.
                # if chunk:
                file.write(chunk)
        file.flush()
        return file

    def get_track_id(self, track):
        return track['storeId']

    def calc_bpm_histogram(self, track):
        file = self.download_track(track)
        debug(
            f"{self.whoami()}: got track {self.get_track_id(track)} to {file.name}"
        )
        histogram = calc_file_bpm_histogram(file.name)
        file.close()
        return histogram
Example #28
0
from gmusicapi.clients import Mobileclient

api = Mobileclient()
logged_in = api.login('<<email_address>>', '<<password>>',
                      Mobileclient.FROM_MAC_ADDRESS)
print(logged_in)

playlists = api.get_all_playlists()
myplaylists = list(
    filter(lambda p: p['name'].find('My Library') == 0, playlists))
for playlist in myplaylists:
    api.delete_playlist(playlist['id'])
    print(playlist['name'])
Example #29
0
File: gp.py Project: vale981/clay
class _GP(object):
    """
    Interface to :class:`gmusicapi.Mobileclient`. Implements
    asynchronous API calls, caching and some other perks.

    Singleton.
    """
    # TODO: Switch to urwid signals for more explicitness?
    caches_invalidated = EventHook()

    def __init__(self):
        # self.is_debug = os.getenv('CLAY_DEBUG')
        self.mobile_client = Mobileclient()
        self.mobile_client._make_call = self._make_call_proxy(
            self.mobile_client._make_call)
        # if self.is_debug:
        #     self.debug_file = open('/tmp/clay-api-log.json', 'w')
        #     self._last_call_index = 0
        self.cached_tracks = None
        self.cached_liked_songs = LikedSongs()
        self.cached_playlists = None
        self.cached_stations = None

        self.invalidate_caches()

        self.auth_state_changed = EventHook()

    def _make_call_proxy(self, func):
        """
        Return a function that wraps *fn* and logs args & return values.
        """
        def _make_call(protocol, *args, **kwargs):
            """
            Wrapper function.
            """
            logger.debug('GP::{}(*{}, **{})'.format(protocol.__name__, args,
                                                    kwargs))
            result = func(protocol, *args, **kwargs)
            # self._last_call_index += 1
            # call_index = self._last_call_index
            # self.debug_file.write(json.dumps([
            #     call_index,
            #     protocol.__name__, args, kwargs,
            #     result
            # ]) + '\n')
            # self.debug_file.flush()
            return result

        return _make_call

    def invalidate_caches(self):
        """
        Clear cached tracks & playlists & stations.
        """
        self.cached_tracks = None
        self.cached_playlists = None
        self.cached_stations = None
        self.caches_invalidated.fire()

    @synchronized
    def login(self, email, password, device_id, **_):
        """
        Log in into Google Play Music.
        """
        self.mobile_client.logout()
        self.invalidate_caches()
        # prev_auth_state = self.is_authenticated
        result = self.mobile_client.login(email, password, device_id)
        # if prev_auth_state != self.is_authenticated:
        self.auth_state_changed.fire(self.is_authenticated)
        return result

    login_async = asynchronous(login)

    @synchronized
    def use_authtoken(self, authtoken, device_id):
        """
        Try to use cached token to log into Google Play Music.
        """
        # pylint: disable=protected-access
        self.mobile_client.session._authtoken = authtoken
        self.mobile_client.session.is_authenticated = True
        self.mobile_client.android_id = device_id
        del self.mobile_client.is_subscribed
        if self.mobile_client.is_subscribed:
            self.auth_state_changed.fire(True)
            return True
        del self.mobile_client.is_subscribed
        self.mobile_client.android_id = None
        self.mobile_client.session.is_authenticated = False
        self.auth_state_changed.fire(False)
        return False

    use_authtoken_async = asynchronous(use_authtoken)

    def get_authtoken(self):
        """
        Return currently active auth token.
        """
        # pylint: disable=protected-access
        return self.mobile_client.session._authtoken

    @synchronized
    def get_all_tracks(self):
        """
        Cache and return all tracks from "My library".

        Each track will have "id" and "storeId" keys.
        """
        if self.cached_tracks:
            return self.cached_tracks
        data = self.mobile_client.get_all_songs()
        self.cached_tracks = Track.from_data(data, Track.SOURCE_LIBRARY, True)

        return self.cached_tracks

    get_all_tracks_async = asynchronous(get_all_tracks)

    def get_stream_url(self, stream_id):
        """
        Returns playable stream URL of track by id.
        """
        return self.mobile_client.get_stream_url(stream_id)

    get_stream_url_async = asynchronous(get_stream_url)

    def increment_song_playcount(self, track_id):
        """
        Increments the playcount of the song given by track_id
        by one.
        """
        return gp.mobile_client.increment_song_playcount(track_id)

    increment_song_playcount_async = asynchronous(increment_song_playcount)

    @synchronized
    def get_all_user_station_contents(self, **_):
        """
              Return list of :class:`.Station` instances.
              """
        if self.cached_stations:
            return self.cached_stations
        self.get_all_tracks()

        self.cached_stations = Station.from_data(
            self.mobile_client.get_all_stations(), True)
        return self.cached_stations

    get_all_user_station_contents_async = (  # pylint: disable=invalid-name
        asynchronous(get_all_user_station_contents))

    @synchronized
    def get_all_user_playlist_contents(self, **_):
        """
        Return list of :class:`.Playlist` instances.
        """
        if self.cached_playlists:
            return [self.cached_liked_songs] + self.cached_playlists

        self.get_all_tracks()

        self.cached_playlists = Playlist.from_data(
            self.mobile_client.get_all_user_playlist_contents(), True)
        return [self.cached_liked_songs] + self.cached_playlists

    get_all_user_playlist_contents_async = (  # pylint: disable=invalid-name
        asynchronous(get_all_user_playlist_contents))

    def get_cached_tracks_map(self):
        """
        Return a dictionary of tracks where keys are strings with track IDs
        and values are :class:`.Track` instances.
        """
        return {track.id: track for track in self.cached_tracks}

    def get_track_by_id(self, any_id):
        """
        Return track by id or store_id.
        """
        for track in self.cached_tracks:
            if any_id in (track.library_id, track.store_id,
                          track.playlist_item_id):
                return track
        return None

    def search(self, query):
        """
        Find tracks and return an instance of :class:`.SearchResults`.
        """
        results = self.mobile_client.search(query)
        return SearchResults.from_data(results)

    search_async = asynchronous(search)

    def add_to_my_library(self, track):
        """
        Add a track to my library.
        """
        result = self.mobile_client.add_store_tracks(track.id)
        if result:
            self.invalidate_caches()
        return result

    def remove_from_my_library(self, track):
        """
        Remove a track from my library.
        """
        result = self.mobile_client.delete_songs(track.id)
        if result:
            self.invalidate_caches()
        return result

    @property
    def is_authenticated(self):
        """
        Return True if user is authenticated on Google Play Music, false otherwise.
        """
        return self.mobile_client.is_authenticated()

    @property
    def is_subscribed(self):
        """
        Return True if user is subscribed on Google Play Music, false otherwise.
        """
        return self.mobile_client.is_subscribed
class MobileClientWrapper(_Base):
	"""Wraps gmusicapi's Mobileclient client interface to provide extra functionality and conveniences."""

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

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

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

	def login(self, username=None, password=None, android_id=None):
		"""Authenticate the gmusicapi Mobileclient instance.

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

		:param username: (Optional) Your Google Music username. Will be prompted if not given.

		:param password: (Optional) Your Google Music password. Will be prompted if not given.

		:param android_id: (Optional) The 16 hex digits from an Android device ID.
		  Default: Use gmusicapi.Mobileclient.FROM_MAC_ADDRESS to create ID from computer's MAC address.
		"""

		if not username:
			username = raw_input("Enter your Google username or email address: ")

		if not password:
			password = getpass.getpass(b"Enter your Google Music password: "******"Sorry, login failed.")

			return False

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

		return True

	def logout(self):
		"""Log out the gmusicapi Mobileclient instance.

		Returns ``True`` on success.
		"""

		return self.api.logout()

	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 Mobileclient.get_all_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_all_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
Example #31
0
    logging.getLogger("urllib3").setLevel(logging.ERROR)
    logging.getLogger("sqlitedict").setLevel(logging.ERROR)

    if len(sys.argv) <= 1:
        print(
            "parameters: -l | dirname [ dirname ... ]"
            "  if dirname given, walk through it and try to make mapping between mp3 file and gmusic tracks"
            "    and save results to local db file"
            "  if -l given, load info from db to gmusic playlist",
            file=sys.stderr
        )
        sys.exit(1)

    oldloglevel = logging.getLogger().level
    logging.getLogger().setLevel(logging.ERROR)
    api = Mobileclient(debug_logging=False)
    logging.getLogger("gmusicapi.Mobileclient1").setLevel(logging.WARNING)
    if not api.oauth_login(gmusic_client_id):
        print(f"Please login to Google Music first (run gmusic-login.py)")
        sys.exit(1)
    logging.getLogger().setLevel(oldloglevel)
    debug("logged in")

    if sys.argv[1] == "-l":
        # load to playlist mode
        playlists = api.get_all_user_playlist_contents()
        # find prevously created playlist with our name
        playlist = None
        for pl in playlists:
            if pl["name"] == playlist_name:
                playlist = pl
Example #32
0
    def __init__(self):

        self.api = Mobileclient()