From 3da23167ca4aef838a7ffcab6b2036e03c8a2da2 Mon Sep 17 00:00:00 2001 From: Carl Suster Date: Fri, 19 Apr 2019 15:57:15 +1000 Subject: [PATCH 1/2] bpd: support decoders command This uses GStreamer APIs to extract a list of audio decoders and the relevant MIME types and file extensions. Some clients like ncmpcpp use this command to fetch a list of supported file extensions. --- beetsplug/bpd/__init__.py | 10 +++++++ beetsplug/bpd/gstplayer.py | 53 ++++++++++++++++++++++++++++++++++++++ test/test_player.py | 16 +++++++++--- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/beetsplug/bpd/__init__.py b/beetsplug/bpd/__init__.py index a70c6ecd61..5c54c9eabe 100644 --- a/beetsplug/bpd/__init__.py +++ b/beetsplug/bpd/__init__.py @@ -1321,6 +1321,16 @@ def cmd_stats(self, conn): u'db_update: ' + six.text_type(int(self.updated_time)), ) + def cmd_decoders(self, conn): + """Send list of supported decoders and formats.""" + decoders = self.player.get_decoders() + for name, (mimes, exts) in decoders.items(): + yield u'plugin: {}'.format(name) + for ext in exts: + yield u'suffix: {}'.format(ext) + for mime in mimes: + yield u'mime_type: {}'.format(mime) + # Searching. tagtype_map = { diff --git a/beetsplug/bpd/gstplayer.py b/beetsplug/bpd/gstplayer.py index fffa8a6eda..8d4e7c9ff4 100644 --- a/beetsplug/bpd/gstplayer.py +++ b/beetsplug/bpd/gstplayer.py @@ -215,6 +215,59 @@ def block(self): while self.playing: time.sleep(1) + def get_decoders(self): + return get_decoders() + + +def get_decoders(): + """Get supported audio decoders from GStreamer. + Returns a dict mapping decoder element names to the associated media types + and file extensions. + """ + # We only care about audio decoder elements. + filt = (Gst.ELEMENT_FACTORY_TYPE_DEPAYLOADER | + Gst.ELEMENT_FACTORY_TYPE_DEMUXER | + Gst.ELEMENT_FACTORY_TYPE_PARSER | + Gst.ELEMENT_FACTORY_TYPE_DECODER | + Gst.ELEMENT_FACTORY_TYPE_MEDIA_AUDIO) + + decoders = {} + mime_types = set() + for f in Gst.ElementFactory.list_get_elements(filt, Gst.Rank.NONE): + for pad in f.get_static_pad_templates(): + if pad.direction == Gst.PadDirection.SINK: + caps = pad.static_caps.get() + mimes = set() + for i in range(caps.get_size()): + struct = caps.get_structure(i) + mime = struct.get_name() + if mime == 'unknown/unknown': + continue + mimes.add(mime) + mime_types.add(mime) + if mimes: + decoders[f.get_name()] = (mimes, set()) + + # Check all the TypeFindFactory plugin features form the registry. If they + # are associated with an audio media type that we found above, get the list + # of corresponding file extensions. + mime_extensions = {mime: set() for mime in mime_types} + for feat in Gst.Registry.get().get_feature_list(Gst.TypeFindFactory): + caps = feat.get_caps() + if caps: + for i in range(caps.get_size()): + struct = caps.get_structure(i) + mime = struct.get_name() + if mime in mime_types: + mime_extensions[mime].update(feat.get_extensions()) + + # Fill in the slot we left for file extensions. + for name, (mimes, exts) in decoders.items(): + for mime in mimes: + exts.update(mime_extensions[mime]) + + return decoders + def play_simple(paths): """Play the files in paths in a straightforward way, without diff --git a/test/test_player.py b/test/test_player.py index d7c9dba17c..102df1d7d3 100644 --- a/test/test_player.py +++ b/test/test_player.py @@ -44,13 +44,14 @@ def _gstplayer_play(*_): # noqa: 42 gstplayer._GstPlayer = mock.MagicMock( spec_set=[ "time", "volume", "playing", "run", "play_file", "pause", "stop", - "seek", "play" + "seek", "play", "get_decoders", ], **{ 'playing': False, 'volume': 0, 'time.return_value': (0, 0), 'play_file.side_effect': _gstplayer_play, 'play.side_effect': _gstplayer_play, + 'get_decoders.return_value': {'default': ({'audio/mpeg'}, {'mp3'})}, }) gstplayer.GstPlayer = lambda _: gstplayer._GstPlayer sys.modules["beetsplug.bpd.gstplayer"] = gstplayer @@ -879,9 +880,16 @@ class BPDDeviceTest(BPDTestHelper): class BPDReflectionTest(BPDTestHelper): test_implements_reflection = implements({ - 'config', 'commands', 'notcommands', 'urlhandlers', - 'decoders', - }, expectedFailure=True) + 'config', 'commands', 'notcommands', 'urlhandlers', + }, expectedFailure=True) + + def test_cmd_decoders(self): + with self.run_bpd() as client: + response = client.send_command('decoders') + self._assert_ok(response) + self.assertEqual('default', response.data['plugin']) + self.assertEqual('mp3', response.data['suffix']) + self.assertEqual('audio/mpeg', response.data['mime_type']) class BPDPeersTest(BPDTestHelper): From c9327511f6df151ff93f4776a16e72212c16b5ba Mon Sep 17 00:00:00 2001 From: Carl Suster Date: Fri, 19 Apr 2019 16:21:52 +1000 Subject: [PATCH 2/2] Changelog for #3222 --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4ad0f057a5..4b238c3ec8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -89,6 +89,8 @@ New features: * :doc:`/plugins/bpd`: MPD protocol command ``idle`` is now supported, allowing the MPD version to be bumped to 0.14. :bug:`3205` :bug:`800` +* :doc:`/plugins/bpd`: MPD protocol command ``decoders`` is now supported. + :bug:`3222` Changes: