def addid(context, uri, songpos=None): """ *musicpd.org, current playlist section:* ``addid {URI} [POSITION]`` Adds a song to the playlist (non-recursive) and returns the song id. ``URI`` is always a single file or URL. For example:: addid "foo.mp3" Id: 999 OK *Clarifications:* - ``addid ""`` should return an error. """ if not uri: raise exceptions.MpdNoExistError('No such song') if songpos is not None and songpos > context.core.tracklist.length.get(): raise exceptions.MpdArgError('Bad song index') tl_tracks = context.core.tracklist.add(uri=uri, at_position=songpos).get() if not tl_tracks: raise exceptions.MpdNoExistError('No such song') return ('Id', tl_tracks[0].tlid)
def searchaddpl(context, *args): """ *musicpd.org, music database section:* ``searchaddpl {NAME} {TYPE} {WHAT} [...]`` Searches for any song that contains ``WHAT`` in tag ``TYPE`` and adds them to the playlist named ``NAME``. If a playlist by that name doesn't exist it is created. Parameters have the same meaning as for ``find``, except that search is not case sensitive. """ parameters = list(args) if not parameters: raise exceptions.MpdArgError('incorrect arguments') playlist_name = parameters.pop(0) try: query = _query_from_mpd_search_parameters(parameters, _SEARCH_MAPPING) except ValueError: return results = context.core.library.search(**query).get() uri = context.lookup_playlist_uri_from_name(playlist_name) playlist = uri is not None and context.core.playlists.lookup(uri).get() if not playlist: playlist = context.core.playlists.create(playlist_name).get() tracks = list(playlist.tracks) + _get_tracks(results) playlist = playlist.copy(tracks=tracks) context.core.playlists.save(playlist)
def playlistdelete(context, name, songpos): """ *musicpd.org, stored playlists section:* ``playlistdelete {NAME} {SONGPOS}`` Deletes ``SONGPOS`` from the playlist ``NAME.m3u``. """ _check_playlist_name(name) uri = context.lookup_playlist_uri_from_name(name) playlist = uri is not None and context.core.playlists.lookup(uri).get() if not playlist: raise exceptions.MpdNoExistError('No such playlist') try: # Convert tracks to list and remove requested tracks = list(playlist.tracks) tracks.pop(songpos) except IndexError: raise exceptions.MpdArgError('Bad song index') # Replace tracks and save playlist playlist = playlist.replace(tracks=tracks) saved_playlist = context.core.playlists.save(playlist).get() if saved_playlist is None: raise exceptions.MpdFailedToSavePlaylist( urllib.parse.urlparse(uri).scheme)
def playlistinfo(context, parameter=None): """ *musicpd.org, current playlist section:* ``playlistinfo [[SONGPOS] | [START:END]]`` Displays a list of all songs in the playlist, or if the optional argument is given, displays information only for the song ``SONGPOS`` or the range of songs ``START:END``. *ncmpc and mpc:* - uses negative indexes, like ``playlistinfo "-1"``, to request the entire playlist """ if parameter is None or parameter == '-1': start, end = 0, None else: tracklist_slice = protocol.RANGE(parameter) start, end = tracklist_slice.start, tracklist_slice.stop tl_tracks = context.core.tracklist.tl_tracks.get() if start and start > len(tl_tracks): raise exceptions.MpdArgError('Bad song index') if end and end > len(tl_tracks): end = None return translator.tracks_to_mpd_format(tl_tracks, start, end)
def play(context, tlid=None): """ *musicpd.org, playback section:* ``play [SONGPOS]`` Begins playing the playlist at song number ``SONGPOS``. The original MPD server resumes from the paused state on ``play`` without arguments. *Clarifications:* - ``play "-1"`` when playing is ignored. - ``play "-1"`` when paused resumes playback. - ``play "-1"`` when stopped with a current track starts playback at the current track. - ``play "-1"`` when stopped without a current track, e.g. after playlist replacement, starts playback at the first track. *BitMPC:* - issues ``play 6`` without quotes around the argument. """ if tlid is None: return context.core.playback.play().get() elif tlid == -1: return _play_minus_one(context) try: tl_track = context.core.tracklist.slice(tlid, tlid + 1).get()[0] return context.core.playback.play(tl_track).get() except IndexError: raise exceptions.MpdArgError('Bad song index')
def validate(*args, **kwargs): if varargs: return func(*args, **kwargs) try: callargs = inspect.getcallargs(func, *args, **kwargs) except TypeError: raise exceptions.MpdArgError( 'wrong number of arguments for "%s"' % name) for key, value in callargs.items(): default = defaults.get(key, object()) if key in validators and value != default: try: callargs[key] = validators[key](value) except ValueError: raise exceptions.MpdArgError('incorrect arguments') return func(**callargs)
def _find_handler(self, request): for pattern in protocol.request_handlers: matches = re.match(pattern, request) if matches is not None: return (protocol.request_handlers[pattern], matches.groupdict()) command_name = request.split(' ')[0] if command_name in [command.name for command in protocol.mpd_commands]: raise exceptions.MpdArgError('incorrect arguments', command=command_name) raise exceptions.MpdUnknownCommand(command=command_name)
def _query_from_mpd_search_parameters(parameters, mapping): query = {} parameters = list(parameters) while parameters: # TODO: does it matter that this is now case insensitive field = mapping.get(parameters.pop(0).lower()) if not field: raise exceptions.MpdArgError('incorrect arguments') if not parameters: raise ValueError value = parameters.pop(0) if value.strip(): query.setdefault(field, []).append(value) return query
def delete(context, position): """ *musicpd.org, current playlist section:* ``delete [{POS} | {START:END}]`` Deletes a song from the playlist. """ start = position.start end = position.stop if end is None: end = context.core.tracklist.length.get() tl_tracks = context.core.tracklist.slice(start, end).get() if not tl_tracks: raise exceptions.MpdArgError('Bad song index', command='delete') for (tlid, _) in tl_tracks: context.core.tracklist.remove(tlid=[tlid])
def volume(context, change): """ *musicpd.org, playback section:* ``volume {CHANGE}`` Changes volume by amount ``CHANGE``. Note: ``volume`` is deprecated, use ``setvol`` instead. """ if change < -100 or change > 100: raise exceptions.MpdArgError('Invalid volume value') old_volume = context.core.mixer.get_volume().get() if old_volume is None: raise exceptions.MpdSystemError('problems setting volume') new_volume = min(max(0, old_volume + change), 100) success = context.core.mixer.set_volume(new_volume).get() if not success: raise exceptions.MpdSystemError('problems setting volume')
def playlistmove(context, name, from_pos, to_pos): """ *musicpd.org, stored playlists section:* ``playlistmove {NAME} {SONGID} {SONGPOS}`` Moves ``SONGID`` in the playlist ``NAME.m3u`` to the position ``SONGPOS``. *Clarifications:* - The second argument is not a ``SONGID`` as used elsewhere in the protocol documentation, but just the ``SONGPOS`` to move *from*, i.e. ``playlistmove {NAME} {FROM_SONGPOS} {TO_SONGPOS}``. """ if from_pos == to_pos: return _check_playlist_name(name) uri = context.lookup_playlist_uri_from_name(name) playlist = uri is not None and context.core.playlists.lookup(uri).get() if not playlist: raise exceptions.MpdNoExistError('No such playlist') if from_pos == to_pos: return # Nothing to do try: # Convert tracks to list and perform move tracks = list(playlist.tracks) track = tracks.pop(from_pos) tracks.insert(to_pos, track) except IndexError: raise exceptions.MpdArgError('Bad song index') # Replace tracks and save playlist playlist = playlist.replace(tracks=tracks) saved_playlist = context.core.playlists.save(playlist).get() if saved_playlist is None: raise exceptions.MpdFailedToSavePlaylist( urllib.parse.urlparse(uri).scheme)
def count(context, *args): """ *musicpd.org, music database section:* ``count {TAG} {NEEDLE}`` Counts the number of songs and their total playtime in the db matching ``TAG`` exactly. *GMPC:* - use multiple tag-needle pairs to make more specific searches. """ try: query = _query_from_mpd_search_parameters(args, _SEARCH_MAPPING) except ValueError: raise exceptions.MpdArgError('incorrect arguments') results = context.core.library.search(query=query, exact=True).get() result_tracks = _get_tracks(results) return [ ('songs', len(result_tracks)), ('playtime', sum(t.length for t in result_tracks if t.length) / 1000), ]
def split(line): """Splits a line into tokens using same rules as MPD. - Lines may not start with whitespace - Tokens are split by arbitrary amount of spaces or tabs - First token must match `[a-z][a-z0-9_]*` - Remaining tokens can be unquoted or quoted tokens. - Unquoted tokens consist of all printable characters except double quotes, single quotes, spaces and tabs. - Quoted tokens are surrounded by a matching pair of double quotes. - The closing quote must be followed by space, tab or end of line. - Any value is allowed inside a quoted token. Including double quotes, assuming it is correctly escaped. - Backslash inside a quoted token is used to escape the following character. For examples see the tests for this function. """ if not line.strip(): raise exceptions.MpdNoCommand('No command given') match = WORD_RE.match(line) if not match: raise exceptions.MpdUnknownError('Invalid word character') whitespace, command, remainder = match.groups() if whitespace: raise exceptions.MpdUnknownError('Letter expected') result = [command] while remainder: match = PARAM_RE.match(remainder) if not match: msg = _determine_error_message(remainder) raise exceptions.MpdArgError(msg, command=command) unquoted, quoted, remainder = match.groups() result.append(unquoted or UNESCAPE_RE.sub(r'\g<1>', quoted)) return result
def list_(context, *args): """ *musicpd.org, music database section:* ``list {TYPE} [ARTIST]`` Lists all tags of the specified type. ``TYPE`` should be ``album``, ``artist``, ``albumartist``, ``date``, or ``genre``. ``ARTIST`` is an optional parameter when type is ``album``, ``date``, or ``genre``. This filters the result list by an artist. *Clarifications:* The musicpd.org documentation for ``list`` is far from complete. The command also supports the following variant: ``list {TYPE} {QUERY}`` Where ``QUERY`` applies to all ``TYPE``. ``QUERY`` is one or more pairs of a field name and a value. If the ``QUERY`` consists of more than one pair, the pairs are AND-ed together to find the result. Examples of valid queries and what they should return: ``list "artist" "artist" "ABBA"`` List artists where the artist name is "ABBA". Response:: Artist: ABBA OK ``list "album" "artist" "ABBA"`` Lists albums where the artist name is "ABBA". Response:: Album: More ABBA Gold: More ABBA Hits Album: Absolute More Christmas Album: Gold: Greatest Hits OK ``list "artist" "album" "Gold: Greatest Hits"`` Lists artists where the album name is "Gold: Greatest Hits". Response:: Artist: ABBA OK ``list "artist" "artist" "ABBA" "artist" "TLC"`` Lists artists where the artist name is "ABBA" *and* "TLC". Should never match anything. Response:: OK ``list "date" "artist" "ABBA"`` Lists dates where artist name is "ABBA". Response:: Date: Date: 1992 Date: 1993 OK ``list "date" "artist" "ABBA" "album" "Gold: Greatest Hits"`` Lists dates where artist name is "ABBA" and album name is "Gold: Greatest Hits". Response:: Date: 1992 OK ``list "genre" "artist" "The Rolling Stones"`` Lists genres where artist name is "The Rolling Stones". Response:: Genre: Genre: Rock OK *GMPC:* - does not add quotes around the field argument. *ncmpc:* - does not add quotes around the field argument. - capitalizes the field argument. """ params = list(args) if not params: raise exceptions.MpdArgError('incorrect arguments') field = params.pop(0).lower() field = _LIST_MAPPING.get(field) if field is None: raise exceptions.MpdArgError('incorrect arguments') if len(params) == 1: if field != 'album': raise exceptions.MpdArgError('should be "Album" for 3 arguments') query = {'artist': params} else: try: query = _query_from_mpd_search_parameters(params, _LIST_MAPPING) except exceptions.MpdArgError as e: e.message = 'not able to parse args' raise except ValueError: return name = _LIST_NAME_MAPPING[field] result = context.core.library.get_distinct(field, query) return [(name, value) for value in result.get()]