Replace OUTPUTS with OUTPUT and switch to simple outputs that return a gst.Bin
This commit is contained in:
parent
5790d0ba07
commit
c565e274a5
@ -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
96
mopidy/outputs.py
Normal 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
|
||||
@ -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)
|
||||
@ -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,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,
|
||||
})
|
||||
@ -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.
|
||||
#:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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. ' +
|
||||
|
||||
Loading…
Reference in New Issue
Block a user