Пример #1
0
def rename(context, old_name, new_name):
    """
    *musicpd.org, stored playlists section:*

        ``rename {NAME} {NEW_NAME}``

        Renames the playlist ``NAME.m3u`` to ``NEW_NAME.m3u``.
    """
    _check_playlist_name(old_name)
    _check_playlist_name(new_name)

    old_uri = context.lookup_playlist_uri_from_name(old_name)
    if not old_uri:
        raise exceptions.MpdNoExistError('No such playlist')

    old_playlist = context.core.playlists.lookup(old_uri).get()
    if not old_playlist:
        raise exceptions.MpdNoExistError('No such playlist')

    new_uri = context.lookup_playlist_uri_from_name(new_name)
    if new_uri and context.core.playlists.lookup(new_uri).get():
        raise exceptions.MpdExistError('Playlist already exists')
    # TODO: should we purge the mapping in an else?

    # Create copy of the playlist and remove original
    uri_scheme = urllib.parse.urlparse(old_uri).scheme
    new_playlist = context.core.playlists.create(new_name, uri_scheme).get()
    new_playlist = new_playlist.replace(tracks=old_playlist.tracks)
    saved_playlist = context.core.playlists.save(new_playlist).get()

    if saved_playlist is None:
        raise exceptions.MpdFailedToSavePlaylist(uri_scheme)
    context.core.playlists.delete(old_playlist.uri).get()
Пример #2
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)
Пример #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 add(context, uri):
    """
    *musicpd.org, current playlist section:*

        ``add {URI}``

        Adds the file ``URI`` to the playlist (directories add recursively).
        ``URI`` can also be a single file.

    *Clarifications:*

    - ``add ""`` should add all tracks in the library to the current playlist.
    """
    if not uri.strip('/'):
        return

    # If we have an URI just try and add it directly without bothering with
    # jumping through browse...
    if urllib.parse.urlparse(uri).scheme != '':
        if context.core.tracklist.add(uris=[uri]).get():
            return

    try:
        uris = []
        for path, ref in context.browse(uri, lookup=False):
            if ref:
                uris.append(ref.uri)
    except exceptions.MpdNoExistError as e:
        e.message = 'directory or file not found'
        raise

    if not uris:
        raise exceptions.MpdNoExistError('directory or file not found')
    context.core.tracklist.add(uris=uris).get()
Пример #5
0
def listall(context, uri=None):
    """
    *musicpd.org, music database section:*

        ``listall [URI]``

        Lists all songs and directories in ``URI``.

        Do not use this command. Do not manage a client-side copy of MPD's
        database. That is fragile and adds huge overhead. It will break with
        large databases. Instead, query MPD whenever you need something.


    .. warning:: This command is disabled by default in Mopidy installs.
    """
    result = []
    for path, track_ref in context.browse(uri, lookup=False):
        if not track_ref:
            result.append(('directory', path))
        else:
            result.append(('file', track_ref.uri))

    if not result:
        raise exceptions.MpdNoExistError('Not found')
    return result
Пример #6
0
def lsinfo(context, uri=None):
    """
    *musicpd.org, music database section:*

        ``lsinfo [URI]``

        Lists the contents of the directory ``URI``.

        When listing the root directory, this currently returns the list of
        stored playlists. This behavior is deprecated; use
        ``listplaylists`` instead.

    MPD returns the same result, including both playlists and the files and
    directories located at the root level, for both ``lsinfo``, ``lsinfo
    ""``, and ``lsinfo "/"``.
    """
    result = []
    for path, lookup_future in context.browse(uri, recursive=False):
        if not lookup_future:
            result.append(('directory', path.lstrip('/')))
        else:
            tracks = lookup_future.get()
            if tracks:
                result.extend(translator.track_to_mpd_format(tracks[0]))

    if uri in (None, '', '/'):
        result.extend(protocol.stored_playlists.listplaylists(context))

    if not result:
        raise exceptions.MpdNoExistError('Not found')
    return result
