Merge branch 'master' into gstreamer
This commit is contained in:
commit
4552784637
@ -4,7 +4,7 @@ Mopidy
|
||||
|
||||
Mopidy is an `MPD <http://mpd.wikia.com/>`_ server with a
|
||||
`Spotify <http://www.spotify.com/>`_ backend. Using a standard MPD client you
|
||||
can search for music in Spotify's wast archive, manage Spotify play lists and
|
||||
can search for music in Spotify's vast archive, manage Spotify play lists and
|
||||
play music from Spotify.
|
||||
|
||||
Mopidy is currently under development. Unless you want to contribute to the
|
||||
|
||||
0
docs/_static/.placeholder
vendored
Normal file
0
docs/_static/.placeholder
vendored
Normal file
121
docs/_themes/nature/static/nature.css_t
vendored
121
docs/_themes/nature/static/nature.css_t
vendored
@ -10,8 +10,8 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 100%;
|
||||
background-color: #111;
|
||||
color: #555;
|
||||
background-color: #111111;
|
||||
color: #555555;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@ -22,7 +22,7 @@ div.documentwrapper {
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 230px;
|
||||
margin: 0 0 0 300px;
|
||||
}
|
||||
|
||||
hr{
|
||||
@ -30,14 +30,14 @@ hr{
|
||||
}
|
||||
|
||||
div.document {
|
||||
background-color: #eee;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 30px 30px;
|
||||
font-size: 0.8em;
|
||||
padding: 1em 30px 30px 30px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
@ -49,25 +49,29 @@ div.footer {
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #444;
|
||||
text-decoration: underline;
|
||||
color: #444444;
|
||||
}
|
||||
|
||||
div.related {
|
||||
background-color: #6BA81E;
|
||||
line-height: 32px;
|
||||
color: #fff;
|
||||
text-shadow: 0px 1px 0 #444;
|
||||
font-size: 0.80em;
|
||||
line-height: 36px;
|
||||
color: #ffffff;
|
||||
text-shadow: 0px 1px 0 #444444;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
div.related a {
|
||||
color: #E2F3CC;
|
||||
}
|
||||
|
||||
|
||||
div.related .right {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
font-size: 0.75em;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.5em;
|
||||
width: 300px
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper{
|
||||
@ -77,46 +81,46 @@ div.sphinxsidebarwrapper{
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: Arial, sans-serif;
|
||||
color: #222;
|
||||
color: #222222;
|
||||
font-size: 1.2em;
|
||||
font-weight: normal;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 5px 10px;
|
||||
background-color: #ddd;
|
||||
text-shadow: 1px 1px 0 white
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4{
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #444;
|
||||
color: #444444;
|
||||
}
|
||||
|
||||
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #888;
|
||||
color: #888888;
|
||||
padding: 5px 20px;
|
||||
margin: 0.5em 0px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.topless {
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px 20px;
|
||||
margin: 10px 10px 10px 20px;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
color: #444444;
|
||||
}
|
||||
|
||||
|
||||
div.sphinxsidebar a:hover {
|
||||
color: #E32E00;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid #cccccc;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
font-size: 1.1em;
|
||||
padding: 0.15em 0.3em;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input[type=text]{
|
||||
@ -132,7 +136,6 @@ a {
|
||||
|
||||
a:hover {
|
||||
color: #E32E00;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
@ -142,20 +145,20 @@ div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #BED4EB;
|
||||
font-weight: normal;
|
||||
color: #212224;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 5px 0 5px 10px;
|
||||
text-shadow: 0px 1px 0 white
|
||||
padding: 5px 0 5px 0px;
|
||||
text-shadow: 0px 1px 0 white;
|
||||
border-bottom: 1px solid #C8D5E3;
|
||||
}
|
||||
|
||||
div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
|
||||
div.body h2 { font-size: 150%; background-color: #C8D5E3; }
|
||||
div.body h3 { font-size: 120%; background-color: #D8DEE3; }
|
||||
div.body h4 { font-size: 110%; background-color: #D8DEE3; }
|
||||
div.body h5 { font-size: 100%; background-color: #D8DEE3; }
|
||||
div.body h6 { font-size: 100%; background-color: #D8DEE3; }
|
||||
div.body h1 { margin-top: 0; font-size: 200%; }
|
||||
div.body h2 { font-size: 150%; }
|
||||
div.body h3 { font-size: 120%; }
|
||||
div.body h4 { font-size: 110%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: #c60f0f;
|
||||
@ -170,7 +173,7 @@ a.headerlink:hover {
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.5em;
|
||||
line-height: 1.8em;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title + p {
|
||||
@ -182,22 +185,23 @@ div.highlight{
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #eeeeee;
|
||||
border: 1px solid #cccccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
background-color: #ffffcc;
|
||||
border: 1px solid #ffff66;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
background-color: #fafafa;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
div.warning {
|
||||
background-color: #ffe4e4;
|
||||
border: 1px solid #f66;
|
||||
border: 1px solid #ff6666;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
@ -210,20 +214,23 @@ p.admonition-title:after {
|
||||
|
||||
pre {
|
||||
padding: 10px;
|
||||
background-color: White;
|
||||
color: #222;
|
||||
line-height: 1.2em;
|
||||
border: 1px solid #C6C9CB;
|
||||
font-size: 1.2em;
|
||||
background-color: #fafafa;
|
||||
color: #222222;
|
||||
line-height: 1.5em;
|
||||
font-size: 1.1em;
|
||||
margin: 1.5em 0 1.5em 0;
|
||||
-webkit-box-shadow: 1px 1px 1px #d8d8d8;
|
||||
-moz-box-shadow: 1px 1px 1px #d8d8d8;
|
||||
-webkit-box-shadow: 0px 0px 4px #d8d8d8;
|
||||
-moz-box-shadow: 0px 0px 4px #d8d8d8;
|
||||
box-shadow: 0px 0px 4px #d8d8d8;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
color: #222222;
|
||||
padding: 1px 2px;
|
||||
font-size: 1.2em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#table-of-contents ul {
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
298
docs/api/backends.rst
Normal file
298
docs/api/backends.rst
Normal file
@ -0,0 +1,298 @@
|
||||
*************************************
|
||||
:mod:`mopidy.backends` -- Backend API
|
||||
*************************************
|
||||
|
||||
.. warning::
|
||||
This is our *planned* backend API, and not the current API.
|
||||
|
||||
.. module:: mopidy.backends
|
||||
:synopsis: Interface between Mopidy and its various backends.
|
||||
|
||||
.. class:: BaseBackend()
|
||||
|
||||
.. attribute:: current_playlist
|
||||
|
||||
The current playlist controller. An instance of
|
||||
:class:`BaseCurrentPlaylistController`.
|
||||
|
||||
.. attribute:: library
|
||||
|
||||
The library controller. An instance of :class:`BaseLibraryController`.
|
||||
|
||||
.. attribute:: playback
|
||||
|
||||
The playback controller. An instance of :class:`BasePlaybackController`.
|
||||
|
||||
.. attribute:: stored_playlists
|
||||
|
||||
The stored playlists controller. An instance of
|
||||
:class:`BaseStoredPlaylistsController`.
|
||||
|
||||
.. attribute:: uri_handlers
|
||||
|
||||
List of URI prefixes this backend can handle.
|
||||
|
||||
|
||||
.. class:: BaseCurrentPlaylistController(backend)
|
||||
|
||||
:param backend: backend the controller is a part of
|
||||
:type backend: :class:`BaseBackend`
|
||||
|
||||
.. method:: add(track, at_position=None)
|
||||
|
||||
Add the track to the end of, or at the given position in the current
|
||||
playlist.
|
||||
|
||||
:param track: track to add
|
||||
:type track: :class:`mopidy.models.Track`
|
||||
:param at_position: position in current playlist to add track
|
||||
:type at_position: int or :class:`None`
|
||||
|
||||
.. method:: clear()
|
||||
|
||||
Clear the current playlist.
|
||||
|
||||
.. method:: load(playlist)
|
||||
|
||||
Replace the current playlist with the given playlist.
|
||||
|
||||
:param playlist: playlist to load
|
||||
:type playlist: :class:`mopidy.models.Playlist`
|
||||
|
||||
.. method:: move(start, end, to_position)
|
||||
|
||||
Move the tracks at positions in [``start``, ``end``] to
|
||||
``to_position``.
|
||||
|
||||
:param start: position of first track to move
|
||||
:type start: int
|
||||
:param end: position of last track to move
|
||||
:type end: int
|
||||
:param to_position: new position for the tracks
|
||||
:type to_position: int
|
||||
|
||||
.. attribute:: playlist
|
||||
|
||||
The currently loaded :class:`mopidy.models.Playlist`.
|
||||
|
||||
.. method:: remove(track)
|
||||
|
||||
Remove the track from the current playlist.
|
||||
|
||||
:param track: track to remove
|
||||
:type track: :class:`mopidy.models.Track`
|
||||
|
||||
.. method:: shuffle(start=None, end=None)
|
||||
|
||||
Shuffles the playlist, optionally a part of the playlist given by
|
||||
``start`` and ``end``.
|
||||
|
||||
:param start: position of first track to shuffle
|
||||
:type start: int or :class:`None`
|
||||
:param end: position of last track to shuffle
|
||||
:type end: int or :class:`None`
|
||||
|
||||
.. attribute:: version
|
||||
|
||||
The current playlist version. Integer which is increased every time the
|
||||
current playlist is changed.
|
||||
|
||||
|
||||
.. class:: BasePlaybackController(backend)
|
||||
|
||||
:param backend: backend the controller is a part of
|
||||
:type backend: :class:`BaseBackend`
|
||||
|
||||
.. attribute:: consume
|
||||
|
||||
:class:`True`
|
||||
Tracks are removed from the playlist when they have been played.
|
||||
:class:`False`
|
||||
Tracks are not removed from the playlist.
|
||||
|
||||
.. attribute:: current_track
|
||||
|
||||
The currently playing or selected :class:`mopidy.models.Track`.
|
||||
|
||||
.. method:: next()
|
||||
|
||||
Play the next track.
|
||||
|
||||
.. method:: pause()
|
||||
|
||||
Pause playblack.
|
||||
|
||||
.. attribute:: PAUSED
|
||||
|
||||
Constant representing the paused state.
|
||||
|
||||
.. method:: play(id=None, position=None)
|
||||
|
||||
Play either the track with the given ID, the given position, or the
|
||||
currently active track.
|
||||
|
||||
:param id: ID of track to play
|
||||
:type id: int
|
||||
:param position: position in current playlist of track to play
|
||||
:type position: int
|
||||
|
||||
.. attribute:: PLAYING
|
||||
|
||||
Constant representing the playing state.
|
||||
|
||||
.. attribute:: playlist_position
|
||||
|
||||
The position in the current playlist.
|
||||
|
||||
.. method:: previous()
|
||||
|
||||
Play the previous track.
|
||||
|
||||
.. attribute:: random
|
||||
|
||||
:class:`True`
|
||||
Tracks are selected at random from the playlist.
|
||||
:class:`False`
|
||||
Tracks are played in the order of the playlist.
|
||||
|
||||
.. attribute:: repeat
|
||||
|
||||
:class:`True`
|
||||
The current track is played repeatedly.
|
||||
:class:`False`
|
||||
The current track is played once.
|
||||
|
||||
.. method:: resume()
|
||||
|
||||
If paused, resume playing the current track.
|
||||
|
||||
.. method:: seek(time_position)
|
||||
|
||||
Seeks to time position given in milliseconds.
|
||||
|
||||
:param time_position: time position in milliseconds
|
||||
:type time_position: int
|
||||
|
||||
.. attribute:: state
|
||||
|
||||
The playback state. Must be :attr:`PLAYING`, :attr:`PAUSED`, or
|
||||
:attr:`STOPPED`.
|
||||
|
||||
.. method:: stop()
|
||||
|
||||
Stop playing.
|
||||
|
||||
.. attribute:: STOPPED
|
||||
|
||||
Constant representing the stopped state.
|
||||
|
||||
.. attribute:: time_position
|
||||
|
||||
Time position in milliseconds.
|
||||
|
||||
.. attribute:: volume
|
||||
|
||||
The audio volume as an int in the range [0, 100]. :class:`None` if
|
||||
unknown.
|
||||
|
||||
|
||||
.. class:: BaseLibraryController(backend)
|
||||
|
||||
:param backend: backend the controller is a part of
|
||||
:type backend: :class:`BaseBackend`
|
||||
|
||||
.. method:: find_exact(type, query)
|
||||
|
||||
Find tracks in the library where ``type`` matches ``query`` exactly.
|
||||
|
||||
:param type: 'title', 'artist', or 'album'
|
||||
:type type: string
|
||||
:param query: the search query
|
||||
:type query: string
|
||||
:rtype: list of :class:`mopidy.models.Track`
|
||||
|
||||
.. method:: lookup(uri)
|
||||
|
||||
Lookup track with given URI.
|
||||
|
||||
:param uri: track URI
|
||||
:type uri: string
|
||||
:rtype: :class:`mopidy.models.Track`
|
||||
|
||||
.. method:: refresh(uri=None)
|
||||
|
||||
Refresh library. Limit to URI and below if an URI is given.
|
||||
|
||||
:param uri: directory or track URI
|
||||
:type uri: string
|
||||
|
||||
.. method:: search(type, query)
|
||||
|
||||
Search the library for tracks where ``type`` contains ``query``.
|
||||
|
||||
:param type: 'title', 'artist', 'album', or 'uri'
|
||||
:type type: string
|
||||
:param query: the search query
|
||||
:type query: string
|
||||
:rtype: list of :class:`mopidy.models.Track`
|
||||
|
||||
|
||||
.. class:: BaseStoredPlaylistsController(backend)
|
||||
|
||||
:param backend: backend the controller is a part of
|
||||
:type backend: :class:`BaseBackend`
|
||||
|
||||
.. method:: add(uri)
|
||||
|
||||
Add existing playlist with the given URI.
|
||||
|
||||
:param uri: URI of existing playlist
|
||||
:type uri: string
|
||||
|
||||
.. method:: create(name)
|
||||
|
||||
Create a new playlist.
|
||||
|
||||
:param name: name of the new playlist
|
||||
:type name: string
|
||||
:rtype: :class:`mopidy.models.Playlist`
|
||||
|
||||
.. attribute:: playlists
|
||||
|
||||
List of :class:`mopidy.models.Playlist`.
|
||||
|
||||
.. method:: delete(playlist)
|
||||
|
||||
Delete playlist.
|
||||
|
||||
:param playlist: the playlist to delete
|
||||
:type playlist: :class:`mopidy.models.Playlist`
|
||||
|
||||
.. method:: lookup(uri)
|
||||
|
||||
Lookup playlist with given URI.
|
||||
|
||||
:param uri: playlist URI
|
||||
:type uri: string
|
||||
:rtype: :class:`mopidy.models.Playlist`
|
||||
|
||||
.. method:: refresh()
|
||||
|
||||
Refresh stored playlists.
|
||||
|
||||
.. method:: rename(playlist, new_name)
|
||||
|
||||
Rename playlist.
|
||||
|
||||
:param playlist: the playlist
|
||||
:type playlist: :class:`mopidy.models.Playlist`
|
||||
:param new_name: the new name
|
||||
:type new_name: string
|
||||
|
||||
.. method:: search(query)
|
||||
|
||||
Search for playlists whose name contains ``query``.
|
||||
|
||||
:param query: query to search for
|
||||
:type query: string
|
||||
:rtype: list of :class:`mopidy.models.Playlist`
|
||||
8
docs/api/models.rst
Normal file
8
docs/api/models.rst
Normal file
@ -0,0 +1,8 @@
|
||||
*********************************************
|
||||
:mod:`mopidy.models` -- Immutable data models
|
||||
*********************************************
|
||||
|
||||
.. automodule:: mopidy.models
|
||||
:synopsis: Immutable data models.
|
||||
:members:
|
||||
:undoc-members:
|
||||
@ -16,13 +16,13 @@ import sys, os
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.append(os.path.abspath('.'))
|
||||
sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/../'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = []
|
||||
extensions = ['sphinx.ext.autodoc']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
@ -147,7 +147,7 @@ html_static_path = ['_static']
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
html_show_sourcelink = False
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
|
||||
@ -6,6 +6,15 @@ Development of Mopidy is coordinated through the IRC channel ``#mopidy`` at
|
||||
``irc.freenode.net`` and through `GitHub <http://github.com/>`_.
|
||||
|
||||
|
||||
API documentation
|
||||
=================
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
api/*
|
||||
|
||||
|
||||
Scope
|
||||
=====
|
||||
|
||||
|
||||
@ -14,5 +14,6 @@ Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
|
||||
@ -123,10 +123,6 @@ class BaseBackend(object):
|
||||
return self.state
|
||||
|
||||
def status_time(self):
|
||||
# XXX This is only called when a client is connected, and is thus not a
|
||||
# complete solution
|
||||
if self._play_time_elapsed >= self.status_time_total() > 0:
|
||||
self.end_of_track()
|
||||
return u'%s:%s' % (self._play_time_elapsed, self.status_time_total())
|
||||
|
||||
def status_time_total(self):
|
||||
@ -208,13 +204,13 @@ class BaseBackend(object):
|
||||
|
||||
# Current/single playlist methods
|
||||
|
||||
def playlist_changes_since(self, version):
|
||||
return None
|
||||
|
||||
def playlist_load(self, name):
|
||||
self._current_song_pos = None
|
||||
matches = filter(lambda p: p.name == name, self._playlists)
|
||||
if matches:
|
||||
self._current_playlist = matches[0]
|
||||
if self.state == self.PLAY:
|
||||
self.play(songpos=0)
|
||||
else:
|
||||
self._current_playlist = None
|
||||
|
||||
|
||||
@ -120,4 +120,5 @@ class DespotifyBackend(BaseBackend):
|
||||
def search(self, type, what):
|
||||
query = u'%s:%s' % (type, what)
|
||||
result = self.spotify.search(query.encode(ENCODING))
|
||||
return self._to_mopidy_playlist(result.playlist).mpd_format()
|
||||
if result is not None:
|
||||
return self._to_mopidy_playlist(result.playlist).mpd_format()
|
||||
|
||||
@ -1,20 +1,40 @@
|
||||
from copy import copy
|
||||
|
||||
class Artist(object):
|
||||
"""
|
||||
:param uri: artist URI
|
||||
:type uri: string
|
||||
:param name: artist name
|
||||
:type name: string
|
||||
"""
|
||||
|
||||
def __init__(self, uri=None, name=None):
|
||||
self._uri = None
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def uri(self):
|
||||
"""The artist URI. Read-only."""
|
||||
return self._uri
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The artist name. Read-only."""
|
||||
return self._name
|
||||
|
||||
|
||||
class Album(object):
|
||||
"""
|
||||
:param uri: album URI
|
||||
:type uri: string
|
||||
:param name: album name
|
||||
:type name: string
|
||||
:param artists: album artists
|
||||
:type artists: list of :class:`Artist`
|
||||
:param num_tracks: number of tracks in album
|
||||
:type num_tracks: integer
|
||||
"""
|
||||
|
||||
def __init__(self, uri=None, name=None, artists=None, num_tracks=0):
|
||||
self._uri = uri
|
||||
self._name = name
|
||||
@ -23,24 +43,49 @@ class Album(object):
|
||||
|
||||
@property
|
||||
def uri(self):
|
||||
"""The album URI. Read-only."""
|
||||
return self._uri
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The album name. Read-only."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def artists(self):
|
||||
"""List of :class:`Artist` elements. Read-only."""
|
||||
return copy(self._artists)
|
||||
|
||||
@property
|
||||
def num_tracks(self):
|
||||
"""The number of tracks in the album. Read-only."""
|
||||
return self._num_tracks
|
||||
|
||||
|
||||
class Track(object):
|
||||
"""
|
||||
:param uri: track URI
|
||||
:type uri: string
|
||||
:param title: track title
|
||||
:type title: string
|
||||
:param artists: track artists
|
||||
:type artists: list of :class:`Artist`
|
||||
:param album: track album
|
||||
:type album: :class:`Album`
|
||||
:param track_no: track number in album
|
||||
:type track_no: integer
|
||||
:param date: track release date
|
||||
:type date: :class:`datetime.date`
|
||||
:param length: track length in milliseconds
|
||||
:type length: integer
|
||||
:param bitrate: bitrate in kbit/s
|
||||
:type bitrate: integer
|
||||
:param id: track ID (unique and non-changing as long as the process lives)
|
||||
:type id: integer
|
||||
"""
|
||||
|
||||
def __init__(self, uri=None, title=None, artists=None, album=None,
|
||||
track_no=0, date=None, length=None, id=None):
|
||||
track_no=0, date=None, length=None, bitrate=None, id=None):
|
||||
self._uri = uri
|
||||
self._title = title
|
||||
self._artists = artists or []
|
||||
@ -48,41 +93,62 @@ class Track(object):
|
||||
self._track_no = track_no
|
||||
self._date = date
|
||||
self._length = length
|
||||
self._bitrate = bitrate
|
||||
self._id = id
|
||||
|
||||
@property
|
||||
def uri(self):
|
||||
"""The track URI. Read-only."""
|
||||
return self._uri
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
"""The track title. Read-only."""
|
||||
return self._title
|
||||
|
||||
@property
|
||||
def artists(self):
|
||||
"""List of :class:`Artist`. Read-only."""
|
||||
return copy(self._artists)
|
||||
|
||||
@property
|
||||
def album(self):
|
||||
"""The track :class:`Album`. Read-only."""
|
||||
return self._album
|
||||
|
||||
@property
|
||||
def track_no(self):
|
||||
"""The track number in album. Read-only."""
|
||||
return self._track_no
|
||||
|
||||
@property
|
||||
def date(self):
|
||||
"""The track release date. Read-only."""
|
||||
return self._date
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
"""The track length in milliseconds. Read-only."""
|
||||
return self._length
|
||||
|
||||
@property
|
||||
def bitrate(self):
|
||||
"""The track's bitrate in kbit/s. Read-only."""
|
||||
return self._bitrate
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""The track ID. Read-only."""
|
||||
return self._id
|
||||
|
||||
def mpd_format(self, position=0):
|
||||
"""
|
||||
Format track for output to MPD client.
|
||||
|
||||
:param position: track's position in playlist
|
||||
:type position: integer
|
||||
:rtype: list of two-tuples
|
||||
"""
|
||||
return [
|
||||
('file', self.uri),
|
||||
('Time', self.length // 1000),
|
||||
@ -96,10 +162,24 @@ class Track(object):
|
||||
]
|
||||
|
||||
def mpd_format_artists(self):
|
||||
"""
|
||||
Format track artists for output to MPD client.
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
return u', '.join([a.name for a in self.artists])
|
||||
|
||||
|
||||
class Playlist(object):
|
||||
"""
|
||||
:param uri: playlist URI
|
||||
:type uri: string
|
||||
:param name: playlist name
|
||||
:type name: string
|
||||
:param tracks: playlist's tracks
|
||||
:type tracks: list of :class:`Track` elements
|
||||
"""
|
||||
|
||||
def __init__(self, uri=None, name=None, tracks=None):
|
||||
self._uri = uri
|
||||
self._name = name
|
||||
@ -107,21 +187,30 @@ class Playlist(object):
|
||||
|
||||
@property
|
||||
def uri(self):
|
||||
"""The playlist URI. Read-only."""
|
||||
return self._uri
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The playlist name. Read-only."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def tracks(self):
|
||||
"""List of :class:`Track` elements. Read-only."""
|
||||
return copy(self._tracks)
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
"""The number of tracks in the playlist. Read-only."""
|
||||
return len(self._tracks)
|
||||
|
||||
def mpd_format(self, start=0, end=None):
|
||||
"""
|
||||
Format playlist for output to MPD client.
|
||||
|
||||
:rtype: list of lists of two-tuples
|
||||
"""
|
||||
if end is None:
|
||||
end = self.length
|
||||
tracks = []
|
||||
|
||||
48
tests/backends/__init__.py
Normal file
48
tests/backends/__init__.py
Normal file
@ -0,0 +1,48 @@
|
||||
from mopidy.models import Track
|
||||
|
||||
class BaseCurrentPlaylistControllerTest(object):
|
||||
uris = []
|
||||
backend_class = None
|
||||
|
||||
def setUp(self):
|
||||
self.backend = self.backend_class()
|
||||
|
||||
def test_add(self):
|
||||
playlist = self.backend.current_playlist
|
||||
|
||||
for uri in self.uris:
|
||||
playlist.add(uri)
|
||||
self.assertEqual(uri, playlist.tracks[-1].uri)
|
||||
|
||||
def test_add_at_position(self):
|
||||
playlist = self.backend.current_playlist
|
||||
|
||||
for uri in self.uris:
|
||||
playlist.add(uri, 0)
|
||||
self.assertEqual(uri, playlist.tracks[0].uri)
|
||||
|
||||
# FIXME test other placements
|
||||
|
||||
class BasePlaybackControllerTest(object):
|
||||
backend_class = None
|
||||
|
||||
def setUp(self):
|
||||
self.backend = self.backend_class()
|
||||
|
||||
def test_play(self):
|
||||
playback = self.backend.playback
|
||||
|
||||
self.assertEqual(playback.state, playback.STOPPED)
|
||||
|
||||
playback.play()
|
||||
|
||||
self.assertEqual(playback.state, playback.PLAYING)
|
||||
|
||||
def test_next(self):
|
||||
playback = self.backend.playback
|
||||
|
||||
current_song = playback.playlist_position
|
||||
|
||||
playback.next()
|
||||
|
||||
self.assertEqual(playback.playlist_position, current_song+1)
|
||||
Loading…
Reference in New Issue
Block a user