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()
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 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 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()
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
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
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])
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)
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
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')
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)))
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
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')
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()
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')
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)
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)
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')
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)
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())
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
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]
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 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)
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()