Пример #7
0
def load(context, name, playlist_slice=slice(0, None)):
    """
    *musicpd.org, stored playlists section:*

        ``load {NAME} [START:END]``

        Loads the playlist into the current queue. Playlist plugins are
        supported. A range may be specified to load only a part of the
        playlist.

    *Clarifications:*

    - ``load`` appends the given playlist to the current playlist.

    - MPD 0.17.1 does not support open-ended ranges, i.e. without end
      specified, for the ``load`` command, even though MPD's general range docs
      allows open-ended ranges.

    - MPD 0.17.1 does not fail if the specified range is outside the playlist,
      in either or both ends.
    """
    playlist = context.lookup_playlist_from_name(name)
    if not playlist:
        raise exceptions.MpdNoExistError('No such playlist')
    context.core.tracklist.add(playlist.tracks[playlist_slice])
Пример #8
0
def add(context, uri):
    """
    *musicpd.org, current playlist section:*

        ``add {URI}``

        Adds the file ``URI`` to the playlist (directories add recursively).
        ``URI`` can also be a single file.

    *Clarifications:*

    - ``add ""`` should add all tracks in the library to the current playlist.
    """
    if not uri.strip('/'):
        return

    if context.core.tracklist.add(uri=uri).get():
        return

    try:
        tracks = []
        for path, lookup_future in context.browse(uri):
            if lookup_future:
                tracks.extend(lookup_future.get())
    except exceptions.MpdNoExistError as e:
        e.message = 'directory or file not found'
        raise

    if not tracks:
        raise exceptions.MpdNoExistError('directory or file not found')
    context.core.tracklist.add(tracks=tracks)
Пример #9
0
def _get_playlist(context, name, must_exist=True):
    playlist = None
    uri = context.lookup_playlist_uri_from_name(name)
    if uri:
        playlist = context.core.playlists.lookup(uri).get()
    if must_exist and playlist is None:
        raise exceptions.MpdNoExistError('No such playlist')
    return playlist
Пример #10
0
def deleteid(context, tlid):
    """
    *musicpd.org, current playlist section:*

        ``deleteid {SONGID}``

        Deletes the song ``SONGID`` from the playlist
    """
    tl_tracks = context.core.tracklist.remove(tlid=[tlid]).get()
    if not tl_tracks:
        raise exceptions.MpdNoExistError('No such song')
Пример #11
0
    def browse(self, path, recursive=True, lookup=True):
        """
        Browse the contents of a given directory path.

        Returns a sequence of two-tuples ``(path, data)``.

        If ``recursive`` is true, it returns results for all entries in the
        given path.

        If ``lookup`` is true and the ``path`` is to a track, the returned
        ``data`` is a future which will contain the results from looking up
        the URI with :meth:`mopidy.core.LibraryController.lookup`. If
        ``lookup`` is false and the ``path`` is to a track, the returned
        ``data`` will be a :class:`mopidy.models.Ref` for the track.

        For all entries that are not tracks, the returned ``data`` will be
        :class:`None`.
        """

        path_parts = re.findall(r'[^/]+', path or '')
        root_path = '/'.join([''] + path_parts)

        uri = self._uri_map.uri_from_name(root_path)
        if uri is None:
            for part in path_parts:
                for ref in self.core.library.browse(uri).get():
                    if ref.type != ref.TRACK and ref.name == part:
                        uri = ref.uri
                        break
                else:
                    raise exceptions.MpdNoExistError('Not found')
            root_path = self._uri_map.insert(root_path, uri)

        if recursive:
            yield (root_path, None)

        path_and_futures = [(root_path, self.core.library.browse(uri))]
        while path_and_futures:
            base_path, future = path_and_futures.pop()
            for ref in future.get():
                path = '/'.join([base_path, ref.name.replace('/', '')])
                path = self._uri_map.insert(path, ref.uri)

                if ref.type == ref.TRACK:
                    if lookup:
                        # TODO: can we lookup all the refs at once now?
                        yield (path, self.core.library.lookup(uris=[ref.uri]))
                    else:
                        yield (path, ref)
                else:
                    yield (path, None)
                    if recursive:
                        path_and_futures.append(
                            (path, self.core.library.browse(ref.uri)))
Пример #12
0
 def directory_path_to_uri(self, path):
     parts = re.findall(r'[^/]+', path)
     uri = None
     for part in parts:
         for ref in self.core.library.browse(uri).get():
             if ref.type == ref.DIRECTORY and ref.name == part:
                 uri = ref.uri
                 break
         else:
             raise exceptions.MpdNoExistError()
     return uri
Пример #13
0
def disableoutput(context, outputid):
    """
    *musicpd.org, audio output section:*

        ``disableoutput``

        Turns an output off.
    """
    if outputid == 0:
        context.core.playback.set_mute(False)
    else:
        raise exceptions.MpdNoExistError('No such audio output')
Пример #14
0
def rm(context, name):
    """
    *musicpd.org, stored playlists section:*

        ``rm {NAME}``

        Removes the playlist ``NAME.m3u`` from the playlist directory.
    """
    _check_playlist_name(name)
    uri = context.lookup_playlist_uri_from_name(name)
    if not uri:
        raise exceptions.MpdNoExistError('No such playlist')
    context.core.playlists.delete(uri).get()
Пример #15
0
def disableoutput(context, outputid):
    """
    *musicpd.org, audio output section:*

        ``disableoutput {ID}``

        Turns an output off.
    """
    if outputid == 0:
        success = context.core.mixer.set_mute(False).get()
        if not success:
            raise exceptions.MpdSystemError('problems disabling output')
    else:
        raise exceptions.MpdNoExistError('No such audio output')
Пример #16
0
def swapid(context, tlid1, tlid2):
    """
    *musicpd.org, current playlist section:*

        ``swapid {SONG1} {SONG2}``

        Swaps the positions of ``SONG1`` and ``SONG2`` (both song ids).
    """
    tl_tracks1 = context.core.tracklist.filter(tlid=[tlid1]).get()
    tl_tracks2 = context.core.tracklist.filter(tlid=[tlid2]).get()
    if not tl_tracks1 or not tl_tracks2:
        raise exceptions.MpdNoExistError('No such song')
    position1 = context.core.tracklist.index(tl_tracks1[0]).get()
    position2 = context.core.tracklist.index(tl_tracks2[0]).get()
    swap(context, position1, position2)
Пример #17
0
def moveid(context, tlid, to):
    """
    *musicpd.org, current playlist section:*

        ``moveid {FROM} {TO}``

        Moves the song with ``FROM`` (songid) to ``TO`` (playlist index) in
        the playlist. If ``TO`` is negative, it is relative to the current
        song in the playlist (if there is one).
    """
    tl_tracks = context.core.tracklist.filter(tlid=[tlid]).get()
    if not tl_tracks:
        raise exceptions.MpdNoExistError('No such song')
    position = context.core.tracklist.index(tl_tracks[0]).get()
    context.core.tracklist.move(position, position + 1, to)
Пример #18
0
def toggleoutput(context, outputid):
    """
    *musicpd.org, audio output section:*

        ``toggleoutput {ID}``

        Turns an output on or off, depending on the current state.
    """
    if outputid == 0:
        mute_status = context.core.mixer.get_mute().get()
        success = context.core.mixer.set_mute(not mute_status)
        if not success:
            raise exceptions.MpdSystemError('problems toggling output')
    else:
        raise exceptions.MpdNoExistError('No such audio output')
Пример #19
0
def listplaylistinfo(context, name):
    """
    *musicpd.org, stored playlists section:*

        ``listplaylistinfo {NAME}``

        Lists songs in the playlist ``NAME.m3u``.

    Output format:

        Standard track listing, with fields: file, Time, Title, Date,
        Album, Artist, Track
    """
    playlist = context.lookup_playlist_from_name(name)
    if not playlist:
        raise exceptions.MpdNoExistError('No such playlist')
    return translator.playlist_to_mpd_format(playlist)
