From 9f77f801ba2d7cdd71b77ac81999f174f1d5aba7 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 23 Apr 2011 21:45:58 +0200 Subject: [PATCH 01/25] Swith to tee based pipeline to allow for multiple outputs --- mopidy/outputs/gstreamer.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index a6d1e9dd..0a4c0d85 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -35,21 +35,28 @@ class GStreamerOutput(ThreadingActor, BaseOutput): :class:`mopidy.utils.process.GObjectEventThread` to be running. This is not enforced by :class:`GStreamerOutput` itself. """ - - logger.debug(u'Setting up GStreamer pipeline') - - self.gst_pipeline = gst.parse_launch(' ! '.join([ + base_pipeline = ' ! '.join([ 'audioconvert name=convert', 'volume name=volume', - settings.GSTREAMER_AUDIO_SINK, - ])) + 'taginject name=tag', + 'tee name=tee', + ]) - pad = self.gst_pipeline.get_by_name('convert').get_pad('sink') + logger.debug(u'Setting up base GStreamer pipeline: %s', base_pipeline) + + self.gst_pipeline = gst.parse_launch(base_pipeline) + + tee = self.gst_pipeline.get_by_name('tee') + convert = self.gst_pipeline.get_by_name('convert') uridecodebin = gst.element_factory_make('uridecodebin', 'uri') uridecodebin.connect('pad-added', self._process_new_pad, pad) self.gst_pipeline.add(uridecodebin) + output_bin = gst.parse_bin_from_description('queue ! %s' % + settings.GSTREAMER_AUDIO_SINK, True) + gst.element_link_many(tee, output_bin) + # Setup bus and message processor gst_bus = self.gst_pipeline.get_bus() gst_bus.add_signal_watch() From ecdf689301895e82a786ef6462723a5e809af46b Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 23 Apr 2011 23:43:29 +0200 Subject: [PATCH 02/25] Factor out method to add output bin --- mopidy/outputs/gstreamer.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 0a4c0d85..7f1d35eb 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -46,22 +46,28 @@ class GStreamerOutput(ThreadingActor, BaseOutput): self.gst_pipeline = gst.parse_launch(base_pipeline) - tee = self.gst_pipeline.get_by_name('tee') - convert = self.gst_pipeline.get_by_name('convert') + self.gst_tee = self.gst_pipeline.get_by_name('tee') + self.gst_convert = self.gst_pipeline.get_by_name('convert') uridecodebin = gst.element_factory_make('uridecodebin', 'uri') uridecodebin.connect('pad-added', self._process_new_pad, pad) self.gst_pipeline.add(uridecodebin) - output_bin = gst.parse_bin_from_description('queue ! %s' % - settings.GSTREAMER_AUDIO_SINK, True) - gst.element_link_many(tee, output_bin) + self._add_output(settings.GSTREAMER_AUDIO_SINK) # Setup bus and message processor gst_bus = self.gst_pipeline.get_bus() gst_bus.add_signal_watch() gst_bus.connect('message', self._process_gstreamer_message) + def _add_output(self, description): + bin = 'queue ! %s' % description + logger.debug('Adding output bin to tee: %s', bin) + output = gst.parse_bin_from_description(bin, True) + self.gst_pipeline.add(output) + output.sync_state_with_parent() + gst.element_link_many(self.gst_tee, output) + def _process_new_pad(self, source, pad, target_pad): pad.link(target_pad) From a81113e1a7a3117ef25b77ee2c17ab030c7e833d Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Apr 2011 02:15:26 +0200 Subject: [PATCH 03/25] Add _build_shoutcast_description to construct shoutcast bin --- mopidy/outputs/gstreamer.py | 23 ++++++++++++++ mopidy/settings.py | 45 ++++++++++++++++++++++++++ tests/outputs/gstreamer_test.py | 56 +++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 7f1d35eb..cdf3829e 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -68,6 +68,29 @@ class GStreamerOutput(ThreadingActor, BaseOutput): output.sync_state_with_parent() gst.element_link_many(self.gst_tee, output) + def _build_shoutcast_description(self): + if settings.SHOUTCAST_OVERRIDE: + return settings.SHOUTCAST_OVERRIDE + + if not settings.SHOUTCAST_SERVER: + return None + + description = ['%s ! shout2send' % settings.SHOUTCAST_ENCODER] + options = { + u'ip': settings.SHOUTCAST_SERVER, + u'mount': settings.SHOUTCAST_MOUNT, + u'port': settings.SHOUTCAST_PORT, + u'username': settings.SHOUTCAST_USER, + u'password': settings.SHOUTCAST_PASSWORD, + } + + for key, value in sorted(options.items()): + if value: + description.append('%s="%s"' % (key, value)) + + return u' '.join(description) + + def _process_new_pad(self, source, pad, target_pad): pad.link(target_pad) diff --git a/mopidy/settings.py b/mopidy/settings.py index 6e33ffaa..cd8f445e 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -174,6 +174,51 @@ MPD_SERVER_PASSWORD = None #: Default: 6600 MPD_SERVER_PORT = 6600 +#: Servar that runs Shoutcast server to send stream to. +#: +#: Default: :class:`None`, disables shoutcast if set to :class:`None` +SHOUTCAST_SERVER = None + +#: User to authenticate as against Shoutcast server. +#: +#: Default: 'source' +SHOUTCAST_USER = u'source' + +#: Password to authenticate with against Shoutcast server. +#: +#: Default: 'hackme' +SHOUTCAST_PASSWORD = u'hackme' + +#: Port to use for streaming to Shoutcast server. +#: +#: Default: 8000 +SHOUTCAST_PORT = 8000 + +#: Mountpoint to use for the stream on the Shoutcast server. +#: +#: Default: /stream +SHOUTCAST_MOUNT = u'/stream' + +#: Encoder to use to process audio data before streaming. +#: +#: Default: vorbisenc ! oggmux +SHOUTCAST_ENCODER = u'vorbisenc ! oggmux' + +#: Overrides to allow advanced setup of shoutcast. Using this settings implies +#: that all other SHOUTCAST_* settings will be ignored. +#: +#: Examples: +#: +#: ``vorbisenc ! oggmux ! shout2send mount=/stream port=8000`` +#: Encode with vorbis and use ogg mux. +#: ``lame bitrate=320 ! shout2send mount=/stream port=8000`` +#: Encode with lame to bitrate=320. +#: +#: For all options see gst-inspect-0.10 lame, vorbisenc and shout2send. +#: +#: Default: :class:`None` +SHOUTCAST_OVERRIDE = None + #: Path to the Spotify cache. #: #: Used by :mod:`mopidy.backends.spotify`. diff --git a/tests/outputs/gstreamer_test.py b/tests/outputs/gstreamer_test.py index 31a16756..b39e5583 100644 --- a/tests/outputs/gstreamer_test.py +++ b/tests/outputs/gstreamer_test.py @@ -60,3 +60,59 @@ class GStreamerOutputTest(unittest.TestCase): @SkipTest def test_set_position(self): pass # TODO + + def test_build_shoutcast_description_without_server(self): + self.assertEqual(None, self.output._build_shoutcast_description()) + + def test_build_shoutcast_description_with_server(self): + settings.SHOUTCAST_SERVER = '127.0.0.1' + + expected = u'%s ! ' % settings.SHOUTCAST_ENCODER + \ + u'shout2send ip="127.0.0.1" mount="/stream" ' \ + u'password="hackme" port="8000" username="source"' + result = self.output._build_shoutcast_description() + self.assertEqual(expected, result) + + def test_build_shoutcast_description_with_mount(self): + settings.SHOUTCAST_SERVER = '127.0.0.1' + settings.SHOUTCAST_MOUNT = '/stream.mp3' + + expected = u'%s ! ' % settings.SHOUTCAST_ENCODER + \ + u'shout2send ip="127.0.0.1" mount="/stream.mp3" ' \ + u'password="hackme" port="8000" username="source"' + result = self.output._build_shoutcast_description() + self.assertEqual(expected, result) + + def test_build_shoutcast_description_with_user_and_passwod(self): + settings.SHOUTCAST_SERVER = '127.0.0.1' + settings.SHOUTCAST_USER = 'john' + settings.SHOUTCAST_PASSWORD = 'doe' + + expected = u'%s ! ' % settings.SHOUTCAST_ENCODER + \ + u'shout2send ip="127.0.0.1" mount="/stream" ' \ + u'password="doe" port="8000" username="john"' + result = self.output._build_shoutcast_description() + self.assertEqual(expected, result) + + def test_build_shoutcast_description_unset_user_and_pass(self): + settings.SHOUTCAST_SERVER = '127.0.0.1' + settings.SHOUTCAST_USER = None + settings.SHOUTCAST_PASSWORD = None + + expected = u'%s ! shout2send ' % settings.SHOUTCAST_ENCODER + \ + u'ip="127.0.0.1" mount="/stream" port="8000"' + result = self.output._build_shoutcast_description() + self.assertEqual(expected, result) + + def test_build_shoutcast_description_with_override(self): + settings.SHOUTCAST_OVERRIDE = 'foobar' + + result = self.output._build_shoutcast_description() + self.assertEqual('foobar', result) + + def test_build_shoutcast_description_with_override_and_server(self): + settings.SHOUTCAST_OVERRIDE = 'foobar' + settings.SHOUTCAST_SERVER = '127.0.0.1' + + result = self.output._build_shoutcast_description() + self.assertEqual('foobar', result) From 700792f4a0372495957325392fd87f93d236168b Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Apr 2011 02:55:14 +0200 Subject: [PATCH 04/25] Add basic streaming support --- mopidy/outputs/gstreamer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index cdf3829e..984efef1 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -55,6 +55,10 @@ class GStreamerOutput(ThreadingActor, BaseOutput): self._add_output(settings.GSTREAMER_AUDIO_SINK) + shoutcast_bin = self._build_shoutcast_description() + if shoutcast_bin: + self._add_output(shoutcast_bin) + # Setup bus and message processor gst_bus = self.gst_pipeline.get_bus() gst_bus.add_signal_watch() From b58436aaf3b09a56bfe17df171a579188d98b8c4 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Apr 2011 03:03:43 +0200 Subject: [PATCH 05/25] Use audioconvert to ensure that volume element is handeled --- mopidy/outputs/gstreamer.py | 2 +- tests/outputs/gstreamer_test.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 984efef1..9430f47c 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -79,7 +79,7 @@ class GStreamerOutput(ThreadingActor, BaseOutput): if not settings.SHOUTCAST_SERVER: return None - description = ['%s ! shout2send' % settings.SHOUTCAST_ENCODER] + description = ['audioconvert ! %s ! shout2send' % settings.SHOUTCAST_ENCODER] options = { u'ip': settings.SHOUTCAST_SERVER, u'mount': settings.SHOUTCAST_MOUNT, diff --git a/tests/outputs/gstreamer_test.py b/tests/outputs/gstreamer_test.py index b39e5583..14493665 100644 --- a/tests/outputs/gstreamer_test.py +++ b/tests/outputs/gstreamer_test.py @@ -67,7 +67,7 @@ class GStreamerOutputTest(unittest.TestCase): def test_build_shoutcast_description_with_server(self): settings.SHOUTCAST_SERVER = '127.0.0.1' - expected = u'%s ! ' % settings.SHOUTCAST_ENCODER + \ + expected = u'audioconvert ! %s ! ' % settings.SHOUTCAST_ENCODER + \ u'shout2send ip="127.0.0.1" mount="/stream" ' \ u'password="hackme" port="8000" username="source"' result = self.output._build_shoutcast_description() @@ -77,7 +77,7 @@ class GStreamerOutputTest(unittest.TestCase): settings.SHOUTCAST_SERVER = '127.0.0.1' settings.SHOUTCAST_MOUNT = '/stream.mp3' - expected = u'%s ! ' % settings.SHOUTCAST_ENCODER + \ + expected = u'audioconvert ! %s ! ' % settings.SHOUTCAST_ENCODER + \ u'shout2send ip="127.0.0.1" mount="/stream.mp3" ' \ u'password="hackme" port="8000" username="source"' result = self.output._build_shoutcast_description() @@ -88,7 +88,7 @@ class GStreamerOutputTest(unittest.TestCase): settings.SHOUTCAST_USER = 'john' settings.SHOUTCAST_PASSWORD = 'doe' - expected = u'%s ! ' % settings.SHOUTCAST_ENCODER + \ + expected = u'audioconvert ! %s ! ' % settings.SHOUTCAST_ENCODER + \ u'shout2send ip="127.0.0.1" mount="/stream" ' \ u'password="doe" port="8000" username="john"' result = self.output._build_shoutcast_description() @@ -99,7 +99,7 @@ class GStreamerOutputTest(unittest.TestCase): settings.SHOUTCAST_USER = None settings.SHOUTCAST_PASSWORD = None - expected = u'%s ! shout2send ' % settings.SHOUTCAST_ENCODER + \ + expected = u'audioconvert ! %s ! shout2send ' % settings.SHOUTCAST_ENCODER + \ u'ip="127.0.0.1" mount="/stream" port="8000"' result = self.output._build_shoutcast_description() self.assertEqual(expected, result) From 3a44f130aa264aa570afbd161394b480beacc674 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Apr 2011 03:07:14 +0200 Subject: [PATCH 06/25] Refactor shoutcast tests --- tests/outputs/gstreamer_test.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tests/outputs/gstreamer_test.py b/tests/outputs/gstreamer_test.py index 14493665..9f380815 100644 --- a/tests/outputs/gstreamer_test.py +++ b/tests/outputs/gstreamer_test.py @@ -77,32 +77,23 @@ class GStreamerOutputTest(unittest.TestCase): settings.SHOUTCAST_SERVER = '127.0.0.1' settings.SHOUTCAST_MOUNT = '/stream.mp3' - expected = u'audioconvert ! %s ! ' % settings.SHOUTCAST_ENCODER + \ - u'shout2send ip="127.0.0.1" mount="/stream.mp3" ' \ - u'password="hackme" port="8000" username="source"' - result = self.output._build_shoutcast_description() - self.assertEqual(expected, result) + self.check_shoutcast_options(u'ip="127.0.0.1" mount="/stream.mp3" ' + u'password="hackme" port="8000" username="source"') def test_build_shoutcast_description_with_user_and_passwod(self): settings.SHOUTCAST_SERVER = '127.0.0.1' settings.SHOUTCAST_USER = 'john' settings.SHOUTCAST_PASSWORD = 'doe' - expected = u'audioconvert ! %s ! ' % settings.SHOUTCAST_ENCODER + \ - u'shout2send ip="127.0.0.1" mount="/stream" ' \ - u'password="doe" port="8000" username="john"' - result = self.output._build_shoutcast_description() - self.assertEqual(expected, result) + self.check_shoutcast_options('ip="127.0.0.1" mount="/stream" ' + u'password="doe" port="8000" username="john"') def test_build_shoutcast_description_unset_user_and_pass(self): settings.SHOUTCAST_SERVER = '127.0.0.1' settings.SHOUTCAST_USER = None settings.SHOUTCAST_PASSWORD = None - expected = u'audioconvert ! %s ! shout2send ' % settings.SHOUTCAST_ENCODER + \ - u'ip="127.0.0.1" mount="/stream" port="8000"' - result = self.output._build_shoutcast_description() - self.assertEqual(expected, result) + self.check_shoutcast_options(u'ip="127.0.0.1" mount="/stream" port="8000"') def test_build_shoutcast_description_with_override(self): settings.SHOUTCAST_OVERRIDE = 'foobar' @@ -116,3 +107,10 @@ class GStreamerOutputTest(unittest.TestCase): result = self.output._build_shoutcast_description() self.assertEqual('foobar', result) + + def check_shoutcast_options(self, options): + expected = u'audioconvert ! %s ! shout2send ' % settings.SHOUTCAST_ENCODER + expected += options + + result = self.output._build_shoutcast_description() + self.assertEqual(expected, result) From 971132d539f017fe82e257ebdcb190ec49b2b800 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Apr 2011 03:11:55 +0200 Subject: [PATCH 07/25] Support just local or shoutcast audio output --- mopidy/outputs/gstreamer.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 9430f47c..e4563744 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -53,11 +53,16 @@ class GStreamerOutput(ThreadingActor, BaseOutput): uridecodebin.connect('pad-added', self._process_new_pad, pad) self.gst_pipeline.add(uridecodebin) - self._add_output(settings.GSTREAMER_AUDIO_SINK) + localaudio = settings.GSTREAMER_AUDIO_SINK + shoutcast = self._build_shoutcast_description() - shoutcast_bin = self._build_shoutcast_description() - if shoutcast_bin: - self._add_output(shoutcast_bin) + if localaudio: + self._add_output(localaudio) + if shoutcast: + self._add_output(shoutcast) + if not localaudio and not shoutcast: + logger.error('No proper output channels have been setup.') + self._add_output('fakesink') # Setup bus and message processor gst_bus = self.gst_pipeline.get_bus() From 9f862fe1b1d63f38e011aa96efb948d11f7fa98b Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Apr 2011 03:17:17 +0200 Subject: [PATCH 08/25] Update settings docs for shoutcast --- mopidy/settings.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/mopidy/settings.py b/mopidy/settings.py index cd8f445e..7daa2257 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -176,32 +176,44 @@ MPD_SERVER_PORT = 6600 #: Servar that runs Shoutcast server to send stream to. #: -#: Default: :class:`None`, disables shoutcast if set to :class:`None` +#: Default:: +#: +#: SHOUTCAST_SERVER = None # Must be set to enable shoutcase SHOUTCAST_SERVER = None #: User to authenticate as against Shoutcast server. #: -#: Default: 'source' +#: Default:: +#: +#: SHOUTCAST_USER = u'source' SHOUTCAST_USER = u'source' #: Password to authenticate with against Shoutcast server. #: -#: Default: 'hackme' +#: Default:: +#: +#: SHOUTCAST_PASSWORD = u'hackme' SHOUTCAST_PASSWORD = u'hackme' #: Port to use for streaming to Shoutcast server. #: -#: Default: 8000 +#: Default:: +#: +#: SHOUTCAST_PORT = 8000 SHOUTCAST_PORT = 8000 #: Mountpoint to use for the stream on the Shoutcast server. #: -#: Default: /stream +#: Default:: +#: +#: SHOUTCAST_MOUNT = u'/stream' SHOUTCAST_MOUNT = u'/stream' #: Encoder to use to process audio data before streaming. #: -#: Default: vorbisenc ! oggmux +#: Default:: +#: +#: SHOUTCAST_ENCODER = u'vorbisenc ! oggmux' SHOUTCAST_ENCODER = u'vorbisenc ! oggmux' #: Overrides to allow advanced setup of shoutcast. Using this settings implies @@ -216,7 +228,9 @@ SHOUTCAST_ENCODER = u'vorbisenc ! oggmux' #: #: For all options see gst-inspect-0.10 lame, vorbisenc and shout2send. #: -#: Default: :class:`None` +#: Default:: +#: +#: SHOUTCAST_OVERRIDE = None SHOUTCAST_OVERRIDE = None #: Path to the Spotify cache. From 44316c7cfc46908a41ad2e93d1d33c78c6dd3adb Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Apr 2011 16:39:05 +0200 Subject: [PATCH 09/25] Reduce number of get_by_name lookups --- mopidy/outputs/gstreamer.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index e4563744..f5e7a1a3 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -48,10 +48,13 @@ class GStreamerOutput(ThreadingActor, BaseOutput): self.gst_tee = self.gst_pipeline.get_by_name('tee') self.gst_convert = self.gst_pipeline.get_by_name('convert') + self.gst_volume = self.gst_pipeline.get_by_name('volume') + self.gst_taginject = self.gst_pipeline.get_by_name('tag') - uridecodebin = gst.element_factory_make('uridecodebin', 'uri') - uridecodebin.connect('pad-added', self._process_new_pad, pad) - self.gst_pipeline.add(uridecodebin) + self.gst_uridecodebin = gst.element_factory_make('uridecodebin', 'uri') + self.gst_uridecodebin.connect('pad-added', self._process_new_pad, + self.gst_convert.get_pad('sink')) + self.gst_pipeline.add(self.gst_uridecodebin) localaudio = settings.GSTREAMER_AUDIO_SINK shoutcast = self._build_shoutcast_description() @@ -124,7 +127,7 @@ class GStreamerOutput(ThreadingActor, BaseOutput): def play_uri(self, uri): """Play audio at URI""" self.set_state('READY') - self.gst_pipeline.get_by_name('uri').set_property('uri', uri) + self.gst_uridecodebin.set_property('uri', uri) return self.set_state('PLAYING') def deliver_data(self, caps_string, data): @@ -188,11 +191,9 @@ class GStreamerOutput(ThreadingActor, BaseOutput): def get_volume(self): """Get volume in range [0..100]""" - gst_volume = self.gst_pipeline.get_by_name('volume') - return int(gst_volume.get_property('volume') * 100) + return int(self.gst_volume.get_property('volume') * 100) def set_volume(self, volume): """Set volume in range [0..100]""" - gst_volume = self.gst_pipeline.get_by_name('volume') - gst_volume.set_property('volume', volume / 100.0) + self.gst_volume.set_property('volume', volume / 100.0) return True From 85970dbc3bcc52ae43b3de073cdc426f61cc1a5b Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Apr 2011 16:39:42 +0200 Subject: [PATCH 10/25] Switch to lame encoding for shoutcast --- mopidy/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/settings.py b/mopidy/settings.py index 7daa2257..7f41cf69 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -213,8 +213,8 @@ SHOUTCAST_MOUNT = u'/stream' #: #: Default:: #: -#: SHOUTCAST_ENCODER = u'vorbisenc ! oggmux' -SHOUTCAST_ENCODER = u'vorbisenc ! oggmux' +#: SHOUTCAST_ENCODER = u'lame mode=stereo bitrate=320' +SHOUTCAST_ENCODER = u'lame mode=stereo bitrate=320' #: Overrides to allow advanced setup of shoutcast. Using this settings implies #: that all other SHOUTCAST_* settings will be ignored. From f4db449f0e80a9d23974d0f7532bf4ba7cced006 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Apr 2011 16:40:37 +0200 Subject: [PATCH 11/25] Add set_metadata to allow taginjection for shoutcast stream --- mopidy/backends/spotify/playback.py | 1 + mopidy/outputs/base.py | 14 ++++++++++++++ mopidy/outputs/gstreamer.py | 9 +++++++++ 3 files changed, 24 insertions(+) diff --git a/mopidy/backends/spotify/playback.py b/mopidy/backends/spotify/playback.py index b02c2d9f..f4db490c 100644 --- a/mopidy/backends/spotify/playback.py +++ b/mopidy/backends/spotify/playback.py @@ -21,6 +21,7 @@ class SpotifyPlaybackProvider(BasePlaybackProvider): Link.from_string(track.uri).as_track()) self.backend.spotify.session.play(1) self.backend.output.play_uri('appsrc://') + self.backend.output.set_metadata(track) return True except SpotifyError as e: logger.warning('Play %s failed: %s', track.uri, e) diff --git a/mopidy/outputs/base.py b/mopidy/outputs/base.py index fbc86688..f81fe905 100644 --- a/mopidy/outputs/base.py +++ b/mopidy/outputs/base.py @@ -89,3 +89,17 @@ class BaseOutput(object): :rtype: :class:`True` if successful, else :class:`False` """ raise NotImplementedError + + 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. + + *MUST be implemented by subclass.* + + :param track: Track containing metadata for current song. + :type track: :class:`mopidy.modes.Track` + """ + raise NotImplementedError diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index f5e7a1a3..c92e51d4 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -197,3 +197,12 @@ class GStreamerOutput(ThreadingActor, BaseOutput): """Set volume in range [0..100]""" self.gst_volume.set_property('volume', volume / 100.0) return True + + def set_metadata(self, track): + tags = u'artist="%(artist)s",title="%(title)s",album="%(album)s"' % { + 'artist': u', '.join([a.name for a in track.artists]), + 'title': track.name, + 'album': track.album.name, + } + logger.debug('Setting tags to: %s', tags) + self.gst_taginject.set_property('tags', tags) From 7016a2081179eacd6f9940727fac6de9e73d1813 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Apr 2011 21:30:30 +0200 Subject: [PATCH 12/25] Kill of BaseOutput --- mopidy/backends/local/__init__.py | 4 +- mopidy/backends/spotify/__init__.py | 4 +- mopidy/outputs/base.py | 105 ------------------------ mopidy/outputs/dummy.py | 63 -------------- mopidy/outputs/gstreamer.py | 3 +- tests/backends/base/current_playlist.py | 4 +- tests/backends/base/playback.py | 4 +- 7 files changed, 9 insertions(+), 178 deletions(-) delete mode 100644 mopidy/outputs/base.py delete mode 100644 mopidy/outputs/dummy.py diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 2fa96dab..3d10a63c 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -12,7 +12,7 @@ from mopidy.backends.base import (Backend, CurrentPlaylistController, BasePlaybackProvider, StoredPlaylistsController, BaseStoredPlaylistsProvider) from mopidy.models import Playlist, Track, Album -from mopidy.outputs.base import BaseOutput +from mopidy.outputs.gstreamer import GStreamerOutput from .translator import parse_m3u, parse_mpd_tag_cache @@ -53,7 +53,7 @@ class LocalBackend(ThreadingActor, Backend): self.output = None def on_start(self): - output_refs = ActorRegistry.get_by_class(BaseOutput) + output_refs = ActorRegistry.get_by_class(GStreamerOutput) assert len(output_refs) == 1, 'Expected exactly one running output.' self.output = output_refs[0].proxy() diff --git a/mopidy/backends/spotify/__init__.py b/mopidy/backends/spotify/__init__.py index 1ac5f0be..79750a5d 100644 --- a/mopidy/backends/spotify/__init__.py +++ b/mopidy/backends/spotify/__init__.py @@ -6,7 +6,7 @@ from pykka.registry import ActorRegistry from mopidy import settings from mopidy.backends.base import (Backend, CurrentPlaylistController, LibraryController, PlaybackController, StoredPlaylistsController) -from mopidy.outputs.base import BaseOutput +from mopidy.outputs.gstreamer import GStreamerOutput logger = logging.getLogger('mopidy.backends.spotify') @@ -67,7 +67,7 @@ class SpotifyBackend(ThreadingActor, Backend): self.spotify = None def on_start(self): - output_refs = ActorRegistry.get_by_class(BaseOutput) + output_refs = ActorRegistry.get_by_class(GStreamerOutput) assert len(output_refs) == 1, 'Expected exactly one running output.' self.output = output_refs[0].proxy() diff --git a/mopidy/outputs/base.py b/mopidy/outputs/base.py deleted file mode 100644 index f81fe905..00000000 --- a/mopidy/outputs/base.py +++ /dev/null @@ -1,105 +0,0 @@ -class BaseOutput(object): - """ - Base class for audio outputs. - """ - - def play_uri(self, uri): - """ - Play URI. - - *MUST be implemented by subclass.* - - :param uri: the URI to play - :type uri: string - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError - - def deliver_data(self, capabilities, data): - """ - Deliver audio data to be played. - - *MUST be implemented by subclass.* - - :param capabilities: a GStreamer capabilities string - :type capabilities: string - """ - raise NotImplementedError - - def end_of_data_stream(self): - """ - Signal that the last audio data has been delivered. - - *MUST be implemented by subclass.* - """ - raise NotImplementedError - - def get_position(self): - """ - Get position in milliseconds. - - *MUST be implemented by subclass.* - - :rtype: int - """ - raise NotImplementedError - - def set_position(self, position): - """ - Set position in milliseconds. - - *MUST be implemented by subclass.* - - :param position: the position in milliseconds - :type volume: int - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError - - def set_state(self, state): - """ - Set playback state. - - *MUST be implemented by subclass.* - - :param state: the state - :type state: string - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError - - def get_volume(self): - """ - Get volume level for software mixer. - - *MUST be implemented by subclass.* - - :rtype: int in range [0..100] - """ - raise NotImplementedError - - def set_volume(self, volume): - """ - Set volume level for software mixer. - - *MUST be implemented by subclass.* - - :param volume: the volume in the range [0..100] - :type volume: int - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError - - 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. - - *MUST be implemented by subclass.* - - :param track: Track containing metadata for current song. - :type track: :class:`mopidy.modes.Track` - """ - raise NotImplementedError diff --git a/mopidy/outputs/dummy.py b/mopidy/outputs/dummy.py deleted file mode 100644 index f09965f7..00000000 --- a/mopidy/outputs/dummy.py +++ /dev/null @@ -1,63 +0,0 @@ -from pykka.actor import ThreadingActor - -from mopidy.outputs.base import BaseOutput - -class DummyOutput(ThreadingActor, BaseOutput): - """ - Audio output used for testing. - """ - - # pylint: disable = R0902 - # Too many instance attributes (9/7) - - #: For testing. Contains the last URI passed to :meth:`play_uri`. - uri = None - - #: For testing. Contains the last capabilities passed to - #: :meth:`deliver_data`. - capabilities = None - - #: For testing. Contains the last data passed to :meth:`deliver_data`. - data = None - - #: For testing. :class:`True` if :meth:`end_of_data_stream` has been - #: called. - end_of_data_stream_called = False - - #: For testing. Contains the current position. - position = 0 - - #: For testing. Contains the current state. - state = 'NULL' - - #: For testing. Contains the current volume. - volume = 100 - - def play_uri(self, uri): - self.uri = uri - return True - - def deliver_data(self, capabilities, data): - self.capabilities = capabilities - self.data = data - - def end_of_data_stream(self): - self.end_of_data_stream_called = True - - def get_position(self): - return self.position - - def set_position(self, position): - self.position = position - return True - - def set_state(self, state): - self.state = state - return True - - def get_volume(self): - return self.volume - - def set_volume(self, volume): - self.volume = volume - return True diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index c92e51d4..738c346d 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -9,11 +9,10 @@ from pykka.registry import ActorRegistry from mopidy import settings from mopidy.backends.base import Backend -from mopidy.outputs.base import BaseOutput logger = logging.getLogger('mopidy.outputs.gstreamer') -class GStreamerOutput(ThreadingActor, BaseOutput): +class GStreamerOutput(ThreadingActor): """ Audio output through `GStreamer `_. diff --git a/tests/backends/base/current_playlist.py b/tests/backends/base/current_playlist.py index ee5e1111..fa5e760c 100644 --- a/tests/backends/base/current_playlist.py +++ b/tests/backends/base/current_playlist.py @@ -3,7 +3,7 @@ import multiprocessing import random from mopidy.models import Playlist, Track -from mopidy.outputs.base import BaseOutput +from mopidy.outputs.gstreamer import GStreamerOutput from tests.backends.base import populate_playlist @@ -12,7 +12,7 @@ class CurrentPlaylistControllerTest(object): def setUp(self): self.backend = self.backend_class() - self.backend.output = mock.Mock(spec=BaseOutput) + self.backend.output = mock.Mock(spec=GStreamerOutput) self.controller = self.backend.current_playlist self.playback = self.backend.playback diff --git a/tests/backends/base/playback.py b/tests/backends/base/playback.py index 8ea48a3a..d5f04655 100644 --- a/tests/backends/base/playback.py +++ b/tests/backends/base/playback.py @@ -4,7 +4,7 @@ import random import time from mopidy.models import Track -from mopidy.outputs.base import BaseOutput +from mopidy.outputs.gstreamer import GStreamerOutput from tests import SkipTest from tests.backends.base import populate_playlist @@ -16,7 +16,7 @@ class PlaybackControllerTest(object): def setUp(self): self.backend = self.backend_class() - self.backend.output = mock.Mock(spec=BaseOutput) + self.backend.output = mock.Mock(spec=GStreamerOutput) self.playback = self.backend.playback self.current_playlist = self.backend.current_playlist From 4a1df118fbd059d21134e048a10289545c1c1fca Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Apr 2011 21:46:19 +0200 Subject: [PATCH 13/25] Move GStreamerOutput up one level to mopidy.gstreamer and rename to GStreamer --- mopidy/backends/local/__init__.py | 4 +-- mopidy/backends/spotify/__init__.py | 4 +-- mopidy/{outputs => }/gstreamer.py | 2 +- mopidy/outputs/__init__.py | 0 tests/backends/base/current_playlist.py | 4 +-- tests/backends/base/playback.py | 4 +-- tests/{outputs => }/gstreamer_test.py | 36 ++++++++++++------------- 7 files changed, 27 insertions(+), 27 deletions(-) rename mopidy/{outputs => }/gstreamer.py (99%) delete mode 100644 mopidy/outputs/__init__.py rename tests/{outputs => }/gstreamer_test.py (74%) diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 3d10a63c..cc09271a 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -12,7 +12,7 @@ from mopidy.backends.base import (Backend, CurrentPlaylistController, BasePlaybackProvider, StoredPlaylistsController, BaseStoredPlaylistsProvider) from mopidy.models import Playlist, Track, Album -from mopidy.outputs.gstreamer import GStreamerOutput +from mopidy.gstreamer import GStreamer from .translator import parse_m3u, parse_mpd_tag_cache @@ -53,7 +53,7 @@ class LocalBackend(ThreadingActor, Backend): self.output = None def on_start(self): - output_refs = ActorRegistry.get_by_class(GStreamerOutput) + output_refs = ActorRegistry.get_by_class(GStreamer) assert len(output_refs) == 1, 'Expected exactly one running output.' self.output = output_refs[0].proxy() diff --git a/mopidy/backends/spotify/__init__.py b/mopidy/backends/spotify/__init__.py index 79750a5d..641f5377 100644 --- a/mopidy/backends/spotify/__init__.py +++ b/mopidy/backends/spotify/__init__.py @@ -6,7 +6,7 @@ from pykka.registry import ActorRegistry from mopidy import settings from mopidy.backends.base import (Backend, CurrentPlaylistController, LibraryController, PlaybackController, StoredPlaylistsController) -from mopidy.outputs.gstreamer import GStreamerOutput +from mopidy.gstreamer import GStreamer logger = logging.getLogger('mopidy.backends.spotify') @@ -67,7 +67,7 @@ class SpotifyBackend(ThreadingActor, Backend): self.spotify = None def on_start(self): - output_refs = ActorRegistry.get_by_class(GStreamerOutput) + output_refs = ActorRegistry.get_by_class(GStreamer) assert len(output_refs) == 1, 'Expected exactly one running output.' self.output = output_refs[0].proxy() diff --git a/mopidy/outputs/gstreamer.py b/mopidy/gstreamer.py similarity index 99% rename from mopidy/outputs/gstreamer.py rename to mopidy/gstreamer.py index 738c346d..e3edf69c 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/gstreamer.py @@ -12,7 +12,7 @@ from mopidy.backends.base import Backend logger = logging.getLogger('mopidy.outputs.gstreamer') -class GStreamerOutput(ThreadingActor): +class GStreamer(ThreadingActor): """ Audio output through `GStreamer `_. diff --git a/mopidy/outputs/__init__.py b/mopidy/outputs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/backends/base/current_playlist.py b/tests/backends/base/current_playlist.py index fa5e760c..a298817a 100644 --- a/tests/backends/base/current_playlist.py +++ b/tests/backends/base/current_playlist.py @@ -3,7 +3,7 @@ import multiprocessing import random from mopidy.models import Playlist, Track -from mopidy.outputs.gstreamer import GStreamerOutput +from mopidy.gstreamer import GStreamer from tests.backends.base import populate_playlist @@ -12,7 +12,7 @@ class CurrentPlaylistControllerTest(object): def setUp(self): self.backend = self.backend_class() - self.backend.output = mock.Mock(spec=GStreamerOutput) + self.backend.output = mock.Mock(spec=GStreamer) self.controller = self.backend.current_playlist self.playback = self.backend.playback diff --git a/tests/backends/base/playback.py b/tests/backends/base/playback.py index d5f04655..972e5b5e 100644 --- a/tests/backends/base/playback.py +++ b/tests/backends/base/playback.py @@ -4,7 +4,7 @@ import random import time from mopidy.models import Track -from mopidy.outputs.gstreamer import GStreamerOutput +from mopidy.gstreamer import GStreamer from tests import SkipTest from tests.backends.base import populate_playlist @@ -16,7 +16,7 @@ class PlaybackControllerTest(object): def setUp(self): self.backend = self.backend_class() - self.backend.output = mock.Mock(spec=GStreamerOutput) + self.backend.output = mock.Mock(spec=GStreamer) self.playback = self.backend.playback self.current_playlist = self.backend.current_playlist diff --git a/tests/outputs/gstreamer_test.py b/tests/gstreamer_test.py similarity index 74% rename from tests/outputs/gstreamer_test.py rename to tests/gstreamer_test.py index 9f380815..5601160e 100644 --- a/tests/outputs/gstreamer_test.py +++ b/tests/gstreamer_test.py @@ -9,26 +9,26 @@ if sys.platform == 'win32': raise SkipTest from mopidy import settings -from mopidy.outputs.gstreamer import GStreamerOutput +from mopidy.gstreamer import GStreamer from mopidy.utils.path import path_to_uri from tests import path_to_data_dir -class GStreamerOutputTest(unittest.TestCase): +class GStreamerTest(unittest.TestCase): def setUp(self): settings.BACKENDS = ('mopidy.backends.local.LocalBackend',) self.song_uri = path_to_uri(path_to_data_dir('song1.wav')) - self.output = GStreamerOutput() - self.output.on_start() + self.gstreamer = GStreamer() + self.gstreamer.on_start() def tearDown(self): settings.runtime.clear() def test_play_uri_existing_file(self): - self.assertTrue(self.output.play_uri(self.song_uri)) + self.assertTrue(self.gstreamer.play_uri(self.song_uri)) def test_play_uri_non_existing_file(self): - self.assertFalse(self.output.play_uri(self.song_uri + 'bogus')) + self.assertFalse(self.gstreamer.play_uri(self.song_uri + 'bogus')) @SkipTest def test_deliver_data(self): @@ -39,19 +39,19 @@ class GStreamerOutputTest(unittest.TestCase): pass # TODO def test_default_get_volume_result(self): - self.assertEqual(100, self.output.get_volume()) + self.assertEqual(100, self.gstreamer.get_volume()) def test_set_volume(self): - self.assertTrue(self.output.set_volume(50)) - self.assertEqual(50, self.output.get_volume()) + self.assertTrue(self.gstreamer.set_volume(50)) + self.assertEqual(50, self.gstreamer.get_volume()) def test_set_volume_to_zero(self): - self.assertTrue(self.output.set_volume(0)) - self.assertEqual(0, self.output.get_volume()) + self.assertTrue(self.gstreamer.set_volume(0)) + self.assertEqual(0, self.gstreamer.get_volume()) def test_set_volume_to_one_hundred(self): - self.assertTrue(self.output.set_volume(100)) - self.assertEqual(100, self.output.get_volume()) + self.assertTrue(self.gstreamer.set_volume(100)) + self.assertEqual(100, self.gstreamer.get_volume()) @SkipTest def test_set_state(self): @@ -62,7 +62,7 @@ class GStreamerOutputTest(unittest.TestCase): pass # TODO def test_build_shoutcast_description_without_server(self): - self.assertEqual(None, self.output._build_shoutcast_description()) + self.assertEqual(None, self.gstreamer._build_shoutcast_description()) def test_build_shoutcast_description_with_server(self): settings.SHOUTCAST_SERVER = '127.0.0.1' @@ -70,7 +70,7 @@ class GStreamerOutputTest(unittest.TestCase): expected = u'audioconvert ! %s ! ' % settings.SHOUTCAST_ENCODER + \ u'shout2send ip="127.0.0.1" mount="/stream" ' \ u'password="hackme" port="8000" username="source"' - result = self.output._build_shoutcast_description() + result = self.gstreamer._build_shoutcast_description() self.assertEqual(expected, result) def test_build_shoutcast_description_with_mount(self): @@ -98,19 +98,19 @@ class GStreamerOutputTest(unittest.TestCase): def test_build_shoutcast_description_with_override(self): settings.SHOUTCAST_OVERRIDE = 'foobar' - result = self.output._build_shoutcast_description() + result = self.gstreamer._build_shoutcast_description() self.assertEqual('foobar', result) def test_build_shoutcast_description_with_override_and_server(self): settings.SHOUTCAST_OVERRIDE = 'foobar' settings.SHOUTCAST_SERVER = '127.0.0.1' - result = self.output._build_shoutcast_description() + result = self.gstreamer._build_shoutcast_description() self.assertEqual('foobar', result) def check_shoutcast_options(self, options): expected = u'audioconvert ! %s ! shout2send ' % settings.SHOUTCAST_ENCODER expected += options - result = self.output._build_shoutcast_description() + result = self.gstreamer._build_shoutcast_description() self.assertEqual(expected, result) From c7ccd0c2d4d332c1d24c1f956c5863decdd37480 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Apr 2011 23:06:33 +0200 Subject: [PATCH 14/25] More fixes with respect to refactoring --- mopidy/backends/spotify/session_manager.py | 4 ++-- mopidy/core.py | 7 ++++--- mopidy/mixers/gstreamer_software.py | 4 ++-- mopidy/settings.py | 7 ------- mopidy/utils/settings.py | 2 ++ 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index e92fe89e..2b768b20 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -10,7 +10,7 @@ from mopidy import get_version, settings from mopidy.backends.base import Backend from mopidy.backends.spotify.translator import SpotifyTranslator from mopidy.models import Playlist -from mopidy.outputs.base import BaseOutput +from mopidy.gstreamer import GStreamer from mopidy.utils.process import BaseThread logger = logging.getLogger('mopidy.backends.spotify.session_manager') @@ -40,7 +40,7 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): self.connect() def setup(self): - output_refs = ActorRegistry.get_by_class(BaseOutput) + output_refs = ActorRegistry.get_by_class(GStreamer) assert len(output_refs) == 1, 'Expected exactly one running output.' self.output = output_refs[0].proxy() diff --git a/mopidy/core.py b/mopidy/core.py index 093f783d..60e25ef1 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -5,6 +5,7 @@ import time from pykka.registry import ActorRegistry from mopidy import get_version, settings, OptionalDependencyError +from mopidy.gstreamer import GStreamer 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 @@ -18,7 +19,7 @@ def main(): setup_logging(options.verbosity_level, options.save_debug_log) setup_settings() setup_gobject_loop() - setup_output() + setup_gstreamer() setup_mixer() setup_backend() setup_frontends() @@ -54,8 +55,8 @@ def setup_gobject_loop(): gobject_loop.start() return gobject_loop -def setup_output(): - return get_class(settings.OUTPUT).start().proxy() +def setup_gstreamer(): + return GStreamer().start().proxy() def setup_mixer(): return get_class(settings.MIXER).start().proxy() diff --git a/mopidy/mixers/gstreamer_software.py b/mopidy/mixers/gstreamer_software.py index d6365b4b..87602772 100644 --- a/mopidy/mixers/gstreamer_software.py +++ b/mopidy/mixers/gstreamer_software.py @@ -2,7 +2,7 @@ from pykka.actor import ThreadingActor from pykka.registry import ActorRegistry from mopidy.mixers.base import BaseMixer -from mopidy.outputs.base import BaseOutput +from mopidy.gstreamer import GStreamer class GStreamerSoftwareMixer(ThreadingActor, BaseMixer): """Mixer which uses GStreamer to control volume in software.""" @@ -11,7 +11,7 @@ class GStreamerSoftwareMixer(ThreadingActor, BaseMixer): self.output = None def on_start(self): - output_refs = ActorRegistry.get_by_class(BaseOutput) + output_refs = ActorRegistry.get_by_class(GStreamer) assert len(output_refs) == 1, 'Expected exactly one running output.' self.output = output_refs[0].proxy() diff --git a/mopidy/settings.py b/mopidy/settings.py index 7f41cf69..e2d85c88 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -143,13 +143,6 @@ MIXER_EXT_SPEAKERS_B = None #: MIXER_MAX_VOLUME = 100 MIXER_MAX_VOLUME = 100 -#: Audio output handler to use. -#: -#: Default:: -#: -#: OUTPUT = u'mopidy.outputs.gstreamer.GStreamerOutput' -OUTPUT = u'mopidy.outputs.gstreamer.GStreamerOutput' - #: Which address Mopidy's MPD server should bind to. #: #:Examples: diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 529c6fb1..b907e85f 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -97,9 +97,11 @@ def validate_settings(defaults, settings): 'DUMP_LOG_FILENAME': 'DEBUG_LOG_FILENAME', 'DUMP_LOG_FORMAT': 'DEBUG_LOG_FORMAT', 'FRONTEND': 'FRONTENDS', + 'GSTREAMER_AUDIO_SINK': 'CUSTOM_OUTPUT', 'LOCAL_MUSIC_FOLDER': 'LOCAL_MUSIC_PATH', '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', From f218bc060bbe88ff1e2f2bde854f8eec09306755 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Apr 2011 23:30:03 +0200 Subject: [PATCH 15/25] Don't bother with album in taginject --- mopidy/gstreamer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index e3edf69c..3aeb7384 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -198,10 +198,9 @@ class GStreamer(ThreadingActor): return True def set_metadata(self, track): - tags = u'artist="%(artist)s",title="%(title)s",album="%(album)s"' % { + tags = u'artist="%(artist)s",title="%(title)s"' % { 'artist': u', '.join([a.name for a in track.artists]), 'title': track.name, - 'album': track.album.name, } logger.debug('Setting tags to: %s', tags) self.gst_taginject.set_property('tags', tags) From 4e232c981cdb9680375ae5f8cfefbf7ac04971f2 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Apr 2011 00:50:15 +0200 Subject: [PATCH 16/25] Switch to modularised output for playback --- mopidy/gstreamer.py | 79 +++++++++++++++++++++------------------------ mopidy/outputs.py | 37 +++++++++++++++++++++ mopidy/settings.py | 18 +++++++++-- 3 files changed, 89 insertions(+), 45 deletions(-) create mode 100644 mopidy/outputs.py diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 3aeb7384..ab70b0ba 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -8,9 +8,42 @@ from pykka.actor import ThreadingActor from pykka.registry import ActorRegistry from mopidy import settings +from mopidy.utils import get_class from mopidy.backends.base import Backend -logger = logging.getLogger('mopidy.outputs.gstreamer') +logger = logging.getLogger('mopidy.gstreamer') + +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 class GStreamer(ThreadingActor): """ @@ -55,53 +88,15 @@ class GStreamer(ThreadingActor): self.gst_convert.get_pad('sink')) self.gst_pipeline.add(self.gst_uridecodebin) - localaudio = settings.GSTREAMER_AUDIO_SINK - shoutcast = self._build_shoutcast_description() - - if localaudio: - self._add_output(localaudio) - if shoutcast: - self._add_output(shoutcast) - if not localaudio and not shoutcast: - logger.error('No proper output channels have been setup.') - self._add_output('fakesink') + for output in settings.OUTPUTS: + output_cls = get_class(output)() + output_cls.connect_bin(self.gst_pipeline, self.gst_tee) # Setup bus and message processor gst_bus = self.gst_pipeline.get_bus() gst_bus.add_signal_watch() gst_bus.connect('message', self._process_gstreamer_message) - def _add_output(self, description): - bin = 'queue ! %s' % description - logger.debug('Adding output bin to tee: %s', bin) - output = gst.parse_bin_from_description(bin, True) - self.gst_pipeline.add(output) - output.sync_state_with_parent() - gst.element_link_many(self.gst_tee, output) - - def _build_shoutcast_description(self): - if settings.SHOUTCAST_OVERRIDE: - return settings.SHOUTCAST_OVERRIDE - - if not settings.SHOUTCAST_SERVER: - return None - - description = ['audioconvert ! %s ! shout2send' % settings.SHOUTCAST_ENCODER] - options = { - u'ip': settings.SHOUTCAST_SERVER, - u'mount': settings.SHOUTCAST_MOUNT, - u'port': settings.SHOUTCAST_PORT, - u'username': settings.SHOUTCAST_USER, - u'password': settings.SHOUTCAST_PASSWORD, - } - - for key, value in sorted(options.items()): - if value: - description.append('%s="%s"' % (key, value)) - - return u' '.join(description) - - def _process_new_pad(self, source, pad, target_pad): pad.link(target_pad) diff --git a/mopidy/outputs.py b/mopidy/outputs.py new file mode 100644 index 00000000..60569a95 --- /dev/null +++ b/mopidy/outputs.py @@ -0,0 +1,37 @@ +from mopidy import settings +from mopidy.gstreamer import BaseOutput + +class LocalAudioOutput(BaseOutput): + def describe_bin(self): + return 'autoaudiosink' + +class CustomOutput(BaseOutput): + def describe_bin(self): + return settings.CUSTOM_OUTPUT + +class NullOutput(BaseOutput): + def describe_bin(self): + return 'fakesink' + +class ShoutcastOutput(BaseOutput): + def describe_bin(self): + if settings.SHOUTCAST_OVERRIDE: + return settings.SHOUTCAST_OVERRIDE + + if not settings.SHOUTCAST_SERVER: + return None + + description = ['audioconvert ! %s ! shout2send' % settings.SHOUTCAST_ENCODER] + options = { + u'ip': settings.SHOUTCAST_SERVER, + u'mount': settings.SHOUTCAST_MOUNT, + u'port': settings.SHOUTCAST_PORT, + u'username': settings.SHOUTCAST_USER, + u'password': settings.SHOUTCAST_PASSWORD, + } + + for key, value in sorted(options.items()): + if value: + description.append('%s="%s"' % (key, value)) + + return u' '.join(description) diff --git a/mopidy/settings.py b/mopidy/settings.py index e2d85c88..ab299eb6 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -20,6 +20,18 @@ BACKENDS = ( u'mopidy.backends.spotify.SpotifyBackend', ) +#: List of outputs to use. See :mod:`mopidy.outputs` for all available +#: backends +#: +#: Default:: +#: +#: OUTPUTS = ( +#: u'mopidy.outputs.LocalAudioOutput', +#: ) +OUTPUTS = ( + u'mopidy.outputs.LocalAudioOutput', +) + #: The log format used for informational logging. #: #: See http://docs.python.org/library/logging.html#formatter-objects for @@ -54,12 +66,12 @@ FRONTENDS = ( u'mopidy.frontends.lastfm.LastfmFrontend', ) -#: Which GStreamer audio sink to use in :mod:`mopidy.outputs.gstreamer`. +#: Which GStreamer bin description to use in :mod:`mopidy.outputs.CustomOutput`. #: #: Default:: #: -#: GSTREAMER_AUDIO_SINK = u'autoaudiosink' -GSTREAMER_AUDIO_SINK = u'autoaudiosink' +#: CUSTOM_OUTPUT = None +CUSTOM_OUTPUT= None #: Your `Last.fm `_ username. #: From ebe9bba9a92f5a7e95827a087b8f33b0079ba0e1 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Apr 2011 00:59:53 +0200 Subject: [PATCH 17/25] Use modify_bin and set_property to construct shoutcast output --- mopidy/outputs.py | 15 +++++++-------- mopidy/settings.py | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/mopidy/outputs.py b/mopidy/outputs.py index 60569a95..229f273e 100644 --- a/mopidy/outputs.py +++ b/mopidy/outputs.py @@ -18,11 +18,12 @@ class ShoutcastOutput(BaseOutput): if settings.SHOUTCAST_OVERRIDE: return settings.SHOUTCAST_OVERRIDE - if not settings.SHOUTCAST_SERVER: - return None + return 'audioconvert ! %s ! shout2send name=shoutcast' \ + % settings.SHOUTCAST_ENCODER - description = ['audioconvert ! %s ! shout2send' % settings.SHOUTCAST_ENCODER] - options = { + def modify_bin(self, output): + shoutcast = output.get_by_name('shoutcast') + properties = { u'ip': settings.SHOUTCAST_SERVER, u'mount': settings.SHOUTCAST_MOUNT, u'port': settings.SHOUTCAST_PORT, @@ -30,8 +31,6 @@ class ShoutcastOutput(BaseOutput): u'password': settings.SHOUTCAST_PASSWORD, } - for key, value in sorted(options.items()): + for key, value in properties.items(): if value: - description.append('%s="%s"' % (key, value)) - - return u' '.join(description) + shoutcast.set_property(key, value) diff --git a/mopidy/settings.py b/mopidy/settings.py index ab299eb6..9db30a11 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -183,8 +183,8 @@ MPD_SERVER_PORT = 6600 #: #: Default:: #: -#: SHOUTCAST_SERVER = None # Must be set to enable shoutcase -SHOUTCAST_SERVER = None +#: SHOUTCAST_SERVER = u'127.0.0.1' +SHOUTCAST_SERVER = u'127.0.0.1' #: User to authenticate as against Shoutcast server. #: From 1c233a3f8a971377de4ee594ba3f3e9817bb40db Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Apr 2011 17:14:31 +0200 Subject: [PATCH 18/25] Replace CustomOutput with override for LocalOutput --- mopidy/outputs.py | 8 +++----- mopidy/settings.py | 4 ++-- mopidy/utils/settings.py | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/mopidy/outputs.py b/mopidy/outputs.py index 229f273e..6aff0a0f 100644 --- a/mopidy/outputs.py +++ b/mopidy/outputs.py @@ -1,14 +1,12 @@ from mopidy import settings from mopidy.gstreamer import BaseOutput -class LocalAudioOutput(BaseOutput): +class LocalOutput(BaseOutput): def describe_bin(self): + if settings.LOCALOUTPUT_OVERRIDE: + return settings.LOCALOUTPUT_OVERRIDE return 'autoaudiosink' -class CustomOutput(BaseOutput): - def describe_bin(self): - return settings.CUSTOM_OUTPUT - class NullOutput(BaseOutput): def describe_bin(self): return 'fakesink' diff --git a/mopidy/settings.py b/mopidy/settings.py index 9db30a11..915363b4 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -70,8 +70,8 @@ FRONTENDS = ( #: #: Default:: #: -#: CUSTOM_OUTPUT = None -CUSTOM_OUTPUT= None +#: LOCALOUTPUT_OVERRIDE = None +LOCALOUTPUT_OVERRIDE = None #: Your `Last.fm `_ username. #: diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index b907e85f..6c678e4b 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -97,7 +97,7 @@ def validate_settings(defaults, settings): 'DUMP_LOG_FILENAME': 'DEBUG_LOG_FILENAME', 'DUMP_LOG_FORMAT': 'DEBUG_LOG_FORMAT', 'FRONTEND': 'FRONTENDS', - 'GSTREAMER_AUDIO_SINK': 'CUSTOM_OUTPUT', + 'GSTREAMER_AUDIO_SINK': 'LOCALOUTPUT_OVERRIDE', 'LOCAL_MUSIC_FOLDER': 'LOCAL_MUSIC_PATH', 'LOCAL_PLAYLIST_FOLDER': 'LOCAL_PLAYLIST_PATH', 'LOCAL_TAG_CACHE': 'LOCAL_TAG_CACHE_FILE', From 5c19bf843463adee36745087b314b979e582d229 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Apr 2011 17:15:43 +0200 Subject: [PATCH 19/25] Create set_properties helper for BaseOutput --- mopidy/gstreamer.py | 9 +++++++++ mopidy/outputs.py | 13 +++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index ab70b0ba..5c242a66 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -45,6 +45,15 @@ class BaseOutput(object): """ 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): """ Audio output through `GStreamer `_. diff --git a/mopidy/outputs.py b/mopidy/outputs.py index 6aff0a0f..1a767d58 100644 --- a/mopidy/outputs.py +++ b/mopidy/outputs.py @@ -15,20 +15,17 @@ class ShoutcastOutput(BaseOutput): def describe_bin(self): if settings.SHOUTCAST_OVERRIDE: return settings.SHOUTCAST_OVERRIDE - return 'audioconvert ! %s ! shout2send name=shoutcast' \ % settings.SHOUTCAST_ENCODER def modify_bin(self, output): - shoutcast = output.get_by_name('shoutcast') - properties = { + if settings.SHOUTCAST_OVERRIDE: + return + + self.set_properties(output.get_by_name('shoutcast'), { u'ip': settings.SHOUTCAST_SERVER, u'mount': settings.SHOUTCAST_MOUNT, u'port': settings.SHOUTCAST_PORT, u'username': settings.SHOUTCAST_USER, u'password': settings.SHOUTCAST_PASSWORD, - } - - for key, value in properties.items(): - if value: - shoutcast.set_property(key, value) + }) From eceba712737a77ed73640a82d7756150d9aef0c7 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Apr 2011 17:30:55 +0200 Subject: [PATCH 20/25] Unify naming of output settings --- mopidy/outputs.py | 22 +++++++++++----------- mopidy/settings.py | 34 +++++++++++++++++----------------- mopidy/utils/settings.py | 2 +- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/mopidy/outputs.py b/mopidy/outputs.py index 1a767d58..ee1d20b0 100644 --- a/mopidy/outputs.py +++ b/mopidy/outputs.py @@ -3,8 +3,8 @@ from mopidy.gstreamer import BaseOutput class LocalOutput(BaseOutput): def describe_bin(self): - if settings.LOCALOUTPUT_OVERRIDE: - return settings.LOCALOUTPUT_OVERRIDE + if settings.LOCAL_OUTPUT_OVERRIDE: + return settings.LOCAL_OUTPUT_OVERRIDE return 'autoaudiosink' class NullOutput(BaseOutput): @@ -13,19 +13,19 @@ class NullOutput(BaseOutput): class ShoutcastOutput(BaseOutput): def describe_bin(self): - if settings.SHOUTCAST_OVERRIDE: - return settings.SHOUTCAST_OVERRIDE + if settings.SHOUTCAST_OUTPUT_OVERRIDE: + return settings.SHOUTCAST_OUTPUT_OVERRIDE return 'audioconvert ! %s ! shout2send name=shoutcast' \ - % settings.SHOUTCAST_ENCODER + % settings.SHOUTCAST_OUTPUT_ENCODER def modify_bin(self, output): - if settings.SHOUTCAST_OVERRIDE: + if settings.SHOUTCAST_OUTPUT_OVERRIDE: return self.set_properties(output.get_by_name('shoutcast'), { - u'ip': settings.SHOUTCAST_SERVER, - u'mount': settings.SHOUTCAST_MOUNT, - u'port': settings.SHOUTCAST_PORT, - u'username': settings.SHOUTCAST_USER, - u'password': settings.SHOUTCAST_PASSWORD, + u'ip': settings.SHOUTCAST_OUTPUT_SERVER, + u'mount': settings.SHOUTCAST_OUTPUT_MOUNT, + u'port': settings.SHOUTCAST_OUTPUT_PORT, + u'username': settings.SHOUTCAST_OUTPUT_USER, + u'password': settings.SHOUTCAST_OUTPUT_PASSWORD, }) diff --git a/mopidy/settings.py b/mopidy/settings.py index 915363b4..483d89dd 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -70,8 +70,8 @@ FRONTENDS = ( #: #: Default:: #: -#: LOCALOUTPUT_OVERRIDE = None -LOCALOUTPUT_OVERRIDE = None +#: LOCAL_OUTPUT_OVERRIDE = None +LOCAL_OUTPUT_OVERRIDE = None #: Your `Last.fm `_ username. #: @@ -183,46 +183,46 @@ MPD_SERVER_PORT = 6600 #: #: Default:: #: -#: SHOUTCAST_SERVER = u'127.0.0.1' -SHOUTCAST_SERVER = u'127.0.0.1' +#: SHOUTCAST_OUTPUT_SERVER = u'127.0.0.1' +SHOUTCAST_OUTPUT_SERVER = u'127.0.0.1' #: User to authenticate as against Shoutcast server. #: #: Default:: #: -#: SHOUTCAST_USER = u'source' -SHOUTCAST_USER = u'source' +#: SHOUTCAST_OUTPUT_USER = u'source' +SHOUTCAST_OUTPUT_USER = u'source' #: Password to authenticate with against Shoutcast server. #: #: Default:: #: -#: SHOUTCAST_PASSWORD = u'hackme' -SHOUTCAST_PASSWORD = u'hackme' +#: SHOUTCAST_OUTPUT_PASSWORD = u'hackme' +SHOUTCAST_OUTPUT_PASSWORD = u'hackme' #: Port to use for streaming to Shoutcast server. #: #: Default:: #: -#: SHOUTCAST_PORT = 8000 -SHOUTCAST_PORT = 8000 +#: SHOUTCAST_OUTPUT_PORT = 8000 +SHOUTCAST_OUTPUT_PORT = 8000 #: Mountpoint to use for the stream on the Shoutcast server. #: #: Default:: #: -#: SHOUTCAST_MOUNT = u'/stream' -SHOUTCAST_MOUNT = u'/stream' +#: SHOUTCAST_OUTPUT_MOUNT = u'/stream' +SHOUTCAST_OUTPUT_MOUNT = u'/stream' #: Encoder to use to process audio data before streaming. #: #: Default:: #: -#: SHOUTCAST_ENCODER = u'lame mode=stereo bitrate=320' -SHOUTCAST_ENCODER = u'lame mode=stereo bitrate=320' +#: SHOUTCAST_OUTPUT_ENCODER = u'lame mode=stereo bitrate=320' +SHOUTCAST_OUTPUT_ENCODER = u'lame mode=stereo bitrate=320' #: Overrides to allow advanced setup of shoutcast. Using this settings implies -#: that all other SHOUTCAST_* settings will be ignored. +#: that all other SHOUTCAST_OUTPUT_* settings will be ignored. #: #: Examples: #: @@ -235,8 +235,8 @@ SHOUTCAST_ENCODER = u'lame mode=stereo bitrate=320' #: #: Default:: #: -#: SHOUTCAST_OVERRIDE = None -SHOUTCAST_OVERRIDE = None +#: SHOUTCAST_OUTPUT_OVERRIDE = None +SHOUTCAST_OUTPUT_OVERRIDE = None #: Path to the Spotify cache. #: diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 6c678e4b..0dc6b4cb 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -97,7 +97,7 @@ def validate_settings(defaults, settings): 'DUMP_LOG_FILENAME': 'DEBUG_LOG_FILENAME', 'DUMP_LOG_FORMAT': 'DEBUG_LOG_FORMAT', 'FRONTEND': 'FRONTENDS', - 'GSTREAMER_AUDIO_SINK': 'LOCALOUTPUT_OVERRIDE', + 'GSTREAMER_AUDIO_SINK': 'LOCAL_OUTPUT_OVERRIDE', 'LOCAL_MUSIC_FOLDER': 'LOCAL_MUSIC_PATH', 'LOCAL_PLAYLIST_FOLDER': 'LOCAL_PLAYLIST_PATH', 'LOCAL_TAG_CACHE': 'LOCAL_TAG_CACHE_FILE', From 169fbae695616074d95985b44e412ff79a3f11eb Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Apr 2011 17:41:33 +0200 Subject: [PATCH 21/25] Fix outputs setting --- mopidy/settings.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mopidy/settings.py b/mopidy/settings.py index 483d89dd..f803c932 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -20,18 +20,6 @@ BACKENDS = ( u'mopidy.backends.spotify.SpotifyBackend', ) -#: List of outputs to use. See :mod:`mopidy.outputs` for all available -#: backends -#: -#: Default:: -#: -#: OUTPUTS = ( -#: u'mopidy.outputs.LocalAudioOutput', -#: ) -OUTPUTS = ( - u'mopidy.outputs.LocalAudioOutput', -) - #: The log format used for informational logging. #: #: See http://docs.python.org/library/logging.html#formatter-objects for @@ -179,6 +167,18 @@ MPD_SERVER_PASSWORD = None #: Default: 6600 MPD_SERVER_PORT = 6600 +#: List of outputs to use. See :mod:`mopidy.outputs` for all available +#: backends +#: +#: Default:: +#: +#: OUTPUTS = ( +#: u'mopidy.outputs.LocalAudioOutput', +#: ) +OUTPUTS = ( + u'mopidy.outputs.LocalOutput', +) + #: Servar that runs Shoutcast server to send stream to. #: #: Default:: From a4c526774bfe29f11f02485c21881c5a3585bfe9 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Apr 2011 17:44:02 +0200 Subject: [PATCH 22/25] Kill off stale shoutcast tests --- tests/gstreamer_test.py | 56 ++--------------------------------------- 1 file changed, 2 insertions(+), 54 deletions(-) diff --git a/tests/gstreamer_test.py b/tests/gstreamer_test.py index 5601160e..9087e0db 100644 --- a/tests/gstreamer_test.py +++ b/tests/gstreamer_test.py @@ -14,6 +14,8 @@ from mopidy.utils.path import path_to_uri from tests import path_to_data_dir +# TODO BaseOutputTest? + class GStreamerTest(unittest.TestCase): def setUp(self): settings.BACKENDS = ('mopidy.backends.local.LocalBackend',) @@ -60,57 +62,3 @@ class GStreamerTest(unittest.TestCase): @SkipTest def test_set_position(self): pass # TODO - - def test_build_shoutcast_description_without_server(self): - self.assertEqual(None, self.gstreamer._build_shoutcast_description()) - - def test_build_shoutcast_description_with_server(self): - settings.SHOUTCAST_SERVER = '127.0.0.1' - - expected = u'audioconvert ! %s ! ' % settings.SHOUTCAST_ENCODER + \ - u'shout2send ip="127.0.0.1" mount="/stream" ' \ - u'password="hackme" port="8000" username="source"' - result = self.gstreamer._build_shoutcast_description() - self.assertEqual(expected, result) - - def test_build_shoutcast_description_with_mount(self): - settings.SHOUTCAST_SERVER = '127.0.0.1' - settings.SHOUTCAST_MOUNT = '/stream.mp3' - - self.check_shoutcast_options(u'ip="127.0.0.1" mount="/stream.mp3" ' - u'password="hackme" port="8000" username="source"') - - def test_build_shoutcast_description_with_user_and_passwod(self): - settings.SHOUTCAST_SERVER = '127.0.0.1' - settings.SHOUTCAST_USER = 'john' - settings.SHOUTCAST_PASSWORD = 'doe' - - self.check_shoutcast_options('ip="127.0.0.1" mount="/stream" ' - u'password="doe" port="8000" username="john"') - - def test_build_shoutcast_description_unset_user_and_pass(self): - settings.SHOUTCAST_SERVER = '127.0.0.1' - settings.SHOUTCAST_USER = None - settings.SHOUTCAST_PASSWORD = None - - self.check_shoutcast_options(u'ip="127.0.0.1" mount="/stream" port="8000"') - - def test_build_shoutcast_description_with_override(self): - settings.SHOUTCAST_OVERRIDE = 'foobar' - - result = self.gstreamer._build_shoutcast_description() - self.assertEqual('foobar', result) - - def test_build_shoutcast_description_with_override_and_server(self): - settings.SHOUTCAST_OVERRIDE = 'foobar' - settings.SHOUTCAST_SERVER = '127.0.0.1' - - result = self.gstreamer._build_shoutcast_description() - self.assertEqual('foobar', result) - - def check_shoutcast_options(self, options): - expected = u'audioconvert ! %s ! shout2send ' % settings.SHOUTCAST_ENCODER - expected += options - - result = self.gstreamer._build_shoutcast_description() - self.assertEqual(expected, result) From 472e4d2790f9abd4be48ab30498673576fb1971b Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Apr 2011 21:30:01 +0200 Subject: [PATCH 23/25] Rename output to gstreamer in backends --- mopidy/backends/local/__init__.py | 20 ++++++++++---------- mopidy/backends/spotify/__init__.py | 8 ++++---- mopidy/backends/spotify/playback.py | 14 +++++++------- mopidy/backends/spotify/session_manager.py | 12 ++++++------ tests/backends/base/current_playlist.py | 2 +- tests/backends/base/playback.py | 12 ++++++------ 6 files changed, 34 insertions(+), 34 deletions(-) diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index cc09271a..126034f1 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -50,12 +50,12 @@ class LocalBackend(ThreadingActor, Backend): self.uri_handlers = [u'file://'] - self.output = None + self.gstreamer = None def on_start(self): - output_refs = ActorRegistry.get_by_class(GStreamer) - assert len(output_refs) == 1, 'Expected exactly one running output.' - self.output = output_refs[0].proxy() + gstreamer_refs = ActorRegistry.get_by_class(GStreamer) + assert len(gstreamer_refs) == 1, 'Expected exactly one running gstreamer.' + self.gstreamer = gstreamer_refs[0].proxy() class LocalPlaybackController(PlaybackController): @@ -67,24 +67,24 @@ class LocalPlaybackController(PlaybackController): @property def time_position(self): - return self.backend.output.get_position().get() + return self.backend.gstreamer.get_position().get() class LocalPlaybackProvider(BasePlaybackProvider): def pause(self): - return self.backend.output.set_state('PAUSED').get() + return self.backend.gstreamer.set_state('PAUSED').get() def play(self, track): - return self.backend.output.play_uri(track.uri).get() + return self.backend.gstreamer.play_uri(track.uri).get() def resume(self): - return self.backend.output.set_state('PLAYING').get() + return self.backend.gstreamer.set_state('PLAYING').get() def seek(self, time_position): - return self.backend.output.set_position(time_position).get() + return self.backend.gstreamer.set_position(time_position).get() def stop(self): - return self.backend.output.set_state('READY').get() + return self.backend.gstreamer.set_state('READY').get() class LocalStoredPlaylistsProvider(BaseStoredPlaylistsProvider): diff --git a/mopidy/backends/spotify/__init__.py b/mopidy/backends/spotify/__init__.py index 641f5377..9dababc0 100644 --- a/mopidy/backends/spotify/__init__.py +++ b/mopidy/backends/spotify/__init__.py @@ -63,13 +63,13 @@ class SpotifyBackend(ThreadingActor, Backend): self.uri_handlers = [u'spotify:', u'http://open.spotify.com/'] - self.output = None + self.gstreamer = None self.spotify = None def on_start(self): - output_refs = ActorRegistry.get_by_class(GStreamer) - assert len(output_refs) == 1, 'Expected exactly one running output.' - self.output = output_refs[0].proxy() + gstreamer_refs = ActorRegistry.get_by_class(GStreamer) + assert len(gstreamer_refs) == 1, 'Expected exactly one running gstreamer.' + self.gstreamer = gstreamer_refs[0].proxy() self.spotify = self._connect() diff --git a/mopidy/backends/spotify/playback.py b/mopidy/backends/spotify/playback.py index f4db490c..496bd83c 100644 --- a/mopidy/backends/spotify/playback.py +++ b/mopidy/backends/spotify/playback.py @@ -8,10 +8,10 @@ logger = logging.getLogger('mopidy.backends.spotify.playback') class SpotifyPlaybackProvider(BasePlaybackProvider): def pause(self): - return self.backend.output.set_state('PAUSED') + return self.backend.gstreamer.set_state('PAUSED') def play(self, track): - self.backend.output.set_state('READY') + self.backend.gstreamer.set_state('READY') if self.backend.playback.state == self.backend.playback.PLAYING: self.backend.spotify.session.play(0) if track.uri is None: @@ -20,8 +20,8 @@ class SpotifyPlaybackProvider(BasePlaybackProvider): self.backend.spotify.session.load( Link.from_string(track.uri).as_track()) self.backend.spotify.session.play(1) - self.backend.output.play_uri('appsrc://') - self.backend.output.set_metadata(track) + self.backend.gstreamer.play_uri('appsrc://') + self.backend.gstreamer.set_metadata(track) return True except SpotifyError as e: logger.warning('Play %s failed: %s', track.uri, e) @@ -31,12 +31,12 @@ class SpotifyPlaybackProvider(BasePlaybackProvider): return self.seek(self.backend.playback.time_position) def seek(self, time_position): - self.backend.output.set_state('READY') + self.backend.gstreamer.set_state('READY') self.backend.spotify.session.seek(time_position) - self.backend.output.set_state('PLAYING') + self.backend.gstreamer.set_state('PLAYING') return True def stop(self): - result = self.backend.output.set_state('READY') + result = self.backend.gstreamer.set_state('READY') self.backend.spotify.session.play(0) return result diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index 2b768b20..1d997156 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -29,7 +29,7 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): BaseThread.__init__(self) self.name = 'SpotifySMThread' - self.output = None + self.gstreamer = None self.backend = None self.connected = threading.Event() @@ -40,9 +40,9 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): self.connect() def setup(self): - output_refs = ActorRegistry.get_by_class(GStreamer) - assert len(output_refs) == 1, 'Expected exactly one running output.' - self.output = output_refs[0].proxy() + gstreamer_refs = ActorRegistry.get_by_class(GStreamer) + assert len(gstreamer_refs) == 1, 'Expected exactly one running gstreamer.' + self.gstreamer = gstreamer_refs[0].proxy() backend_refs = ActorRegistry.get_by_class(Backend) assert len(backend_refs) == 1, 'Expected exactly one running backend.' @@ -102,7 +102,7 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): 'sample_rate': sample_rate, 'channels': channels, } - self.output.deliver_data(capabilites, bytes(frames)) + self.gstreamer.deliver_data(capabilites, bytes(frames)) def play_token_lost(self, session): """Callback used by pyspotify""" @@ -116,7 +116,7 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): def end_of_track(self, session): """Callback used by pyspotify""" logger.debug(u'End of data stream reached') - self.output.end_of_data_stream() + self.gstreamer.end_of_data_stream() def refresh_stored_playlists(self): """Refresh the stored playlists in the backend with fresh meta data diff --git a/tests/backends/base/current_playlist.py b/tests/backends/base/current_playlist.py index a298817a..427ce76d 100644 --- a/tests/backends/base/current_playlist.py +++ b/tests/backends/base/current_playlist.py @@ -12,7 +12,7 @@ class CurrentPlaylistControllerTest(object): def setUp(self): self.backend = self.backend_class() - self.backend.output = mock.Mock(spec=GStreamer) + self.backend.gstreamer = mock.Mock(spec=GStreamer) self.controller = self.backend.current_playlist self.playback = self.backend.playback diff --git a/tests/backends/base/playback.py b/tests/backends/base/playback.py index 972e5b5e..2d455225 100644 --- a/tests/backends/base/playback.py +++ b/tests/backends/base/playback.py @@ -16,7 +16,7 @@ class PlaybackControllerTest(object): def setUp(self): self.backend = self.backend_class() - self.backend.output = mock.Mock(spec=GStreamer) + self.backend.gstreamer = mock.Mock(spec=GStreamer) self.playback = self.backend.playback self.current_playlist = self.backend.current_playlist @@ -520,7 +520,7 @@ class PlaybackControllerTest(object): self.assert_(wrapper.called) - @SkipTest # Blocks for 10ms and does not work with DummyOutput + @SkipTest # Blocks for 10ms @populate_playlist def test_end_of_track_callback_gets_called(self): self.playback.play() @@ -599,7 +599,7 @@ class PlaybackControllerTest(object): self.playback.pause() self.assertEqual(self.playback.resume(), None) - @SkipTest # Uses sleep and does not work with DummyOutput+LocalBackend + @SkipTest # Uses sleep and might not work with LocalBackend @populate_playlist def test_resume_continues_from_right_position(self): self.playback.play() @@ -729,7 +729,7 @@ class PlaybackControllerTest(object): def test_time_position_when_stopped(self): future = mock.Mock() future.get = mock.Mock(return_value=0) - self.backend.output.get_position = mock.Mock(return_value=future) + self.backend.gstreamer.get_position = mock.Mock(return_value=future) self.assertEqual(self.playback.time_position, 0) @@ -737,11 +737,11 @@ class PlaybackControllerTest(object): def test_time_position_when_stopped_with_playlist(self): future = mock.Mock() future.get = mock.Mock(return_value=0) - self.backend.output.get_position = mock.Mock(return_value=future) + self.backend.gstreamer.get_position = mock.Mock(return_value=future) self.assertEqual(self.playback.time_position, 0) - @SkipTest # Uses sleep and does not work with LocalBackend+DummyOutput + @SkipTest # Uses sleep and does might not work with LocalBackend @populate_playlist def test_time_position_when_playing(self): self.playback.play() From bb8296ceb70c9b942593aee320264da468d0a3c5 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Apr 2011 21:30:15 +0200 Subject: [PATCH 24/25] Fix stale documentation in settings.py --- mopidy/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/settings.py b/mopidy/settings.py index f803c932..b6176f9b 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -173,7 +173,7 @@ MPD_SERVER_PORT = 6600 #: Default:: #: #: OUTPUTS = ( -#: u'mopidy.outputs.LocalAudioOutput', +#: u'mopidy.outputs.LocalOutput', #: ) OUTPUTS = ( u'mopidy.outputs.LocalOutput', From 9b9419fcb3a41accd1db0164da34342528c6eb72 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Apr 2011 21:32:34 +0200 Subject: [PATCH 25/25] s/USER/USERNAME/ --- mopidy/outputs.py | 2 +- mopidy/settings.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mopidy/outputs.py b/mopidy/outputs.py index ee1d20b0..5a57f446 100644 --- a/mopidy/outputs.py +++ b/mopidy/outputs.py @@ -26,6 +26,6 @@ class ShoutcastOutput(BaseOutput): u'ip': settings.SHOUTCAST_OUTPUT_SERVER, u'mount': settings.SHOUTCAST_OUTPUT_MOUNT, u'port': settings.SHOUTCAST_OUTPUT_PORT, - u'username': settings.SHOUTCAST_OUTPUT_USER, + u'username': settings.SHOUTCAST_OUTPUT_USERNAME, u'password': settings.SHOUTCAST_OUTPUT_PASSWORD, }) diff --git a/mopidy/settings.py b/mopidy/settings.py index b6176f9b..c0ee3569 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -190,8 +190,8 @@ SHOUTCAST_OUTPUT_SERVER = u'127.0.0.1' #: #: Default:: #: -#: SHOUTCAST_OUTPUT_USER = u'source' -SHOUTCAST_OUTPUT_USER = u'source' +#: SHOUTCAST_OUTPUT_USERNAME = u'source' +SHOUTCAST_OUTPUT_USERNAME = u'source' #: Password to authenticate with against Shoutcast server. #: