def write(fn=None): """ Write the current queue to a file. Keyword arguments: fn=None: File to be written to. File is stored at ~/.local/share/pmcli/playlists/. """ if not queue: # Can't save an empty queue. error_msg(outbar, 'Queue is empty.') return if fn is None: # No argument. error_msg(outbar, 'Missing argument to write.') return path = join(expanduser('~'), '.local', 'share', 'pmcli', 'playlists') if not exists(path): # No playlists directory. error_msg(outbar, 'Path to playlists does not exist.') elif exists(join(path, fn)): error_msg(outbar, 'Playist %s already exists.' % fn) else: # Write the playlist. with open(join(path, fn), 'a') as f: json.dump(queue, f) addstr(outbar, 'Wrote queue to %s.' % fn)
def search(query=None): """ Search Google Play Music for a given query. Keyword arguments: query=None: The search query. Returns: A dict of lists with keys 'songs', 'artists', and 'albums'. """ global content if query is None: # No argument. error_msg(outbar, 'Missing search query.') return # Fetch as many results as we can display depending on terminal height. limit = int((main.getmaxyx()[0] - 3) / 3) addstr(outbar, 'Searching for \'%s\'...' % query) result = api.search(query, max_results=limit) outbar.erase() # Remove trailing output. outbar.refresh() # 'class' => class of MusicObject # 'hits' => key in search result # 'key' => per-entry key in search result mapping = { 'songs': { 'class': Song, 'hits': 'song_hits', 'key': 'track', }, 'artists': { 'class': Artist, 'hits': 'artist_hits', 'key': 'artist', }, 'albums': { 'class': Album, 'hits': 'album_hits', 'key': 'album', } } content = {'songs': [], 'artists': [], 'albums': []} for k in content.keys(): result[k] = iter(result[mapping[k]['hits']]) # Create 'limit' of each type. for i in range(limit): for k in content.keys(): try: content[k].append(mapping[k]['class'](next( result[k])[mapping[k]['key']])) except StopIteration: pass return content
def get_input(): """Get user input in the bottom bar.""" addstr(inbar, '> ') crs.curs_set(2) # Show the cursor. try: input = inbar.getstr() except KeyboardInterrupt: addstr(outbar, 'Goodbye, thanks for using pmcli!') leave(1) inbar.deleteln() crs.curs_set(0) # Hide the cursor. return input.decode('utf-8')
def transition(input): """ Route input to the appropriate function. Arguments: input: User input. """ commands = { 'h': help, 'help': help, 'e': expand, 'expand': expand, 's': search, 'search': search, 'p': play, 'play': play, 'q': enqueue, 'queue': enqueue, 'w': write, 'write': write, 'r': restore, 'restore': restore, } arg = None if content is None: addstr(infobar, 'Now playing: None') try: command, arg = input.split(maxsplit=1) except ValueError: command = input if command in commands: commands[command](arg) if content is not None: display() else: error_msg(outbar, 'Nonexistent command.')
def restore(fn=None): """ Restore queue from a file. Keyword arguments: fn=None: Name of the file containing the playlist. File should be at ~/.local/share/pmcli/playlists/. """ if fn is None: # No argument. error_msg(outbar, 'Missing argument to restore.') return path = join(expanduser('~'), '.local', 'share', 'pmcli', 'playlists') if not isfile(join(path, fn)): # Playlist file doesn't exist. error_msg(outbar, '%s does not exist.' % fn) return addstr(outbar, 'Restoring queue from %s...' % fn) try: # Read the playlist. json_songs = json.loads(open(join(path, fn)).read()) except json.decoder.JSONDecodeError: # Bad file. error_msg(outbar, '%s is not a valid playlist file.' % fn) return songs = [] for song in json_songs: if Song.verify(song): # Make sure all the data is there. songs.append(Song(song, json=True)) # Replace the current queue with the playlist. del queue[:] del queue.ids[:] queue.extend(songs) addstr( outbar, 'Restored %d/%d songs from playlist %s.' % (len(songs), len(json_songs), fn))
def expand(num=None): """ Display all of a MusicObject's information: songs, artists, and albums. Keyword arguments: num=None: Index of the MusicObject in the main window to be expanded. """ global content if num is None: # No argument. error_msg(outbar, 'Missing argument to play.') elif content is None: # Nothing to expand. error_msg(outbar, 'Wrong context for expand.') else: try: num = int(num) except ValueError: # num needs to be an int. error_msg(outbar, 'Invalid argument to play.') else: limit = int((main.getmaxyx()[0] - 6) / 3) opt = get_option(num, limit) if opt is not None: # Valid input. addstr(outbar, 'Loading \'%s\'...' % to_string(opt)) content = opt.collect(limit=limit) outbar.erase() outbar.refresh() else: # num out of range. error_msg( outbar, 'Invalid number. Valid between 1-%d.' % sum([len(content[k]) for k in content.keys()]))
def play(arg=None): """ Play a MusicObject or the current queue. Keyword arguments: arg=None: A number n to play item n, 's' to play the queue in shuffle mode, or None to play the current queue in order. """ global content if arg is None or arg is 's': if not queue: # Can't play an empty queue. error_msg(outbar, 'The queue is empty.') else: # Play the queue. if arg is 's': # Shuffle. queue.shuffle() content = queue.collect() display() addstr(outbar, '[spc] pause [q] stop [n] next [9-0] volume') queue.play(infobar) outbar.erase() # Remove trailing output. outbar.refresh() elif content is None: # Nothing to play. error_msg(outbar, 'Wrong context for play.') else: try: num = int(arg) except ValueError: # arg needs to be an int if it isn't 's'. error_msg(outbar, 'Invalid argument to play.') else: opt = get_option(num) if opt is not None: # Valid input. addstr(outbar, '[spc] pause [q] stop [n] next [9-0] volume') opt.play(infobar) addstr(infobar, 'Now playing: None') outbar.erase() outbar.refresh() else: # num out of range. error_msg( outbar, 'Invalid number. Valid between 1-%d' % sum([len(content[k]) for k in content.keys()]))
def enqueue(arg=None): """ Display the current queue, or add an item to the queue. Keyword arguments: arg=None: Index of the MusicObject in the main window to add to the queue, 'c' to clear the queue, None to display the queue, or a space-delimited list of indices to add to the queue, i.e. '1 2 3'. Returns: True if an element was successfully inserted. To be used when inserting multiple elements. """ global content if arg is None: if not queue: # Nothing to display. error_msg(outbar, 'The queue is empty.') else: # Display the queue. content = queue.collect() elif content is None: # Nothing to queue. error_msg(outbar, 'Wrong context for queue.') else: if arg is 'c': # Clear the queue. addstr(outbar, 'Cleared queue.') queue.clear() else: try: num = int(arg) except ValueError: # Check for multi-option argument. try: nums = [int(i) for i in arg.split()] except ValueError: error_msg(outbar, 'Invalid argument to queue.') else: # Add all arguments to the queue. count = 0 for num in nums: count = count + 1 if enqueue(num) else count addstr( outbar, 'Added %d item%s to the queue.' % (count, '' if count == 1 else 's')) else: opt = get_option(num) if opt is not None: if opt['kind'] == 'artist': # Artists can't be queued. error_msg( outbar, 'Can only add songs or albums to the queue.') elif opt['id'] in queue.ids: # Duplicate entry. error_msg( outbar, '\'%s\' is already in the queue.' % to_string(opt)) else: # Valid input. addstr( outbar, 'Adding \'%s\' to the queue...' % to_string(opt)) queue.append(opt) addstr(outbar, 'Added \'%s\' to the queue.' % to_string(opt)) return True else: # num out of range. error_msg( outbar, 'Invalid number. Valid between 1-%d.' % sum([len(content[k]) for k in content.keys()]))
if not queue: # Can't save an empty queue. error_msg(outbar, 'Queue is empty.') return if fn is None: # No argument. error_msg(outbar, 'Missing argument to write.') return path = join(expanduser('~'), '.local', 'share', 'pmcli', 'playlists') if not exists(path): # No playlists directory. error_msg(outbar, 'Path to playlists does not exist.') elif exists(join(path, fn)): error_msg(outbar, 'Playist %s already exists.' % fn) else: # Write the playlist. with open(join(path, fn), 'a') as f: json.dump(queue, f) addstr(outbar, 'Wrote queue to %s.' % fn) if __name__ == '__main__': colour, main, inbar, infobar, outbar = initialize() addstr(infobar, 'Enter \'h\' or \'help\' if you need help.') queue = Queue() global content content = None while True: transition(get_input())