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.
This commit is contained in:
Carl Suster 2019-04-19 15:57:15 +10:00
parent 7b910c3fde
commit 3da23167ca
3 changed files with 75 additions and 4 deletions

View file

@ -1321,6 +1321,16 @@ class Server(BaseServer):
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 = {

View file

@ -215,6 +215,59 @@ class GstPlayer(object):
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

View file

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