Merge remote-tracking branch 'adamcik/feature/simplify-outputs' into develop
Conflicts: docs/changes.rst mopidy/utils/settings.py
This commit is contained in:
commit
6b41806eea
@ -26,6 +26,13 @@ v0.8 (in development)
|
||||
known setting, and suggests to the user what we think the setting should have
|
||||
been.
|
||||
|
||||
- Removed most traces of multiple outputs support. Having this feature
|
||||
currently seems to be more trouble than what it is worth.
|
||||
:attr:`mopidy.settings.OUTPUTS` setting is no longer supported, and has been
|
||||
replaced with :attr:`mopidy.settings.OUTPUT` which is a GStreamer
|
||||
bin described in the same format as ``gst-launch`` expects. Default value is
|
||||
``autoaudiosink``.
|
||||
|
||||
|
||||
v0.7.3 (2012-08-11)
|
||||
===================
|
||||
|
||||
@ -112,12 +112,9 @@ Using a custom audio sink
|
||||
=========================
|
||||
|
||||
If you for some reason want to use some other GStreamer audio sink than
|
||||
``autoaudiosink``, you can add ``mopidy.outputs.custom.CustomOutput`` to the
|
||||
:attr:`mopidy.settings.OUTPUTS` setting, and set the
|
||||
:attr:`mopidy.settings.CUSTOM_OUTPUT` setting to a partial GStreamer pipeline
|
||||
description describing the GStreamer sink you want to use.
|
||||
``autoaudiosink``, you can set :attr:`mopidy.settings.OUTPUT` to a partial
|
||||
GStreamer pipeline description describing the GStreamer sink you want to use.
|
||||
|
||||
Example of ``settings.py`` for OSS4::
|
||||
|
||||
OUTPUTS = (u'mopidy.outputs.custom.CustomOutput',)
|
||||
CUSTOM_OUTPUT = u'oss4sink'
|
||||
OUTPUT = u'oss4sink'
|
||||
|
||||
@ -157,18 +157,18 @@ server simultaneously. To use the SHOUTcast output, do the following:
|
||||
#. Install, configure and start the Icecast server. It can be found in the
|
||||
``icecast2`` package in Debian/Ubuntu.
|
||||
|
||||
#. Add ``mopidy.outputs.shoutcast.ShoutcastOutput`` output to the
|
||||
:attr:`mopidy.settings.OUTPUTS` setting.
|
||||
#. Set :attr:`mopidy.settings.OUTPUT` to ``lame ! shout2send`` (an Ogg Vorbis
|
||||
encoder could be used instead of lame).
|
||||
|
||||
#. Check the default values for the following settings, and alter them to match
|
||||
your Icecast setup if needed:
|
||||
#. You might also need to change the ``shout2send`` default settings, run
|
||||
``gst-inspect-0.10 shout2send`` to see the available settings. Most likely
|
||||
you want to change ``ip``, ``username``, ``password`` and ``mount``. For
|
||||
example, to set the password use:
|
||||
``lame ! shout2send username="foobar" password="s3cret"``.
|
||||
|
||||
- :attr:`mopidy.settings.SHOUTCAST_OUTPUT_HOSTNAME`
|
||||
- :attr:`mopidy.settings.SHOUTCAST_OUTPUT_PORT`
|
||||
- :attr:`mopidy.settings.SHOUTCAST_OUTPUT_USERNAME`
|
||||
- :attr:`mopidy.settings.SHOUTCAST_OUTPUT_PASSWORD`
|
||||
- :attr:`mopidy.settings.SHOUTCAST_OUTPUT_MOUNT`
|
||||
- :attr:`mopidy.settings.SHOUTCAST_OUTPUT_ENCODER`
|
||||
Other advanced setups are also possible for outputs. Basically anything you can
|
||||
get a ``gst-lauch`` command to output to can be plugged into
|
||||
:attr:`mopidy.settings.OUTPUT``.
|
||||
|
||||
|
||||
Available settings
|
||||
|
||||
@ -20,7 +20,7 @@ sys.argv[1:] = gstreamer_args
|
||||
|
||||
from mopidy import (get_version, settings, OptionalDependencyError,
|
||||
SettingsError, DATA_PATH, SETTINGS_PATH, SETTINGS_FILE)
|
||||
from mopidy.gstreamer import GStreamer
|
||||
from mopidy.gstreamer import GStreamer, GStreamerError
|
||||
from mopidy.utils import get_class
|
||||
from mopidy.utils.deps import list_deps_optparse_callback
|
||||
from mopidy.utils.log import setup_logging
|
||||
@ -46,6 +46,8 @@ def main():
|
||||
loop.run()
|
||||
except SettingsError as e:
|
||||
logger.error(e.message)
|
||||
except GStreamerError as e:
|
||||
logger.error(e)
|
||||
except KeyboardInterrupt:
|
||||
logger.info(u'Interrupted. Exiting...')
|
||||
except Exception as e:
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gobject
|
||||
import gst
|
||||
|
||||
import logging
|
||||
@ -8,19 +9,22 @@ from pykka.actor import ThreadingActor
|
||||
from pykka.registry import ActorRegistry
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.utils import get_class
|
||||
from mopidy.backends.base import Backend
|
||||
|
||||
logger = logging.getLogger('mopidy.gstreamer')
|
||||
|
||||
|
||||
class GStreamerError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GStreamer(ThreadingActor):
|
||||
"""
|
||||
Audio output through `GStreamer <http://gstreamer.freedesktop.org/>`_.
|
||||
|
||||
**Settings:**
|
||||
|
||||
- :attr:`mopidy.settings.OUTPUTS`
|
||||
- :attr:`mopidy.settings.OUTPUT`
|
||||
|
||||
"""
|
||||
|
||||
@ -36,28 +40,25 @@ class GStreamer(ThreadingActor):
|
||||
rate=(int)44100""")
|
||||
self._pipeline = None
|
||||
self._source = None
|
||||
self._tee = None
|
||||
self._uridecodebin = None
|
||||
self._volume = None
|
||||
self._outputs = []
|
||||
self._handlers = {}
|
||||
self._output = None
|
||||
|
||||
def on_start(self):
|
||||
self._setup_pipeline()
|
||||
self._setup_outputs()
|
||||
self._setup_output()
|
||||
self._setup_message_processor()
|
||||
|
||||
def _setup_pipeline(self):
|
||||
description = ' ! '.join([
|
||||
'uridecodebin name=uri',
|
||||
'audioconvert name=convert',
|
||||
'volume name=volume',
|
||||
'tee name=tee'])
|
||||
'audioresample name=resample',
|
||||
'queue name=queue',
|
||||
'volume name=volume'])
|
||||
|
||||
logger.debug(u'Setting up base GStreamer pipeline: %s', description)
|
||||
|
||||
self._pipeline = gst.parse_launch(description)
|
||||
self._tee = self._pipeline.get_by_name('tee')
|
||||
self._volume = self._pipeline.get_by_name('volume')
|
||||
self._uridecodebin = self._pipeline.get_by_name('uri')
|
||||
|
||||
@ -65,9 +66,16 @@ class GStreamer(ThreadingActor):
|
||||
self._uridecodebin.connect('pad-added', self._on_new_pad,
|
||||
self._pipeline.get_by_name('convert').get_pad('sink'))
|
||||
|
||||
def _setup_outputs(self):
|
||||
for output in settings.OUTPUTS:
|
||||
get_class(output)(self).connect()
|
||||
def _setup_output(self):
|
||||
try:
|
||||
self._output = gst.parse_bin_from_description(settings.OUTPUT, True)
|
||||
except gobject.GError as e:
|
||||
raise GStreamerError('%r while creating %r' % (e.message,
|
||||
settings.OUTPUT))
|
||||
|
||||
self._pipeline.add(self._output)
|
||||
gst.element_link_many(self._volume, self._output)
|
||||
logger.debug('Output set to %s', settings.OUTPUT)
|
||||
|
||||
def _setup_message_processor(self):
|
||||
bus = self._pipeline.get_bus()
|
||||
@ -88,10 +96,6 @@ class GStreamer(ThreadingActor):
|
||||
pad.link(target_pad)
|
||||
|
||||
def _on_message(self, bus, message):
|
||||
if message.src in self._handlers:
|
||||
if self._handlers[message.src](message):
|
||||
return # Message was handeled by output
|
||||
|
||||
if message.type == gst.MESSAGE_EOS:
|
||||
logger.debug(u'GStreamer signalled end-of-stream. '
|
||||
'Telling backend ...')
|
||||
@ -293,104 +297,3 @@ class GStreamer(ThreadingActor):
|
||||
|
||||
event = gst.event_new_tag(taglist)
|
||||
self._pipeline.send_event(event)
|
||||
|
||||
def connect_output(self, output):
|
||||
"""
|
||||
Connect output to pipeline.
|
||||
|
||||
:param output: output to connect to the pipeline
|
||||
:type output: :class:`gst.Bin`
|
||||
"""
|
||||
self._pipeline.add(output)
|
||||
output.sync_state_with_parent() # Required to add to running pipe
|
||||
gst.element_link_many(self._tee, output)
|
||||
self._outputs.append(output)
|
||||
logger.debug('GStreamer added %s', output.get_name())
|
||||
|
||||
def list_outputs(self):
|
||||
"""
|
||||
Get list with the name of all active outputs.
|
||||
|
||||
:rtype: list of strings
|
||||
"""
|
||||
return [output.get_name() for output in self._outputs]
|
||||
|
||||
def remove_output(self, output):
|
||||
"""
|
||||
Remove output from our pipeline.
|
||||
|
||||
:param output: output to remove from the pipeline
|
||||
:type output: :class:`gst.Bin`
|
||||
"""
|
||||
if output not in self._outputs:
|
||||
raise LookupError('Ouput %s not present in pipeline'
|
||||
% output.get_name)
|
||||
teesrc = output.get_pad('sink').get_peer()
|
||||
handler = teesrc.add_event_probe(self._handle_event_probe)
|
||||
|
||||
struct = gst.Structure('mopidy-unlink-tee')
|
||||
struct.set_value('handler', handler)
|
||||
|
||||
event = gst.event_new_custom(gst.EVENT_CUSTOM_DOWNSTREAM, struct)
|
||||
self._tee.send_event(event)
|
||||
|
||||
def _handle_event_probe(self, teesrc, event):
|
||||
if (event.type == gst.EVENT_CUSTOM_DOWNSTREAM
|
||||
and event.has_name('mopidy-unlink-tee')):
|
||||
data = self._get_structure_data(event.get_structure())
|
||||
|
||||
output = teesrc.get_peer().get_parent()
|
||||
|
||||
teesrc.unlink(teesrc.get_peer())
|
||||
teesrc.remove_event_probe(data['handler'])
|
||||
|
||||
output.set_state(gst.STATE_NULL)
|
||||
self._pipeline.remove(output)
|
||||
|
||||
logger.warning('Removed %s', output.get_name())
|
||||
return False
|
||||
return True
|
||||
|
||||
def _get_structure_data(self, struct):
|
||||
# Ugly hack to get around missing get_value in pygst bindings :/
|
||||
data = {}
|
||||
def get_data(key, value):
|
||||
data[key] = value
|
||||
struct.foreach(get_data)
|
||||
return data
|
||||
|
||||
def connect_message_handler(self, element, handler):
|
||||
"""
|
||||
Attach custom message handler for given element.
|
||||
|
||||
Hook to allow outputs (or other code) to register custom message
|
||||
handlers for all messages coming from the element in question.
|
||||
|
||||
In the case of outputs, :meth:`mopidy.outputs.BaseOutput.on_connect`
|
||||
should be used to attach such handlers and care should be taken to
|
||||
remove them in :meth:`mopidy.outputs.BaseOutput.on_remove` using
|
||||
:meth:`remove_message_handler`.
|
||||
|
||||
The handler callback will only be given the message in question, and
|
||||
is free to ignore the message. However, if the handler wants to prevent
|
||||
the default handling of the message it should return :class:`True`
|
||||
indicating that the message has been handled.
|
||||
|
||||
Note that there can only be one handler per element.
|
||||
|
||||
:param element: element to watch messages from
|
||||
:type element: :class:`gst.Element`
|
||||
:param handler: callable that takes :class:`gst.Message` and returns
|
||||
:class:`True` if the message has been handeled
|
||||
:type handler: callable
|
||||
"""
|
||||
self._handlers[element] = handler
|
||||
|
||||
def remove_message_handler(self, element):
|
||||
"""
|
||||
Remove custom message handler.
|
||||
|
||||
:param element: element to remove message handling from.
|
||||
:type element: :class:`gst.Element`
|
||||
"""
|
||||
self._handlers.pop(element, None)
|
||||
|
||||
@ -1,105 +0,0 @@
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger('mopidy.outputs')
|
||||
|
||||
class BaseOutput(object):
|
||||
"""Base class for pluggable audio outputs."""
|
||||
|
||||
MESSAGE_EOS = gst.MESSAGE_EOS
|
||||
MESSAGE_ERROR = gst.MESSAGE_ERROR
|
||||
MESSAGE_WARNING = gst.MESSAGE_WARNING
|
||||
|
||||
def __init__(self, gstreamer):
|
||||
self.gstreamer = gstreamer
|
||||
self.bin = self._build_bin()
|
||||
self.bin.set_name(self.get_name())
|
||||
|
||||
self.modify_bin()
|
||||
|
||||
def _build_bin(self):
|
||||
description = 'queue ! %s' % self.describe_bin()
|
||||
logger.debug('Creating new output: %s', description)
|
||||
return gst.parse_bin_from_description(description, True)
|
||||
|
||||
def connect(self):
|
||||
"""Attach output to GStreamer pipeline."""
|
||||
self.gstreamer.connect_output(self.bin)
|
||||
self.on_connect()
|
||||
|
||||
def on_connect(self):
|
||||
"""
|
||||
Called after output has been connected to GStreamer pipeline.
|
||||
|
||||
*MAY be implemented by subclass.*
|
||||
"""
|
||||
pass
|
||||
|
||||
def remove(self):
|
||||
"""Remove output from GStreamer pipeline."""
|
||||
self.gstreamer.remove_output(self.bin)
|
||||
self.on_remove()
|
||||
|
||||
def on_remove(self):
|
||||
"""
|
||||
Called after output has been removed from GStreamer pipeline.
|
||||
|
||||
*MAY be implemented by subclass.*
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_name(self):
|
||||
"""
|
||||
Get name of the output. Defaults to the output's class name.
|
||||
|
||||
*MAY be implemented by subclass.*
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
return self.__class__.__name__
|
||||
|
||||
def modify_bin(self):
|
||||
"""
|
||||
Modifies ``self.bin`` before it is installed if needed.
|
||||
|
||||
Overriding this method allows for outputs to modify the constructed bin
|
||||
before it is installed. This can for instance be a good place to call
|
||||
`set_properties` on elements that need to be configured.
|
||||
|
||||
*MAY be implemented by subclass.*
|
||||
"""
|
||||
pass
|
||||
|
||||
def describe_bin(self):
|
||||
"""
|
||||
Return string describing the output bin in :command:`gst-launch`
|
||||
format.
|
||||
|
||||
For simple cases this can just be a sink such as ``autoaudiosink``,
|
||||
or it can be a chain like ``element1 ! element2 ! sink``. See the
|
||||
manpage of :command:`gst-launch` for details on the format.
|
||||
|
||||
*MUST be implemented by subclass.*
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def set_properties(self, element, properties):
|
||||
"""
|
||||
Helper method for setting of properties on elements.
|
||||
|
||||
Will call :meth:`gst.Element.set_property` on ``element`` for each key
|
||||
in ``properties`` that has a value that is not :class:`None`.
|
||||
|
||||
:param element: element to set properties on
|
||||
:type element: :class:`gst.Element`
|
||||
:param properties: properties to set on element
|
||||
:type properties: dict
|
||||
"""
|
||||
for key, value in properties.items():
|
||||
if value is not None:
|
||||
element.set_property(key, value)
|
||||
@ -1,34 +0,0 @@
|
||||
from mopidy import settings
|
||||
from mopidy.outputs import BaseOutput
|
||||
|
||||
class CustomOutput(BaseOutput):
|
||||
"""
|
||||
Custom output for using alternate setups.
|
||||
|
||||
This output is intended to handle two main cases:
|
||||
|
||||
1. Simple things like switching which sink to use. Say :class:`LocalOutput`
|
||||
doesn't work for you and you want to switch to ALSA, simple. Set
|
||||
:attr:`mopidy.settings.CUSTOM_OUTPUT` to ``alsasink`` and you are good
|
||||
to go. Some possible sinks include:
|
||||
|
||||
- alsasink
|
||||
- osssink
|
||||
- pulsesink
|
||||
- ...and many more
|
||||
|
||||
2. Advanced setups that require complete control of the output bin. For
|
||||
these cases setup :attr:`mopidy.settings.CUSTOM_OUTPUT` with a
|
||||
:command:`gst-launch` compatible string describing the target setup.
|
||||
|
||||
**Dependencies:**
|
||||
|
||||
- None
|
||||
|
||||
**Settings:**
|
||||
|
||||
- :attr:`mopidy.settings.CUSTOM_OUTPUT`
|
||||
"""
|
||||
|
||||
def describe_bin(self):
|
||||
return settings.CUSTOM_OUTPUT
|
||||
@ -1,20 +0,0 @@
|
||||
from mopidy.outputs import BaseOutput
|
||||
|
||||
class LocalOutput(BaseOutput):
|
||||
"""
|
||||
Basic output to local audio sink.
|
||||
|
||||
This output will normally tell GStreamer to choose whatever it thinks is
|
||||
best for your system. In other words this is usually a sane choice.
|
||||
|
||||
**Dependencies:**
|
||||
|
||||
- None
|
||||
|
||||
**Settings:**
|
||||
|
||||
- None
|
||||
"""
|
||||
|
||||
def describe_bin(self):
|
||||
return 'autoaudiosink'
|
||||
@ -1,58 +0,0 @@
|
||||
import logging
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.outputs import BaseOutput
|
||||
|
||||
logger = logging.getLogger('mopidy.outputs.shoutcast')
|
||||
|
||||
class ShoutcastOutput(BaseOutput):
|
||||
"""
|
||||
Shoutcast streaming output.
|
||||
|
||||
This output allows for streaming to an icecast server or anything else that
|
||||
supports Shoutcast. The output supports setting for: server address, port,
|
||||
mount point, user, password and encoder to use. Please see
|
||||
:class:`mopidy.settings` for details about settings.
|
||||
|
||||
**Dependencies:**
|
||||
|
||||
- A SHOUTcast/Icecast server
|
||||
|
||||
**Settings:**
|
||||
|
||||
- :attr:`mopidy.settings.SHOUTCAST_OUTPUT_HOSTNAME`
|
||||
- :attr:`mopidy.settings.SHOUTCAST_OUTPUT_PORT`
|
||||
- :attr:`mopidy.settings.SHOUTCAST_OUTPUT_USERNAME`
|
||||
- :attr:`mopidy.settings.SHOUTCAST_OUTPUT_PASSWORD`
|
||||
- :attr:`mopidy.settings.SHOUTCAST_OUTPUT_MOUNT`
|
||||
- :attr:`mopidy.settings.SHOUTCAST_OUTPUT_ENCODER`
|
||||
"""
|
||||
|
||||
def describe_bin(self):
|
||||
return 'audioconvert ! %s ! shout2send name=shoutcast' \
|
||||
% settings.SHOUTCAST_OUTPUT_ENCODER
|
||||
|
||||
def modify_bin(self):
|
||||
self.set_properties(self.bin.get_by_name('shoutcast'), {
|
||||
u'ip': settings.SHOUTCAST_OUTPUT_HOSTNAME,
|
||||
u'port': settings.SHOUTCAST_OUTPUT_PORT,
|
||||
u'mount': settings.SHOUTCAST_OUTPUT_MOUNT,
|
||||
u'username': settings.SHOUTCAST_OUTPUT_USERNAME,
|
||||
u'password': settings.SHOUTCAST_OUTPUT_PASSWORD,
|
||||
})
|
||||
|
||||
def on_connect(self):
|
||||
self.gstreamer.connect_message_handler(
|
||||
self.bin.get_by_name('shoutcast'), self.message_handler)
|
||||
|
||||
def on_remove(self):
|
||||
self.gstreamer.remove_message_handler(
|
||||
self.bin.get_by_name('shoutcast'))
|
||||
|
||||
def message_handler(self, message):
|
||||
if message.type != self.MESSAGE_ERROR:
|
||||
return False
|
||||
error, debug = message.parse_error()
|
||||
logger.warning('%s (%s)', error, debug)
|
||||
self.remove()
|
||||
return True
|
||||
@ -26,14 +26,6 @@ BACKENDS = (
|
||||
#: details on the format.
|
||||
CONSOLE_LOG_FORMAT = u'%(levelname)-8s %(message)s'
|
||||
|
||||
#: Which GStreamer bin description to use in
|
||||
#: :class:`mopidy.outputs.custom.CustomOutput`.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: CUSTOM_OUTPUT = u'fakesink'
|
||||
CUSTOM_OUTPUT = u'fakesink'
|
||||
|
||||
#: The log format used for debug logging.
|
||||
#:
|
||||
#: See http://docs.python.org/library/logging.html#formatter-objects for
|
||||
@ -185,71 +177,12 @@ MPD_SERVER_PASSWORD = None
|
||||
#: Default: 20
|
||||
MPD_SERVER_MAX_CONNECTIONS = 20
|
||||
|
||||
#: List of outputs to use. See :mod:`mopidy.outputs` for all available
|
||||
#: backends
|
||||
#: Output to use. See :mod:`mopidy.outputs` for all available backends
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: OUTPUTS = (
|
||||
#: u'mopidy.outputs.local.LocalOutput',
|
||||
#: )
|
||||
OUTPUTS = (
|
||||
u'mopidy.outputs.local.LocalOutput',
|
||||
)
|
||||
|
||||
#: Hostname of the SHOUTcast server which Mopidy should stream audio to.
|
||||
#:
|
||||
#: Used by :mod:`mopidy.outputs.shoutcast`.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: SHOUTCAST_OUTPUT_HOSTNAME = u'127.0.0.1'
|
||||
SHOUTCAST_OUTPUT_HOSTNAME = u'127.0.0.1'
|
||||
|
||||
#: Port of the SHOUTcast server.
|
||||
#:
|
||||
#: Used by :mod:`mopidy.outputs.shoutcast`.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: SHOUTCAST_OUTPUT_PORT = 8000
|
||||
SHOUTCAST_OUTPUT_PORT = 8000
|
||||
|
||||
#: User to authenticate as against SHOUTcast server.
|
||||
#:
|
||||
#: Used by :mod:`mopidy.outputs.shoutcast`.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: SHOUTCAST_OUTPUT_USERNAME = u'source'
|
||||
SHOUTCAST_OUTPUT_USERNAME = u'source'
|
||||
|
||||
#: Password to authenticate with against SHOUTcast server.
|
||||
#:
|
||||
#: Used by :mod:`mopidy.outputs.shoutcast`.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: SHOUTCAST_OUTPUT_PASSWORD = u'hackme'
|
||||
SHOUTCAST_OUTPUT_PASSWORD = u'hackme'
|
||||
|
||||
#: Mountpoint to use for the stream on the SHOUTcast server.
|
||||
#:
|
||||
#: Used by :mod:`mopidy.outputs.shoutcast`.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: SHOUTCAST_OUTPUT_MOUNT = u'/stream'
|
||||
SHOUTCAST_OUTPUT_MOUNT = u'/stream'
|
||||
|
||||
#: Encoder to use to process audio data before streaming to SHOUTcast server.
|
||||
#:
|
||||
#: Used by :mod:`mopidy.outputs.shoutcast`.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: SHOUTCAST_OUTPUT_ENCODER = u'lame mode=stereo bitrate=320'
|
||||
SHOUTCAST_OUTPUT_ENCODER = u'lame mode=stereo bitrate=320'
|
||||
#: OUTPUT = u'autoaudiosink'
|
||||
OUTPUT = u'autoaudiosink'
|
||||
|
||||
#: Path to the Spotify cache.
|
||||
#:
|
||||
|
||||
@ -5,6 +5,8 @@ import sys
|
||||
|
||||
logger = logging.getLogger('mopidy.utils')
|
||||
|
||||
|
||||
# TODO: user itertools.chain.from_iterable(the_list)?
|
||||
def flatten(the_list):
|
||||
result = []
|
||||
for element in the_list:
|
||||
@ -14,22 +16,25 @@ def flatten(the_list):
|
||||
result.append(element)
|
||||
return result
|
||||
|
||||
|
||||
def import_module(name):
|
||||
__import__(name)
|
||||
return sys.modules[name]
|
||||
|
||||
|
||||
def get_class(name):
|
||||
logger.debug('Loading: %s', name)
|
||||
if '.' not in name:
|
||||
raise ImportError("Couldn't load: %s" % name)
|
||||
module_name = name[:name.rindex('.')]
|
||||
class_name = name[name.rindex('.') + 1:]
|
||||
cls_name = name[name.rindex('.') + 1:]
|
||||
try:
|
||||
module = import_module(module_name)
|
||||
class_object = getattr(module, class_name)
|
||||
cls = getattr(module, cls_name)
|
||||
except (ImportError, AttributeError):
|
||||
raise ImportError("Couldn't load: %s" % name)
|
||||
return class_object
|
||||
return cls
|
||||
|
||||
|
||||
def locale_decode(bytestr):
|
||||
try:
|
||||
|
||||
@ -113,6 +113,7 @@ def validate_settings(defaults, settings):
|
||||
errors = {}
|
||||
|
||||
changed = {
|
||||
'CUSTOM_OUTPUT': 'OUTPUT',
|
||||
'DUMP_LOG_FILENAME': 'DEBUG_LOG_FILENAME',
|
||||
'DUMP_LOG_FORMAT': 'DEBUG_LOG_FORMAT',
|
||||
'FRONTEND': 'FRONTENDS',
|
||||
@ -121,7 +122,6 @@ def validate_settings(defaults, settings):
|
||||
'LOCAL_OUTPUT_OVERRIDE': 'CUSTOM_OUTPUT',
|
||||
'LOCAL_PLAYLIST_FOLDER': 'LOCAL_PLAYLIST_PATH',
|
||||
'LOCAL_TAG_CACHE': 'LOCAL_TAG_CACHE_FILE',
|
||||
'OUTPUT': None,
|
||||
'SERVER': None,
|
||||
'SERVER_HOSTNAME': 'MPD_SERVER_HOSTNAME',
|
||||
'SERVER_PORT': 'MPD_SERVER_PORT',
|
||||
@ -137,29 +137,37 @@ def validate_settings(defaults, settings):
|
||||
else:
|
||||
errors[setting] = u'Deprecated setting. Use %s.' % (
|
||||
changed[setting],)
|
||||
continue
|
||||
|
||||
if setting == 'BACKENDS':
|
||||
elif setting == 'BACKENDS':
|
||||
if 'mopidy.backends.despotify.DespotifyBackend' in value:
|
||||
errors[setting] = (u'Deprecated setting value. ' +
|
||||
'"mopidy.backends.despotify.DespotifyBackend" is no ' +
|
||||
'longer available.')
|
||||
continue
|
||||
errors[setting] = (
|
||||
u'Deprecated setting value. '
|
||||
u'"mopidy.backends.despotify.DespotifyBackend" is no '
|
||||
u'longer available.')
|
||||
|
||||
if setting == 'SPOTIFY_BITRATE':
|
||||
elif setting == 'OUTPUTS':
|
||||
errors[setting] = (
|
||||
u'Deprecated setting, please change to OUTPUT. OUTPUT expectes '
|
||||
u'a GStreamer bin describing your desired output.')
|
||||
|
||||
elif setting == 'SPOTIFY_BITRATE':
|
||||
if value not in (96, 160, 320):
|
||||
errors[setting] = (u'Unavailable Spotify bitrate. ' +
|
||||
u'Available bitrates are 96, 160, and 320.')
|
||||
errors[setting] = (
|
||||
u'Unavailable Spotify bitrate. Available bitrates are 96, '
|
||||
u'160, and 320.')
|
||||
|
||||
if setting not in defaults:
|
||||
elif setting.startswith('SHOUTCAST_OUTPUT_'):
|
||||
errors[setting] = (
|
||||
u'Deprecated setting, please set the value via the GStreamer '
|
||||
u'bin in OUTPUT.')
|
||||
|
||||
elif setting not in defaults:
|
||||
errors[setting] = u'Unknown setting.'
|
||||
suggestion = did_you_mean(setting, defaults)
|
||||
|
||||
if suggestion:
|
||||
errors[setting] += u' Did you mean %s?' % suggestion
|
||||
|
||||
continue
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
|
||||
@ -14,7 +14,6 @@ class GStreamerTest(unittest.TestCase):
|
||||
settings.BACKENDS = ('mopidy.backends.local.LocalBackend',)
|
||||
self.song_uri = path_to_uri(path_to_data_dir('song1.wav'))
|
||||
self.gstreamer = GStreamer()
|
||||
self.gstreamer.on_start()
|
||||
|
||||
def prepare_uri(self, uri):
|
||||
self.gstreamer.prepare_change()
|
||||
@ -71,3 +70,8 @@ class GStreamerTest(unittest.TestCase):
|
||||
@unittest.SkipTest
|
||||
def test_set_position(self):
|
||||
pass # TODO
|
||||
|
||||
@unittest.SkipTest
|
||||
def test_invalid_output_raises_error(self):
|
||||
pass # TODO
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user