audio: Re-add improved version of python based scanner.
- Unlike the old python version we do not wait for the first audio handoff, we only progress until the PAUSED state. This ensure we don't block on empty files. - Instead of using the signal watch and running the main loop we simply poll the messages from the bus directly allowing for a synchronous code flow. - Between each file the pipeline is always returned to NULL, this is done as we found that gst 0.10 will slow down as the uribin does not cleanup the children it creates for handling each file. This issue is not present in 1.0. - This also works around a segfault that was likely caused by a race condition that seems to trigger in the 0.10 version of the pbutils discoverer. - This version of the scanner also fixes the per track slow down, and works out to be considerably faster than even the built in discoverer from 1.0. - Finally this removes the WMA hack as I kan no longer find any evidence of it being needed.
This commit is contained in:
parent
0ab1aacbc5
commit
d6ab78a86c
88
mopidy/audio/scan.py
Normal file
88
mopidy/audio/scan.py
Normal file
@ -0,0 +1,88 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
import gobject
|
||||
|
||||
import time
|
||||
|
||||
from mopidy import exceptions
|
||||
|
||||
|
||||
class Scanner(object):
|
||||
def __init__(self, timeout=1000):
|
||||
self.timeout_ms = timeout
|
||||
|
||||
sink = gst.element_factory_make('fakesink')
|
||||
|
||||
audio_caps = gst.Caps(b'audio/x-raw-int; audio/x-raw-float')
|
||||
pad_added = lambda src, pad: pad.link(sink.get_pad('sink'))
|
||||
|
||||
self.uribin = gst.element_factory_make('uridecodebin')
|
||||
self.uribin.set_property('caps', audio_caps)
|
||||
self.uribin.connect('pad-added', pad_added)
|
||||
|
||||
self.pipe = gst.element_factory_make('pipeline')
|
||||
self.pipe.add(self.uribin)
|
||||
self.pipe.add(sink)
|
||||
|
||||
self.bus = self.pipe.get_bus()
|
||||
self.bus.set_flushing(True)
|
||||
|
||||
def scan(self, uri):
|
||||
try:
|
||||
self._setup(uri)
|
||||
data = self._collect()
|
||||
# Make sure uri and duration does not come from tags.
|
||||
data[b'uri'] = uri
|
||||
data[gst.TAG_DURATION] = self._query_duration()
|
||||
finally:
|
||||
self._reset()
|
||||
|
||||
return data
|
||||
|
||||
def _setup(self, uri):
|
||||
"""Primes the pipeline for collection."""
|
||||
self.pipe.set_state(gst.STATE_READY)
|
||||
self.uribin.set_property(b'uri', uri)
|
||||
self.bus.set_flushing(False)
|
||||
self.pipe.set_state(gst.STATE_PAUSED)
|
||||
|
||||
def _collect(self):
|
||||
"""Polls for messages to collect data."""
|
||||
start = time.time()
|
||||
timeout_s = self.timeout_ms / float(1000)
|
||||
poll_timeout_ns = 1000
|
||||
data = {}
|
||||
|
||||
while time.time() - start < timeout_s:
|
||||
message = self.bus.poll(gst.MESSAGE_ANY, poll_timeout_ns)
|
||||
|
||||
if message is None:
|
||||
pass # polling the bus timed out.
|
||||
elif message.type == gst.MESSAGE_ERROR:
|
||||
raise exceptions.ScannerError(message.parse_error()[0])
|
||||
elif message.type == gst.MESSAGE_EOS:
|
||||
return data
|
||||
elif message.type == gst.MESSAGE_ASYNC_DONE:
|
||||
if message.src == self.pipe:
|
||||
return data
|
||||
elif message.type == gst.MESSAGE_TAG:
|
||||
taglist = message.parse_tag()
|
||||
for key in taglist.keys():
|
||||
data[key] = taglist[key]
|
||||
|
||||
raise exceptions.ScannerError('Timeout after %dms' % self.timeout_ms)
|
||||
|
||||
def _reset(self):
|
||||
"""Ensures we cleanup child elements and flush the bus."""
|
||||
self.bus.set_flushing(True)
|
||||
self.pipe.set_state(gst.STATE_NULL)
|
||||
|
||||
def _query_duration(self):
|
||||
try:
|
||||
duration = self.pipe.query_duration(gst.FORMAT_TIME, None)[0]
|
||||
return duration // gst.MSECOND
|
||||
except gst.QueryError:
|
||||
return None
|
||||
Loading…
Reference in New Issue
Block a user