From c565e274a52e87f7da37e787854e5862301838df Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 23 Aug 2012 23:11:59 +0200 Subject: [PATCH] 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. ' +