Пример #20
0
def playlistid(context, tlid=None):
    """
    *musicpd.org, current playlist section:*

        ``playlistid {SONGID}``

        Displays a list of songs in the playlist. ``SONGID`` is optional
        and specifies a single song to display info for.
    """
    if tlid is not None:
        tl_tracks = context.core.tracklist.filter(tlid=[tlid]).get()
        if not tl_tracks:
            raise exceptions.MpdNoExistError('No such song')
        position = context.core.tracklist.index(tl_tracks[0]).get()
        return translator.track_to_mpd_format(tl_tracks[0], position=position)
    else:
        return translator.tracks_to_mpd_format(
            context.core.tracklist.tl_tracks.get())
Пример #21
0
def listall(context, uri=None):
    """
    *musicpd.org, music database section:*

        ``listall [URI]``

        Lists all songs and directories in ``URI``.
    """
    result = []
    for path, track_ref in context.browse(uri, lookup=False):
        if not track_ref:
            result.append(('directory', path))
        else:
            result.append(('file', track_ref.uri))

    if not result:
        raise exceptions.MpdNoExistError('Not found')
    return result
Пример #22
0
def listplaylist(context, name):
    """
    *musicpd.org, stored playlists section:*

        ``listplaylist {NAME}``

        Lists the files in the playlist ``NAME.m3u``.

    Output format::

        file: relative/path/to/file1.flac
        file: relative/path/to/file2.ogg
        file: relative/path/to/file3.mp3
    """
    playlist = context.lookup_playlist_from_name(name)
    if not playlist:
        raise exceptions.MpdNoExistError('No such playlist')
    return ['file: %s' % t.uri for t in playlist.tracks]
Пример #23
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)
Пример #24
0
    def browse(self, path, recursive=True, lookup=True):
        path_parts = re.findall(r'[^/]+', path or '')
        root_path = '/'.join([''] + path_parts)

        if root_path not in self._uri_from_name:
            uri = None
            for part in path_parts:
                for ref in self.core.library.browse(uri).get():
                    if (ref.type in (ref.DIRECTORY, ref.ALBUM, ref.PLAYLIST)
                            and ref.name == part):
                        uri = ref.uri
                        break
                else:
                    raise exceptions.MpdNoExistError('Not found')
            root_path = self.insert_name_uri_mapping(root_path, uri)

        else:
            uri = self._uri_from_name[root_path]

        if recursive:
            yield (root_path, None)

        path_and_futures = [(root_path, self.core.library.browse(uri))]
        while path_and_futures:
            base_path, future = path_and_futures.pop()
            for ref in future.get():
                path = '/'.join([base_path, ref.name.replace('/', '')])
                path = self.insert_name_uri_mapping(path, ref.uri)

                if ref.type in (ref.DIRECTORY, ref.ALBUM, ref.PLAYLIST):
                    yield (path, None)
                    if recursive:
                        path_and_futures.append(
                            (path, self.core.library.browse(ref.uri)))
                elif ref.type == ref.TRACK:
                    if lookup:
                        yield (path, self.core.library.lookup(ref.uri))
                    else:
                        yield (path, ref)
Пример #25
0
def playid(context, tlid):
    """
    *musicpd.org, playback section:*

        ``playid [SONGID]``

        Begins playing the playlist at song ``SONGID``.

    *Clarifications:*

    - ``playid "-1"`` when playing is ignored.
    - ``playid "-1"`` when paused resumes playback.
    - ``playid "-1"`` when stopped with a current track starts playback at the
      current track.
    - ``playid "-1"`` when stopped without a current track, e.g. after playlist
      replacement, starts playback at the first track.
    """
    if tlid == -1:
        return _play_minus_one(context)
    tl_tracks = context.core.tracklist.filter(tlid=[tlid]).get()
    if not tl_tracks:
        raise exceptions.MpdNoExistError('No such song')
    return context.core.playback.play(tl_tracks[0]).get()