Replace OUTPUTS with OUTPUT and switch to simple outputs that return a gst.Bin

This commit is contained in:
Thomas Adamcik 2012-08-23 23:11:59 +02:00
parent 5790d0ba07
commit c565e274a5
9 changed files with 127 additions and 197 deletions

View File

@ -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()

96
mopidy/outputs.py Normal file
View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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'

View File

@ -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,
})

View File

@ -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.
#:

View File

@ -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:

View File

@ -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. ' +