def shuffle(mode, verbose=0, quiet=False, _create_request=False): """Turn shuffle on or off.""" request = { 'endpoint': ('me/player/shuffle?state={}'.format('true' if mode == 'on' else 'false')), 'method': 'PUT', 'handle_errs': { 404: NoPlaybackError }, } if _create_request: return request Spotify.request(**request) if quiet: return if verbose == 0: click.echo('Shuffle turned {}.'.format(mode)) else: from cli.commands.status import status status.callback(verbose=verbose, _override={'is_shuffle': mode == 'on'}) return
def toggle(verbose=0, quiet=False, raw=False): """Resume any paused playback, or pause it if already running.""" res = Spotify.request('me/player', method='GET') if not res: raise NoPlaybackError if res['is_playing']: Spotify.request('me/player/pause', method='PUT', ignore_errs=[403], handle_errs={404: NoPlaybackError}) if not quiet: from cli.commands.status import status status.callback(verbose=verbose, _override={'is_playing': False}) else: Spotify.request('me/player/play', method='PUT', ignore_errs=[403], handle_errs={404: NoPlaybackError}) if not quiet: from cli.commands.status import status status.callback(verbose=verbose, _override={'is_playing': True}) return
def repeat(mode, verbose=0, quiet=False, _create_request=False): """Turn repeat on (all/track) or off.""" state_map = {'all': 'context', 'track': 'track', 'off': 'off'} request = { 'endpoint': 'me/player/repeat?state={}'.format(state_map[mode]), 'method': 'PUT', 'handle_errs': { 404: NoPlaybackError }, } if _create_request: return request Spotify.request(**request) if quiet: return if verbose == 0: if mode == 'off': click.echo('Repeat turned off.') elif mode == 'track': click.echo('Repeat set to current track.') elif mode == 'all': click.echo('Repeat set to all tracks.') else: from cli.commands.status import status status.callback(verbose=verbose, _override={'is_repeat': mode in ['all', 'track']}) return
def previous(verbose=0, quiet=False): """Play the previous song in the queue.""" Spotify.request('me/player/previous', method='POST') if not quiet: from cli.commands.status import status status.callback(verbose=verbose) return
def _next(verbose=0, quiet=False): """Play the next track in the queue.""" Spotify.request('me/player/next', method='POST', handle_errs={404: NoPlaybackError}) if not quiet: from cli.commands.status import status status.callback(verbose=verbose) return
def pause(verbose=0, quiet=False): """Pause playback.""" Spotify.request( 'me/player/pause', method='PUT', ignore_errs=[403], handle_errs={404: NoPlaybackError} ) if not quiet: from cli.commands.status import status status.callback(verbose=verbose, _override={'is_playing': False}) return
def browse(browse_type, verbose=0, quiet=False): """Open the current track, album, artist, or playlist in the browser. Specify one of the above options to change what to browse (default: track). """ import webbrowser from cli.commands.status import status playback_data = status.callback(_return_parsed=True) music = playback_data['music'] # parse command and playback context if browse_type in ['track', 'album', 'artist']: url = music[browse_type]['url'] name = music[browse_type]['name'] if browse_type != 'artist': name = '"{}" by {}'.format(name, music['artist']['name']) elif browse_type == 'playlist': # playlist and radio are both type 'playlist' if music['context']['type'] != 'playlist': click.echo('Error: Current session is not a playlist.', err=True) return url = music['context']['url'] id_str = music['context']['id'] name = Spotify.request('playlists/' + id_str)['name'] if not quiet: click.echo('{} - {}\n' '{}'.format(browse_type.title(), name, url)) webbrowser.open(url) return
def play(verbose=0, quiet=False): """Resume playback.""" try: Spotify.request('me/player/play', method='PUT') except SpotifyAPIError as e: if e.status == 403: pass else: raise e if not quiet: from cli.commands.status import status status.callback(verbose=verbose, override={'is_playing': True}) return
def volume(to, up, down): """Control the active device's volume level.""" num_options = (bool(up) + bool(down) + bool(to)) if num_options != 1: raise InvalidVolumeInput if to: new_volume = to else: from cli.commands.status import status raw_status = status.callback(raw=True, verbose=-1) current_volume = raw_status['device']['volume_percent'] new_volume = current_volume + up - down if new_volume > 100: new_volume = 100 elif new_volume < 0: new_volume = 0 try: Spotify.request( 'me/player/volume?volume_percent={}'.format(new_volume), method='PUT') except SpotifyAPIError as e: if e.status == 403: raise DeviceOperationRestricted else: raise e click.echo('Volume set to {}%'.format(new_volume)) return
def volume(mode, amount): """Control the active device's volume level (0-100). Example: spotify volume to 50 """ if mode == 'to': new_volume = amount else: from cli.commands.status import status device = status.callback(raw=True, verbose=-1).get('device') current_volume = device['volume_percent'] if mode == 'up': increment = amount elif mode == 'down': increment = -amount new_volume = current_volume + increment if new_volume > 100: new_volume = 100 elif new_volume < 0: new_volume = 0 Spotify.request('me/player/volume?volume_percent={}'.format(new_volume), method='PUT', handle_errs={403: DeviceOperationRestricted}) click.echo('Volume set to {}%'.format(new_volume)) return
def queue(keyword, queue_type='track', yes=False, quiet=False): """Add a track or album to your queue. Example: Use 'spotify queue .' to add the current track. """ keyword = ' '.join(keyword) if keyword == '.': from cli.commands.status import status playback_data = status.callback(_return_parsed=True) item = playback_data['music']['track'] else: import urllib.parse as ul pager = Spotify.Pager( 'search', params={ 'q': ul.quote_plus(keyword), 'type': queue_type, }, content_callback=lambda c: c[queue_type + 's'], ) if len(pager.content['items']) == 0: click.echo('No results found for "{}"'.format(keywords), err=True) return item = pager.content['items'][0] # parse command and playback context display_str = '{} - {}{}'.format( cut_string(item['name'], 50), cut_string(', '.join(parse_artists(item['artists'])['names']), 30), '' if queue_type == 'track' else ' ({} tracks)'.format( item['total_tracks']), ) # handle confirmation & request if not yes: click.confirm(display_str + '\nAdd this {} to the queue?'.format(queue_type), default=True, abort=True) if queue_type == 'track': uris = [item['uri']] else: album = Spotify.request('albums/' + item['id']) uris = [track['uri'] for track in album['tracks']['items']] requests = [{ 'endpoint': 'me/player/queue?uri=' + uri, 'method': 'POST', } for uri in uris] Spotify.multirequest(requests, delay_between=0.25) # print output if not quiet: click.echo(queue_type.capitalize() + ' added to queue' + ('.' if not yes else ':\n' + display_str)) return
def save(keyword, save_type, yes, quiet=False): """Save a track, album, artist, or playlist. Example: Use 'spotify save .' to save the current track. """ keyword = ' '.join(keyword) if keyword == '.': from cli.commands.status import status playback_data = status.callback(_return_parsed=True) music = playback_data['music'] else: import urllib.parse as ul pager = Spotify.Pager( 'search', params={ 'q': ul.quote_plus(keyword), 'type': save_type, }, content_callback=lambda c: c[save_type + 's'], ) if len(pager.content['items']) == 0: click.echo('No results found for "{}"'.format(keywords), err=True) return music = pager.content['items'][0] # parse command and playback context if keyword == '.' and save_type != 'playlist': music = music[save_type] if save_type in ['track', 'album']: id_str = music['id'] name = cut_string(music['name'], 50) elif save_type == 'artist': id_str = music['id'] name = music['name'] elif save_type == 'playlist': # playlist and radio are both type 'playlist' if keyword == '.': if music['context']['type'] != 'playlist': click.echo('Error: Current session is not a playlist.', err=True) return id_str = music['context']['id'] name = Spotify.request('playlists/' + id_str)['name'] else: id_str = music['id'] name = music['name'] # format endpoint data = None if save_type in ['track', 'album']: endpoint = 'me/{}s?ids={}'.format(save_type, id_str) message = 'Saved {} - {} to library.'.format(save_type, name) elif save_type == 'artist': endpoint = 'me/following?type=artist&ids={}'.format(id_str) message = 'Following {} - {}.'.format(save_type, name) elif save_type == 'playlist': endpoint = 'playlists/{}/followers'.format(id_str) message = 'Following {} - {}.'.format(save_type, name) data = {'public': True} if not yes: click.confirm(name + '\nSave this {} to your library?'.format(save_type), default=True, abort=True) Spotify.request(endpoint, method='PUT', data=data, handle_errs={ 403: (AuthScopeError, { 'required_scope_key': 'user-modify' }) }) click.echo(message) return
def seek(forward, reverse, position): """Seek to time (default unit: seconds) in the current track. \b Examples: spotify seek -f 50 # increment playback position by 50s spotify seek -r 1m10s # decrement playback position by 1m10s spotify seek 50% # set playback position to half the track duration spotify seek 2m # set playback position to 2 minutes into the track """ tokens = [s for s in re.split('(%|ms|m|s)', position) if len(s) > 0] relative_factor = 0 if reverse: relative_factor = -1 if forward: relative_factor = 1 if len(tokens) > 0: from cli.commands.status import status status_data = status.callback(raw=True, verbose=-1) progress_ms = status_data.get('progress_ms') duration_ms = status_data.get('item').get('duration_ms') expect_unit = False new_position_ms = 0 for t in tokens: if not expect_unit: try: value = float(t) expect_unit = True except: raise InvalidInput( ' Expected number but can\'t convert {} to number.'. format(t)) else: if t == "ms": new_position_ms += int(value) elif t == "s": new_position_ms += int(value * 1000) elif t == "m": new_position_ms += int(value * 60000) elif t == "%": new_position_ms += int((value / 100.0) * duration_ms) else: raise InvalidInput( ' Expected unit (m, s, ms or %) but got {}.'.format(t)) expect_unit = False value = None if value is not None: new_position_ms += int(value * 1000) if relative_factor != 0: new_position_ms = progress_ms + relative_factor * new_position_ms new_position_ms = min(max(0, new_position_ms), duration_ms) Spotify.request( 'me/player/seek?position_ms={}'.format(new_position_ms), method='PUT', handle_errs={404: NoPlaybackError}) position_str = "" if new_position_ms >= 60000: position_str += "{}m".format(new_position_ms // 60000) position_str += "{}s".format((new_position_ms // 1000) % 60) click.echo('Position set to {}.'.format(position_str)) return