From f912164d399051ce5f0a9538460a92737c2fb975 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 17 Jul 2011 03:31:02 +0200 Subject: [PATCH 01/11] Quick rewrite to tee-less design --- mopidy/gstreamer.py | 34 ++++++++++++++-------------------- mopidy/outputs/__init__.py | 2 +- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index b5e38b92..1de6e000 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -36,7 +36,6 @@ class GStreamer(ThreadingActor): def __init__(self): self._pipeline = None self._source = None - self._tee = None self._uridecodebin = None self._volume = None self._outputs = [] @@ -54,13 +53,11 @@ class GStreamer(ThreadingActor): description = ' ! '.join([ 'uridecodebin name=uri', 'audioconvert name=convert', - 'volume name=volume', - 'tee name=tee']) + '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') @@ -70,7 +67,8 @@ class GStreamer(ThreadingActor): def _setup_outputs(self): for output in settings.OUTPUTS: - get_class(output)(self).connect() + self._outputs.append(get_class(output)(self)) + self._outputs[0].connect() def _setup_message_processor(self): bus = self._pipeline.get_bus() @@ -304,9 +302,8 @@ class GStreamer(ThreadingActor): """ 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()) + gst.element_link_many(self._volume, output) + logger.debug('Output set to %s', output.get_name()) def list_outputs(self): """ @@ -323,26 +320,23 @@ class GStreamer(ThreadingActor): :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) + peersrc = output.get_pad('sink').get_peer() + handler = peersrc.add_event_probe(self._handle_event_probe) - struct = gst.Structure('mopidy-unlink-tee') + struct = gst.Structure('mopidy-unlink') struct.set_value('handler', handler) event = gst.event_new_custom(gst.EVENT_CUSTOM_DOWNSTREAM, struct) - self._tee.send_event(event) + self._volume.send_event(event) - def _handle_event_probe(self, teesrc, event): - if event.type == gst.EVENT_CUSTOM_DOWNSTREAM and event.has_name('mopidy-unlink-tee'): + def _handle_event_probe(self, srcpad, event): + if event.type == gst.EVENT_CUSTOM_DOWNSTREAM and event.has_name('mopidy-unlink'): data = self._get_structure_data(event.get_structure()) - output = teesrc.get_peer().get_parent() + output = srcpad.get_peer().get_parent() - teesrc.unlink(teesrc.get_peer()) - teesrc.remove_event_probe(data['handler']) + srcpad.unlink(srcpad.get_peer()) + srcpad.remove_event_probe(data['handler']) output.set_state(gst.STATE_NULL) self._pipeline.remove(output) diff --git a/mopidy/outputs/__init__.py b/mopidy/outputs/__init__.py index ba242c4b..d94c0727 100644 --- a/mopidy/outputs/__init__.py +++ b/mopidy/outputs/__init__.py @@ -21,7 +21,7 @@ class BaseOutput(object): self.modify_bin() def _build_bin(self): - description = 'queue ! %s' % self.describe_bin() + description = self.describe_bin() logger.debug('Creating new output: %s', description) return gst.parse_bin_from_description(description, True) From b2ccdec9603a26e5efeb557db69d8aab23aa2e29 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 17 Jul 2011 04:14:21 +0200 Subject: [PATCH 02/11] Rip out rest of code that supported simulatnous outputs --- docs/changes.rst | 9 +++- docs/installation/gstreamer.rst | 4 +- docs/settings.rst | 2 +- mopidy/gstreamer.py | 96 ++------------------------------- mopidy/outputs/__init__.py | 29 +--------- mopidy/outputs/shoutcast.py | 16 ------ mopidy/settings.py | 8 +-- 7 files changed, 19 insertions(+), 145 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 5506bfb0..b0d320eb 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -17,10 +17,17 @@ v0.6.0 (in development) - Replace :attr:`mopidy.backends.base.Backend.uri_handlers` with :attr:`mopidy.backends.base.Backend.uri_schemes`, which just takes the part up to the colon of an URI, and not any prefix. + - Add Listener API, :mod:`mopidy.listeners`, to be implemented by actors wanting to receive events from the backend. This is a formalization of the ad hoc events the Last.fm scrobbler has already been using for some time. -- Fix metadata update in Shoutcast streaming (Fixes: :issue:`122`) + +- Fix metadata update in Shoutcast streaming (Fixes: :issue:`122`) + +- Multiple simultaneously playing outputs was considered more trouble than what + it is worth maintnance wise. Thus, this feature has been axed for now. + Switching outputs is still posible, but only one can be active at a time, and + it is still the case that switching during playback does not funtion. v0.5.0 (2011-06-15) diff --git a/docs/installation/gstreamer.rst b/docs/installation/gstreamer.rst index 08e16378..8f2ea07e 100644 --- a/docs/installation/gstreamer.rst +++ b/docs/installation/gstreamer.rst @@ -73,8 +73,8 @@ 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 +``autoaudiosink``, you can set :attr:`mopidy.settings.OUTPUTS` to +``mopidy.outputs.custom.CustomOutput``, and set the :attr:`mopidy.settings.CUSTOM_OUTPUT` setting to a partial GStreamer pipeline description describing the GStreamer sink you want to use. diff --git a/docs/settings.rst b/docs/settings.rst index 68adfd55..d3c9015e 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -131,7 +131,7 @@ 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 +#. Set ``mopidy.outputs.shoutcast.ShoutcastOutput`` as the first output in the :attr:`mopidy.settings.OUTPUTS` setting. #. Check the default values for the following settings, and alter them to match diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 1de6e000..b43089e0 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -29,7 +29,7 @@ class GStreamer(ThreadingActor): **Settings:** - - :attr:`mopidy.settings.OUTPUTS` + - :attr:`mopidy.settings.OUTPUT` """ @@ -66,9 +66,9 @@ class GStreamer(ThreadingActor): self._pipeline.get_by_name('convert').get_pad('sink')) def _setup_outputs(self): - for output in settings.OUTPUTS: - self._outputs.append(get_class(output)(self)) - self._outputs[0].connect() + for klass in settings.OUTPUTS: + self._outputs.append(get_class(klass)()) + self.connect_output(self._outputs[0].bin) def _setup_message_processor(self): bus = self._pipeline.get_bus() @@ -87,10 +87,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 ...') @@ -305,86 +301,4 @@ class GStreamer(ThreadingActor): gst.element_link_many(self._volume, output) logger.debug('Output set to %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` - """ - peersrc = output.get_pad('sink').get_peer() - handler = peersrc.add_event_probe(self._handle_event_probe) - - struct = gst.Structure('mopidy-unlink') - struct.set_value('handler', handler) - - event = gst.event_new_custom(gst.EVENT_CUSTOM_DOWNSTREAM, struct) - self._volume.send_event(event) - - def _handle_event_probe(self, srcpad, event): - if event.type == gst.EVENT_CUSTOM_DOWNSTREAM and event.has_name('mopidy-unlink'): - data = self._get_structure_data(event.get_structure()) - - output = srcpad.get_peer().get_parent() - - srcpad.unlink(srcpad.get_peer()) - srcpad.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) + # FIXME re-add disconnect / swap output code? diff --git a/mopidy/outputs/__init__.py b/mopidy/outputs/__init__.py index d94c0727..21179f94 100644 --- a/mopidy/outputs/__init__.py +++ b/mopidy/outputs/__init__.py @@ -13,8 +13,7 @@ class BaseOutput(object): MESSAGE_ERROR = gst.MESSAGE_ERROR MESSAGE_WARNING = gst.MESSAGE_WARNING - def __init__(self, gstreamer): - self.gstreamer = gstreamer + def __init__(self): self.bin = self._build_bin() self.bin.set_name(self.get_name()) @@ -25,32 +24,6 @@ class BaseOutput(object): 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. diff --git a/mopidy/outputs/shoutcast.py b/mopidy/outputs/shoutcast.py index ffe09aae..0279ae2d 100644 --- a/mopidy/outputs/shoutcast.py +++ b/mopidy/outputs/shoutcast.py @@ -40,19 +40,3 @@ class ShoutcastOutput(BaseOutput): 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 diff --git a/mopidy/settings.py b/mopidy/settings.py index f3e012ed..392c9ad7 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -173,12 +173,8 @@ MPD_SERVER_PASSWORD = None #: #: Default:: #: -#: OUTPUTS = ( -#: u'mopidy.outputs.local.LocalOutput', -#: ) -OUTPUTS = ( - u'mopidy.outputs.local.LocalOutput', -) +#: OUTPUTS = (u'mopidy.outputs.local.LocalOutput',) +OUTPUTS = (u'mopidy.outputs.local.LocalOutput',) #: Hostname of the SHOUTcast server which Mopidy should stream audio to. #: From f995b2f1deb7914c214def8f8a137ac573b50200 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 23 Aug 2012 01:07:22 +0200 Subject: [PATCH 03/11] Continue ripping out multi output support. --- mopidy/gstreamer.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 8781a4b2..4f36b94f 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -38,12 +38,11 @@ class GStreamer(ThreadingActor): self._source = 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): @@ -62,10 +61,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 klass in settings.OUTPUTS: - self._outputs.append(get_class(klass)()) - self.connect_output(self._outputs[0].bin) + def _setup_output(self): + self._output = get_class(settings.OUTPUTS[0])() + + if len(settings.OUTPUTS) > 1: + logger.warning('Only first output will be used.') + + self._pipeline.add(self._output.bin) + gst.element_link_many(self._volume, self._output.bin) + + logger.debug('Output set to %s', self._output.get_name()) def _setup_message_processor(self): bus = self._pipeline.get_bus() @@ -287,15 +292,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._volume, output) - logger.debug('Output set to %s', output.get_name()) From 5790d0ba07d5429d09d93aa6dc8fe63796c44dd4 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 23 Aug 2012 01:13:08 +0200 Subject: [PATCH 04/11] Add removal of multiple outsputs support to changelog. --- docs/changes.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index a2a45960..ad74ade9 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -14,6 +14,9 @@ v0.8 (in development) and various client support. Requires gevent, which currently is not a dependency of Mopidy. +- Removed most traces of multiple outputs support. Having this feature + currently seems to be more trouble than what it is worth. + v0.7.3 (2012-08-11) =================== From c565e274a52e87f7da37e787854e5862301838df Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 23 Aug 2012 23:11:59 +0200 Subject: [PATCH 05/11] Replace OUTPUTS with OUTPUT and switch to simple outputs that return a gst.Bin --- mopidy/gstreamer.py | 16 ++----- mopidy/outputs.py | 96 +++++++++++++++++++++++++++++++++++++ mopidy/outputs/__init__.py | 78 ------------------------------ mopidy/outputs/custom.py | 34 ------------- mopidy/outputs/local.py | 20 -------- mopidy/outputs/shoutcast.py | 42 ---------------- mopidy/settings.py | 7 ++- mopidy/utils/__init__.py | 19 ++++++-- mopidy/utils/settings.py | 12 +++-- 9 files changed, 127 insertions(+), 197 deletions(-) create mode 100644 mopidy/outputs.py delete mode 100644 mopidy/outputs/__init__.py delete mode 100644 mopidy/outputs/custom.py delete mode 100644 mopidy/outputs/local.py delete mode 100644 mopidy/outputs/shoutcast.py diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 4f36b94f..8d8bedb4 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -7,8 +7,7 @@ import logging from pykka.actor import ThreadingActor from pykka.registry import ActorRegistry -from mopidy import settings -from mopidy.utils import get_class +from mopidy import settings, utils from mopidy.backends.base import Backend logger = logging.getLogger('mopidy.gstreamer') @@ -62,15 +61,10 @@ class GStreamer(ThreadingActor): self._pipeline.get_by_name('convert').get_pad('sink')) def _setup_output(self): - self._output = get_class(settings.OUTPUTS[0])() - - if len(settings.OUTPUTS) > 1: - logger.warning('Only first output will be used.') - - self._pipeline.add(self._output.bin) - gst.element_link_many(self._volume, self._output.bin) - - logger.debug('Output set to %s', self._output.get_name()) + self._output = utils.get_function(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() diff --git a/mopidy/outputs.py b/mopidy/outputs.py new file mode 100644 index 00000000..d9619fb8 --- /dev/null +++ b/mopidy/outputs.py @@ -0,0 +1,96 @@ +import pygst +pygst.require('0.10') +import gst + +from mopidy import settings + + +def custom(): + """ + 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` + """ + return gst.parse_bin_from_description(settings.CUSTOM_OUTPUT, True) + + +def local(): + """ + 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 + """ + return gst.parse_bin_from_description('autoaudiosink', True) + + +def shoutcast(): + """ + 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` + """ + encoder = settings.SHOUTCAST_OUTPUT_ENCODER + output = gst.parse_bin_from_description( + '%s ! shout2send name=shoutcast' % encoder, True) + + shoutcast = output.get_by_name('shoutcast') + + properties = { + 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, + } + + for name, value in properties.items(): + shoutcast.set_property(name, value) + + return output diff --git a/mopidy/outputs/__init__.py b/mopidy/outputs/__init__.py deleted file mode 100644 index 21179f94..00000000 --- a/mopidy/outputs/__init__.py +++ /dev/null @@ -1,78 +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): - self.bin = self._build_bin() - self.bin.set_name(self.get_name()) - - self.modify_bin() - - def _build_bin(self): - description = self.describe_bin() - logger.debug('Creating new output: %s', description) - return gst.parse_bin_from_description(description, True) - - 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) diff --git a/mopidy/outputs/custom.py b/mopidy/outputs/custom.py deleted file mode 100644 index 09239a44..00000000 --- a/mopidy/outputs/custom.py +++ /dev/null @@ -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 diff --git a/mopidy/outputs/local.py b/mopidy/outputs/local.py deleted file mode 100644 index 8101e026..00000000 --- a/mopidy/outputs/local.py +++ /dev/null @@ -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' diff --git a/mopidy/outputs/shoutcast.py b/mopidy/outputs/shoutcast.py deleted file mode 100644 index 0279ae2d..00000000 --- a/mopidy/outputs/shoutcast.py +++ /dev/null @@ -1,42 +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, - }) diff --git a/mopidy/settings.py b/mopidy/settings.py index a47b389d..07bfda43 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -185,13 +185,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',) +#: OUTPUT = u'mopidy.outputs.local' +OUTPUT = u'mopidy.outputs.local' #: Hostname of the SHOUTcast server which Mopidy should stream audio to. #: diff --git a/mopidy/utils/__init__.py b/mopidy/utils/__init__.py index 00129cdd..b1234aec 100644 --- a/mopidy/utils/__init__.py +++ b/mopidy/utils/__init__.py @@ -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,31 @@ def flatten(the_list): result.append(element) return result + def import_module(name): __import__(name) return sys.modules[name] -def get_class(name): + +def _get_obj(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:] + obj_name = name[name.rindex('.') + 1:] try: module = import_module(module_name) - class_object = getattr(module, class_name) + obj = getattr(module, obj_name) except (ImportError, AttributeError): raise ImportError("Couldn't load: %s" % name) - return class_object + return obj + + +# We provide both get_class and get_function to make it more obvious what the +# intent of our code really is. +get_class = _get_obj +get_function = _get_obj + def locale_decode(bytestr): try: diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index ff449a61..65548f33 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -120,7 +120,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', @@ -140,11 +139,16 @@ def validate_settings(defaults, settings): if setting == 'BACKENDS': if 'mopidy.backends.despotify.DespotifyBackend' in value: - errors[setting] = (u'Deprecated setting value. ' + - '"mopidy.backends.despotify.DespotifyBackend" is no ' + - 'longer available.') + errors[setting] = (u'Deprecated setting value. ' + u'"mopidy.backends.despotify.DespotifyBackend" is no ' + u'longer available.') continue + if setting == 'OUTPUTS': + errors[setting] = (u'Deprecated setting, please change to OUTPUT. ' + u'Please note that output values have also changed.') + continue + if setting == 'SPOTIFY_BITRATE': if value not in (96, 160, 320): errors[setting] = (u'Unavailable Spotify bitrate. ' + From 7948921510f59c5b1d4b40e336db698995f79bc5 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 26 Aug 2012 12:18:28 +0200 Subject: [PATCH 06/11] Make settings.OUTPUT a GStreamer bin description. --- mopidy/gstreamer.py | 2 +- mopidy/settings.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 8d8bedb4..52fe079e 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -61,7 +61,7 @@ class GStreamer(ThreadingActor): self._pipeline.get_by_name('convert').get_pad('sink')) def _setup_output(self): - self._output = utils.get_function(settings.OUTPUT)() + self._output = gst.parse_bin_from_description(settings.OUTPUT, True) self._pipeline.add(self._output) gst.element_link_many(self._volume, self._output) logger.debug('Output set to %s', settings.OUTPUT) diff --git a/mopidy/settings.py b/mopidy/settings.py index 07bfda43..fce729d3 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -189,8 +189,8 @@ MPD_SERVER_MAX_CONNECTIONS = 20 #: #: Default:: #: -#: OUTPUT = u'mopidy.outputs.local' -OUTPUT = u'mopidy.outputs.local' +#: OUTPUT = u'autoaudiosink' +OUTPUT = u'autoaudiosink' #: Hostname of the SHOUTcast server which Mopidy should stream audio to. #: From 343207ebe27f1b172b20e66dc267cc67371bf925 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 26 Aug 2012 12:37:23 +0200 Subject: [PATCH 07/11] Update docs with latest OUTPUT changes and fix issues raised in review of pull request. --- docs/changes.rst | 4 ++ docs/installation/gstreamer.rst | 9 ++-- docs/settings.rst | 19 ++++--- mopidy/outputs.py | 96 --------------------------------- mopidy/settings.py | 8 --- mopidy/utils/__init__.py | 14 ++--- mopidy/utils/settings.py | 14 +++-- 7 files changed, 29 insertions(+), 135 deletions(-) delete mode 100644 mopidy/outputs.py diff --git a/docs/changes.rst b/docs/changes.rst index ad74ade9..db6a1c60 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -16,6 +16,10 @@ v0.8 (in development) - 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 descriped in the same format as gst-launch expects. Default value is + ``autoaudiosink``. v0.7.3 (2012-08-11) diff --git a/docs/installation/gstreamer.rst b/docs/installation/gstreamer.rst index c6359f6f..546b53ba 100644 --- a/docs/installation/gstreamer.rst +++ b/docs/installation/gstreamer.rst @@ -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 set :attr:`mopidy.settings.OUTPUTS` to -``mopidy.outputs.custom.CustomOutput``, 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' diff --git a/docs/settings.rst b/docs/settings.rst index 980fcd4c..f754bb5e 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -157,18 +157,17 @@ 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. -#. Set ``mopidy.outputs.shoutcast.ShoutcastOutput`` as the first output in 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 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 diff --git a/mopidy/outputs.py b/mopidy/outputs.py deleted file mode 100644 index d9619fb8..00000000 --- a/mopidy/outputs.py +++ /dev/null @@ -1,96 +0,0 @@ -import pygst -pygst.require('0.10') -import gst - -from mopidy import settings - - -def custom(): - """ - 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` - """ - return gst.parse_bin_from_description(settings.CUSTOM_OUTPUT, True) - - -def local(): - """ - 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 - """ - return gst.parse_bin_from_description('autoaudiosink', True) - - -def shoutcast(): - """ - 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` - """ - encoder = settings.SHOUTCAST_OUTPUT_ENCODER - output = gst.parse_bin_from_description( - '%s ! shout2send name=shoutcast' % encoder, True) - - shoutcast = output.get_by_name('shoutcast') - - properties = { - 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, - } - - for name, value in properties.items(): - shoutcast.set_property(name, value) - - return output diff --git a/mopidy/settings.py b/mopidy/settings.py index fce729d3..0bb04823 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -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 diff --git a/mopidy/utils/__init__.py b/mopidy/utils/__init__.py index b1234aec..567c7301 100644 --- a/mopidy/utils/__init__.py +++ b/mopidy/utils/__init__.py @@ -22,24 +22,18 @@ def import_module(name): return sys.modules[name] -def _get_obj(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('.')] - obj_name = name[name.rindex('.') + 1:] + cls_name = name[name.rindex('.') + 1:] try: module = import_module(module_name) - obj = getattr(module, obj_name) + cls = getattr(module, cls_name) except (ImportError, AttributeError): raise ImportError("Couldn't load: %s" % name) - return obj - - -# We provide both get_class and get_function to make it more obvious what the -# intent of our code really is. -get_class = _get_obj -get_function = _get_obj + return cls def locale_decode(bytestr): diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 65548f33..8060c667 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -112,6 +112,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', @@ -139,20 +140,23 @@ def validate_settings(defaults, settings): if setting == 'BACKENDS': if 'mopidy.backends.despotify.DespotifyBackend' in value: - errors[setting] = (u'Deprecated setting value. ' + errors[setting] = ( + u'Deprecated setting value. ' u'"mopidy.backends.despotify.DespotifyBackend" is no ' u'longer available.') continue if setting == 'OUTPUTS': - errors[setting] = (u'Deprecated setting, please change to OUTPUT. ' - u'Please note that output values have also changed.') + errors[setting] = ( + u'Deprecated setting, please change to OUTPUT. OUTPUT expectes ' + u'a GStreamer bin describing your desired output.') continue if 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: errors[setting] = u'Unknown setting. Is it misspelled?' From e840bce233899170322236389136d48ec25459a6 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 26 Aug 2012 18:00:53 +0200 Subject: [PATCH 08/11] Fix comments from review. --- docs/changes.rst | 2 +- docs/settings.rst | 9 +++++---- mopidy/gstreamer.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 76309461..f8f2d402 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -22,7 +22,7 @@ v0.8 (in development) 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 descriped in the same format as gst-launch expects. Default value is + bin described in the same format as ``gst-launch`` expects. Default value is ``autoaudiosink``. diff --git a/docs/settings.rst b/docs/settings.rst index f754bb5e..2f0f0f12 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -157,16 +157,17 @@ 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. -#. Set :attr:`mopidy.settings.OUTPUT` to ``lame ! shout2send`` (an ogg-vorbis +#. Set :attr:`mopidy.settings.OUTPUT` to ``lame ! shout2send`` (an Ogg Vorbis encoder could be used instead of lame). -#. You might also need to change the shout2send default settings, run +#. 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 password="s3cret"``. + example, to set the password use: + ``lame ! shout2send username="foobar" password="s3cret"``. Other advanced setups are also possible for outputs. Basically anything you can -get a gst-lauch command to output to can be plugged into +get a ``gst-lauch`` command to output to can be plugged into :attr:`mopidy.settings.OUTPUT``. diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 52fe079e..0dd02937 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -7,7 +7,7 @@ import logging from pykka.actor import ThreadingActor from pykka.registry import ActorRegistry -from mopidy import settings, utils +from mopidy import settings from mopidy.backends.base import Backend logger = logging.getLogger('mopidy.gstreamer') From 703141c15b214edaff14a73a476db5def3472042 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 1 Sep 2012 01:32:52 +0200 Subject: [PATCH 09/11] Make sure bad data to OUTPUT does not deadlock. - Moves GStreamer initialization out of on-start as it is not obvious to me how to stop rest of setup on other ways. - Note that gst.GError != gobject.GError as far as except is concerned. --- mopidy/core.py | 4 +++- mopidy/gstreamer.py | 13 +++++++++++-- tests/gstreamer_test.py | 6 +++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/mopidy/core.py b/mopidy/core.py index 596e0fe5..ddbf9b8b 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -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.log import setup_logging from mopidy.utils.path import get_or_create_folder, get_or_create_file @@ -45,6 +45,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: diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 0dd02937..8d349fcc 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -1,5 +1,6 @@ import pygst pygst.require('0.10') +import gobject import gst import logging @@ -13,6 +14,10 @@ from mopidy.backends.base import Backend logger = logging.getLogger('mopidy.gstreamer') +class GStreamerError(Exception): + pass + + class GStreamer(ThreadingActor): """ Audio output through `GStreamer `_. @@ -39,7 +44,6 @@ class GStreamer(ThreadingActor): self._volume = None self._output = None - def on_start(self): self._setup_pipeline() self._setup_output() self._setup_message_processor() @@ -61,7 +65,12 @@ class GStreamer(ThreadingActor): self._pipeline.get_by_name('convert').get_pad('sink')) def _setup_output(self): - self._output = gst.parse_bin_from_description(settings.OUTPUT, True) + 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) diff --git a/tests/gstreamer_test.py b/tests/gstreamer_test.py index 012c9002..b370981a 100644 --- a/tests/gstreamer_test.py +++ b/tests/gstreamer_test.py @@ -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 + From 0a86afbe31a42913947f55eef4f7eaca87a3830a Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 1 Sep 2012 11:19:46 +0200 Subject: [PATCH 10/11] Add audioresample and queue to pipeline, fixes #159 - Audioresample should perform as a noop in cases where no conversion is needed. In cases where the sink requires a fixed sample rate this will prevent output from breaking. - The queue is needed to ensure that our outputs play nicely and is simply a continuation of the queue that was in our old Output abstraction. --- mopidy/gstreamer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 8d349fcc..ab4fd59b 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -52,6 +52,8 @@ class GStreamer(ThreadingActor): description = ' ! '.join([ 'uridecodebin name=uri', 'audioconvert name=convert', + 'audioresample name=resample', + 'queue name=queue', 'volume name=volume']) logger.debug(u'Setting up base GStreamer pipeline: %s', description) From 387da5842582b950c329277bafb169d8682caaa3 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 1 Sep 2012 11:22:05 +0200 Subject: [PATCH 11/11] Deprecate and remove shoutcast settings. I've also verfied that the examples provided will actually work. --- mopidy/settings.py | 54 ---------------------------------------- mopidy/utils/settings.py | 16 ++++++------ 2 files changed, 9 insertions(+), 61 deletions(-) diff --git a/mopidy/settings.py b/mopidy/settings.py index 0bb04823..e7c5593a 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -184,60 +184,6 @@ MPD_SERVER_MAX_CONNECTIONS = 20 #: OUTPUT = u'autoaudiosink' OUTPUT = u'autoaudiosink' -#: 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' - #: Path to the Spotify cache. #: #: Used by :mod:`mopidy.backends.spotify`. diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 8060c667..a07075fb 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -136,29 +136,31 @@ 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. ' u'"mopidy.backends.despotify.DespotifyBackend" is no ' u'longer available.') - continue - if setting == 'OUTPUTS': + elif setting == 'OUTPUTS': errors[setting] = ( u'Deprecated setting, please change to OUTPUT. OUTPUT expectes ' u'a GStreamer bin describing your desired output.') - continue - if setting == 'SPOTIFY_BITRATE': + elif setting == 'SPOTIFY_BITRATE': if value not in (96, 160, 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. Is it misspelled?' continue