From dc018024f93121a8f7e16b6eff2a034a66de9f8a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 3 May 2011 10:04:20 +0200 Subject: [PATCH 01/11] Move BaseOutput from mopidy.gstreamer to mopidy.outputs --- mopidy/gstreamer.py | 40 ----------------------------- mopidy/outputs.py | 61 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 41 deletions(-) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index ddcc6a56..0c356c45 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -22,46 +22,6 @@ default_caps = gst.Caps(""" signed=(boolean)true, rate=(int)44100""") -class BaseOutput(object): - def connect_bin(self, pipeline, element_to_link_to): - """ - Connect output bin to pipeline and given element. - """ - description = 'queue ! %s' % self.describe_bin() - logger.debug('Adding new output to tee: %s', description) - - output = self.parse_bin(description) - self.modify_bin(output) - - pipeline.add(output) - output.sync_state_with_parent() - gst.element_link_many(element_to_link_to, output) - - def parse_bin(self, description): - return gst.parse_bin_from_description(description, True) - - def modify_bin(self, output): - """ - Modifies bin before it is installed if needed - """ - pass - - def describe_bin(self): - """ - Describe bin to be parsed. - - Must be implemented by subclasses. - """ - raise NotImplementedError - - def set_properties(self, element, properties): - """ - Set properties on element if they have a value. - """ - for key, value in properties.items(): - if value: - element.set_property(key, value) - class GStreamer(ThreadingActor): """ diff --git a/mopidy/outputs.py b/mopidy/outputs.py index 5a57f446..3aac4105 100644 --- a/mopidy/outputs.py +++ b/mopidy/outputs.py @@ -1,17 +1,76 @@ +import logging + +import pygst +pygst.require('0.10') +import gst + from mopidy import settings -from mopidy.gstreamer import BaseOutput + +logger = logging.getLogger('mopidy.outputs') + + +class BaseOutput(object): + """TODO adamcik""" + + def connect_bin(self, pipeline, element_to_link_to): + """ + Connect output bin to pipeline and given element. + """ + description = 'queue ! %s' % self.describe_bin() + logger.debug('Adding new output to tee: %s', description) + + output = self.parse_bin(description) + self.modify_bin(output) + + pipeline.add(output) + output.sync_state_with_parent() + gst.element_link_many(element_to_link_to, output) + + def parse_bin(self, description): + return gst.parse_bin_from_description(description, True) + + def modify_bin(self, output): + """ + Modifies bin before it is installed if needed + """ + pass + + def describe_bin(self): + """ + Describe bin to be parsed. + + Must be implemented by subclasses. + """ + raise NotImplementedError + + def set_properties(self, element, properties): + """ + Set properties on element if they have a value. + """ + for key, value in properties.items(): + if value: + element.set_property(key, value) + class LocalOutput(BaseOutput): + """TODO adamcik""" + def describe_bin(self): if settings.LOCAL_OUTPUT_OVERRIDE: return settings.LOCAL_OUTPUT_OVERRIDE return 'autoaudiosink' + class NullOutput(BaseOutput): + """TODO adamcik""" + def describe_bin(self): return 'fakesink' + class ShoutcastOutput(BaseOutput): + """TODO adamcik""" + def describe_bin(self): if settings.SHOUTCAST_OUTPUT_OVERRIDE: return settings.SHOUTCAST_OUTPUT_OVERRIDE From ed5cd8f52b2b73d4aaacff1bf6b11ec8a0a2a61f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 3 May 2011 10:08:21 +0200 Subject: [PATCH 02/11] docs: Refresh docs wrt. mopidy.{gstreamer,outputs} --- docs/api/outputs.rst | 14 +++++--------- docs/modules/gstreamer.rst | 9 +++++++++ docs/modules/outputs.rst | 9 +++++++++ docs/modules/outputs/gstreamer.rst | 9 --------- 4 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 docs/modules/gstreamer.rst create mode 100644 docs/modules/outputs.rst delete mode 100644 docs/modules/outputs/gstreamer.rst diff --git a/docs/api/outputs.rst b/docs/api/outputs.rst index 5ef1606d..b96c909e 100644 --- a/docs/api/outputs.rst +++ b/docs/api/outputs.rst @@ -2,19 +2,15 @@ Output API ********** -Outputs are responsible for playing audio. +Outputs are used by :mod:`mopidy.gstreamer` to output audio in some way. -.. warning:: - - A stable output API is not available yet, as we've only implemented a - single output module. - -.. automodule:: mopidy.outputs.base - :synopsis: Base class for outputs +.. autoclass:: mopidy.outputs.BaseOutput :members: Output implementations ====================== -* :mod:`mopidy.outputs.gstreamer` +* :class:`mopidy.outputs.LocalOutput` +* :class:`mopidy.outputs.NullOutput` +* :class:`mopidy.outputs.ShoutcastOutput` diff --git a/docs/modules/gstreamer.rst b/docs/modules/gstreamer.rst new file mode 100644 index 00000000..adbf5fda --- /dev/null +++ b/docs/modules/gstreamer.rst @@ -0,0 +1,9 @@ +******************************************** +:mod:`mopidy.gstreamer` -- GStreamer adapter +******************************************** + +.. inheritance-diagram:: mopidy.gstreamer + +.. automodule:: mopidy.gstreamer + :synopsis: GStreamer adapter + :members: diff --git a/docs/modules/outputs.rst b/docs/modules/outputs.rst new file mode 100644 index 00000000..0a6986dd --- /dev/null +++ b/docs/modules/outputs.rst @@ -0,0 +1,9 @@ +************************************************ +:mod:`mopidy.outputs` -- GStreamer audio outputs +************************************************ + +.. inheritance-diagram:: mopidy.outputs + +.. automodule:: mopidy.outputs + :synopsis: GStreamer audio outputs + :members: diff --git a/docs/modules/outputs/gstreamer.rst b/docs/modules/outputs/gstreamer.rst deleted file mode 100644 index 69c77dad..00000000 --- a/docs/modules/outputs/gstreamer.rst +++ /dev/null @@ -1,9 +0,0 @@ -********************************************************************* -:mod:`mopidy.outputs.gstreamer` -- GStreamer output for all platforms -********************************************************************* - -.. inheritance-diagram:: mopidy.outputs.gstreamer - -.. automodule:: mopidy.outputs.gstreamer - :synopsis: GStreamer output for all platforms - :members: From 32cf5588eb5a7e01dcbaa10857c44502a558190a Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 3 May 2011 23:25:33 +0200 Subject: [PATCH 03/11] Recovered old BastOutput docstrings and added them to GStreamer class where apropriate --- mopidy/gstreamer.py | 58 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 0c356c45..bd1467b2 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -29,7 +29,7 @@ class GStreamer(ThreadingActor): **Settings:** - - :attr:`mopidy.settings.GSTREAMER_AUDIO_SINK` + - :attr:`mopidy.settings.OUTPUTS` """ @@ -105,15 +105,27 @@ class GStreamer(ThreadingActor): return backend_refs[0].proxy() def play_uri(self, uri): - """Play audio at URI""" + """ + Play audio at URI + + :param uri: the URI to play + :type uri: string + :rtype: :class:`True` if successful, else :class:`False` + """ self.set_state('READY') self.gst_uridecodebin.set_property('uri', uri) return self.set_state('PLAYING') - def deliver_data(self, caps_string, data): - """Deliver audio data to be played""" + def deliver_data(self, capabilities, data): + """ + Deliver audio data to be played + + :param capabilities: a GStreamer capabilities string + :type capabilities: string + :param data: raw audio data to be played + """ source = self.gst_pipeline.get_by_name('source') - caps = gst.caps_from_string(caps_string) + caps = gst.caps_from_string(capabilities) buffer_ = gst.Buffer(buffer(data)) buffer_.set_caps(caps) source.set_property('caps', caps) @@ -129,6 +141,11 @@ class GStreamer(ThreadingActor): self.gst_pipeline.get_by_name('source').emit('end-of-stream') def get_position(self): + """ + Get position in milliseconds. + + :rtype: int + """ if self.gst_pipeline.get_state()[1] == gst.STATE_NULL: return 0 try: @@ -139,6 +156,13 @@ class GStreamer(ThreadingActor): return 0 def set_position(self, position): + """ + Set position in milliseconds. + + :param position: the position in milliseconds + :type volume: int + :rtype: :class:`True` if successful, else :class:`False` + """ self.gst_pipeline.get_state() # block until state changes are done handeled = self.gst_pipeline.seek_simple(gst.Format(gst.FORMAT_TIME), gst.SEEK_FLAG_FLUSH, position * gst.MSECOND) @@ -172,15 +196,35 @@ class GStreamer(ThreadingActor): return True def get_volume(self): - """Get volume in range [0..100]""" + """ + Get volume level for software mixer. + + :rtype: int in range [0..100] + """ return int(self.gst_volume.get_property('volume') * 100) def set_volume(self, volume): - """Set volume in range [0..100]""" + """ + Set volume level for software mixer. + + :param volume: the volume in the range [0..100] + :type volume: int + :rtype: :class:`True` if successful, else :class:`False` + """ self.gst_volume.set_property('volume', volume / 100.0) return True def set_metadata(self, track): + """ + Set track metadata for currently playing song. + + Only needs to be called by sources such as appsrc which don't already + inject tags in pipeline. + + :param track: Track containing metadata for current song. + :type track: :class:`mopidy.modes.Track` + """ + # FIXME what if we want to unset taginject tags? tags = u'artist="%(artist)s",title="%(title)s"' % { 'artist': u', '.join([a.name for a in track.artists]), 'title': track.name, From 9b47870404389bbbf4d07211a63cbaa3b5ee25d5 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 3 May 2011 23:27:23 +0200 Subject: [PATCH 04/11] Make mopidy.ouputs a module again --- mopidy/{outputs.py => outputs/__init__.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename mopidy/{outputs.py => outputs/__init__.py} (100%) diff --git a/mopidy/outputs.py b/mopidy/outputs/__init__.py similarity index 100% rename from mopidy/outputs.py rename to mopidy/outputs/__init__.py From 8e8a7cd037fbc2c34173960be70d1629339d0a4d Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 3 May 2011 23:41:01 +0200 Subject: [PATCH 05/11] Add docstring for BaseOutput --- mopidy/outputs/__init__.py | 49 ++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/mopidy/outputs/__init__.py b/mopidy/outputs/__init__.py index 3aac4105..8ff43a5f 100644 --- a/mopidy/outputs/__init__.py +++ b/mopidy/outputs/__init__.py @@ -10,45 +10,68 @@ logger = logging.getLogger('mopidy.outputs') class BaseOutput(object): - """TODO adamcik""" + """Base class for providing support for multiple pluggable outputs.""" - def connect_bin(self, pipeline, element_to_link_to): + def connect_bin(self, pipeline, element): """ Connect output bin to pipeline and given element. + + In normal cases the element will probably be a `tee`, + thus allowing us to connect any number of outputs. This + however is why each bin is forced to have its own `queue` + after the `tee`. + + :param pipeline: gst.Pipeline to add output to. + :type pipeline: :class:`gst.Pipeline` + :param element: gst.Element in pipeline to connect output to. + :type element: :class:`gst.Element` """ description = 'queue ! %s' % self.describe_bin() logger.debug('Adding new output to tee: %s', description) - output = self.parse_bin(description) + output = gst.parse_bin_from_description(description, True) self.modify_bin(output) pipeline.add(output) - output.sync_state_with_parent() - gst.element_link_many(element_to_link_to, output) - - def parse_bin(self, description): - return gst.parse_bin_from_description(description, True) + output.sync_state_with_parent() # Required to add to running pipe + gst.element_link_many(element, output) def modify_bin(self, output): """ - Modifies bin before it is installed if needed + Modifies 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. """ pass def describe_bin(self): """ - Describe bin to be parsed. + Return text string describing bin in gst-launch format. - Must be implemented by subclasses. + For simple cases this can just be a plain sink such as `autoaudiosink` + or it can be a chain `element1 ! element2 ! sink`. See `man + gst-launch0.10` for details on format. + + *MUST be implemented by subclass.* """ raise NotImplementedError def set_properties(self, element, properties): """ - Set properties on element if they have a value. + Helper to allow for simple setting of properties on elements. + + Will call `set_property` on the element for each key that has a value + that is not None. + + :param element: gst.Element to set properties on. + :type element: :class:`gst.Element` + :param properties: Dictionary of properties to set on element. + :type element: dict """ for key, value in properties.items(): - if value: + if value is not None: element.set_property(key, value) From f69e01c501b14921b74c57e3536ba703af33a92a Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 3 May 2011 23:56:24 +0200 Subject: [PATCH 06/11] docstrings for outputs --- mopidy/outputs/__init__.py | 41 +++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/mopidy/outputs/__init__.py b/mopidy/outputs/__init__.py index 8ff43a5f..9623681f 100644 --- a/mopidy/outputs/__init__.py +++ b/mopidy/outputs/__init__.py @@ -76,7 +76,21 @@ class BaseOutput(object): class LocalOutput(BaseOutput): - """TODO adamcik""" + """ + 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. + + Advanced: + + However, there are chases when you want to explicitly set what GStreamer + should use. This can be achieved by setting `settings.LOCAL_OUTPUT_OVERRIDE` + to the sink you want to use. Some of the possible values are: alsasink, + esdsink, jackaudiosink, oss4sink and osssink. Exact values that will work + on your system will depend on your sound setup and installed GStreamer + plugins. Run `gst-inspect0.10` for list of all available plugins. + """ def describe_bin(self): if settings.LOCAL_OUTPUT_OVERRIDE: @@ -85,14 +99,35 @@ class LocalOutput(BaseOutput): class NullOutput(BaseOutput): - """TODO adamcik""" + """ + Fall-back null output. + + This output will not output anything. It is intended as a fall-back for + when setup of all other outputs have failed and should not be used by end + users. Inserting this output in such a case ensures that the pipeline does + not crash. + """ def describe_bin(self): return 'fakesink' class ShoutcastOutput(BaseOutput): - """TODO adamcik""" + """ + 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. + + Advanced: + + If you need to do something special that this output has not taken into + account the setting `settings.SHOUTCAST_OUTPUT_OVERRIDE` has been provided + to allow for manual setup of the bin using a gst-launch string. If this + setting is set all other shoutcast settings will be ignored. + """ def describe_bin(self): if settings.SHOUTCAST_OUTPUT_OVERRIDE: From 0a2a582b25a52517bdebcaf864db4e817fc028e6 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 3 May 2011 23:57:38 +0200 Subject: [PATCH 07/11] Remove last refrenceto GStreamerOutput --- mopidy/gstreamer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index bd1467b2..4c69da91 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -41,9 +41,9 @@ class GStreamer(ThreadingActor): def _setup_gstreamer(self): """ - **Warning:** :class:`GStreamerOutput` requires + **Warning:** :class:`GStreamer` requires :class:`mopidy.utils.process.GObjectEventThread` to be running. This is - not enforced by :class:`GStreamerOutput` itself. + not enforced by :class:`GStreamer` itself. """ base_pipeline = ' ! '.join([ 'audioconvert name=convert', From 4df54ffdef57524683e0a9962804e61680dd4991 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 4 May 2011 00:00:38 +0200 Subject: [PATCH 08/11] Fix refrence to mopidy.outputs.CustomOutput --- mopidy/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/settings.py b/mopidy/settings.py index c0ee3569..78abb6b7 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -54,7 +54,7 @@ FRONTENDS = ( u'mopidy.frontends.lastfm.LastfmFrontend', ) -#: Which GStreamer bin description to use in :mod:`mopidy.outputs.CustomOutput`. +#: Which GStreamer bin description to use in :class:`mopidy.outputs.LocalOutput`. #: #: Default:: #: From fad015ba7e424a4ca9e9e0872a77821874ad4e0f Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 4 May 2011 00:15:21 +0200 Subject: [PATCH 09/11] Update changelog with modularised output and time error bug --- docs/changes.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index 12da4e6d..31d0a015 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -10,9 +10,16 @@ This change log is used to track all major changes to Mopidy. No description yet. +**Important changes** + +- Mopidy now supports running with 1-n outputs at the same time. This feature + was mainly added to facilitate Shoutcast support, which Mopidy has also + gained. In its current state outputs can not be toggled during runtime. + **Changes** -No changes yet. +- Fix local backend time query errors that where coming from stopped pipeline. + (Fixes: :issue:`87`) 0.4.0 (2011-04-27) From 173b5cfb827adc2e6f15d382640965c3a977276c Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 4 May 2011 00:23:21 +0200 Subject: [PATCH 10/11] Minor doc fixes for outputs --- mopidy/outputs/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mopidy/outputs/__init__.py b/mopidy/outputs/__init__.py index 9623681f..f7a796a8 100644 --- a/mopidy/outputs/__init__.py +++ b/mopidy/outputs/__init__.py @@ -43,6 +43,9 @@ class BaseOutput(object): 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. + + :param output: gst.Bin to modify in some way. + :type output: :class:`gst.Bin` """ pass @@ -68,7 +71,7 @@ class BaseOutput(object): :param element: gst.Element to set properties on. :type element: :class:`gst.Element` :param properties: Dictionary of properties to set on element. - :type element: dict + :type properties: dict """ for key, value in properties.items(): if value is not None: From 6feb4f4c419c8c6442a6814d4d5b93ec90823e2c Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 4 May 2011 11:18:17 +0200 Subject: [PATCH 11/11] Add pulsesink to docstring and mention gst-launch --- mopidy/outputs/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mopidy/outputs/__init__.py b/mopidy/outputs/__init__.py index f7a796a8..e3747463 100644 --- a/mopidy/outputs/__init__.py +++ b/mopidy/outputs/__init__.py @@ -90,9 +90,10 @@ class LocalOutput(BaseOutput): However, there are chases when you want to explicitly set what GStreamer should use. This can be achieved by setting `settings.LOCAL_OUTPUT_OVERRIDE` to the sink you want to use. Some of the possible values are: alsasink, - esdsink, jackaudiosink, oss4sink and osssink. Exact values that will work - on your system will depend on your sound setup and installed GStreamer - plugins. Run `gst-inspect0.10` for list of all available plugins. + esdsink, jackaudiosink, oss4sink, osssink and pulsesink. Exact values that + will work on your system will depend on your sound setup and installed + GStreamer plugins. Run `gst-inspect0.10` for list of all available plugins. + Also note that this accepts properties and bins in `gst-launch` format. """ def describe_bin(self):