Пример #1
0
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)
Пример #2
0
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)
Пример #3
0
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)
Пример #4
0
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)
Пример #5
0
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')
Пример #6
0
            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)
Пример #7
0
 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)
Пример #8
0
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
Пример #9
0
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])
Пример #10
0
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')
Пример #11
0
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)
Пример #12
0
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),
    ]
Пример #13
0
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
Пример #14
0
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()]