Ejemplo n.º 1
0
    def _collect(self):
        """Polls for messages to collect data."""
        start = time.time()
        timeout_s = self._timeout_ms / float(1000)
        tags = {}

        while time.time() - start < timeout_s:
            if not self._bus.have_pending():
                continue
            message = self._bus.pop()

            if message.type == gst.MESSAGE_ERROR:
                raise exceptions.ScannerError(
                    encoding.locale_decode(message.parse_error()[0]))
            elif message.type == gst.MESSAGE_EOS:
                return tags
            elif message.type == gst.MESSAGE_ASYNC_DONE:
                if message.src == self._pipe:
                    return tags
            elif message.type == gst.MESSAGE_TAG:
                # Taglists are not really dicts, hence the lack of .items() and
                # explicit .keys. We only keep the last tag for each key, as we
                # assume this is the best, some formats will produce multiple
                # taglists. Lastly we force everything to lists for conformity.
                taglist = message.parse_tag()
                for key in taglist.keys():
                    value = taglist[key]
                    if not isinstance(value, list):
                        value = [value]
                    tags[key] = value

        raise exceptions.ScannerError('Timeout after %dms' % self._timeout_ms)
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    def scan(self, uri):
        try:
            info = self.discoverer.discover_uri(uri)
        except gobject.GError as e:
            # Loosing traceback is non-issue since this is from C code.
            raise exceptions.ScannerError(e)

        data = {}
        audio_streams = info.get_audio_streams()

        if not audio_streams:
            raise exceptions.ScannerError('Did not find any audio streams.')

        for stream in audio_streams:
            taglist = stream.get_tags()
            if not taglist:
                continue
            for key in taglist.keys():
                # XXX: For some crazy reason some wma files spit out lists
                # here, not sure if this is due to better data in headers or
                # wma being stupid. So ugly hack for now :/
                if type(taglist[key]) is list:
                    data[key] = taglist[key][0]
                else:
                    data[key] = taglist[key]

        # Never trust metadata for these fields:
        data[b'uri'] = uri
        data[b'duration'] = info.get_duration() // gst.MSECOND

        if data[b'duration'] < 100:
            raise exceptions.ScannerError(
                'Rejecting file with less than 100ms audio data.')

        return data
Ejemplo n.º 4
0
def _process(pipeline, timeout_ms):
    bus = pipeline.get_bus()
    tags = {}
    mime = None
    have_audio = False
    missing_message = None

    types = (Gst.MessageType.ELEMENT | Gst.MessageType.APPLICATION
             | Gst.MessageType.ERROR | Gst.MessageType.EOS
             | Gst.MessageType.ASYNC_DONE | Gst.MessageType.TAG)

    timeout = timeout_ms
    previous = int(time.time() * 1000)
    while timeout > 0:
        message = bus.timed_pop_filtered(timeout * Gst.MSECOND, types)

        if message is None:
            break
        elif message.type == Gst.MessageType.ELEMENT:
            if GstPbutils.is_missing_plugin_message(message):
                missing_message = message
        elif message.type == Gst.MessageType.APPLICATION:
            if message.get_structure().get_name() == 'have-type':
                mime = message.get_structure().get_value('caps').get_name()
                if mime and (mime.startswith('text/')
                             or mime == 'application/xml'):
                    return tags, mime, have_audio
            elif message.get_structure().get_name() == 'have-audio':
                have_audio = True
        elif message.type == Gst.MessageType.ERROR:
            error = encoding.locale_decode(message.parse_error()[0])
            if missing_message and not mime:
                caps = missing_message.get_structure().get_value('detail')
                mime = caps.get_structure(0).get_name()
                return tags, mime, have_audio
            raise exceptions.ScannerError(error)
        elif message.type == Gst.MessageType.EOS:
            return tags, mime, have_audio
        elif message.type == Gst.MessageType.ASYNC_DONE:
            if message.src == pipeline:
                return tags, mime, have_audio
        elif message.type == Gst.MessageType.TAG:
            taglist = message.parse_tag()
            # Note that this will only keep the last tag.
            tags.update(tags_lib.convert_taglist(taglist))

        now = int(time.time() * 1000)
        timeout -= now - previous
        previous = now

    raise exceptions.ScannerError('Timeout after %dms' % timeout_ms)
Ejemplo n.º 5
0
def _process(pipeline, timeout_ms):
    clock = pipeline.get_clock()
    bus = pipeline.get_bus()
    timeout = timeout_ms * gst.MSECOND
    tags = {}
    mime = None
    have_audio = False
    missing_message = None

    types = (gst.MESSAGE_ELEMENT | gst.MESSAGE_APPLICATION | gst.MESSAGE_ERROR
             | gst.MESSAGE_EOS | gst.MESSAGE_ASYNC_DONE | gst.MESSAGE_TAG)

    previous = clock.get_time()
    while timeout > 0:
        message = bus.timed_pop_filtered(timeout, types)

        if message is None:
            break
        elif message.type == gst.MESSAGE_ELEMENT:
            if gst.pbutils.is_missing_plugin_message(message):
                missing_message = message
        elif message.type == gst.MESSAGE_APPLICATION:
            if message.structure.get_name() == 'have-type':
                mime = message.structure['caps'].get_name()
                if mime.startswith('text/') or mime == 'application/xml':
                    return tags, mime, have_audio
            elif message.structure.get_name() == 'have-audio':
                have_audio = True
        elif message.type == gst.MESSAGE_ERROR:
            error = encoding.locale_decode(message.parse_error()[0])
            if missing_message and not mime:
                caps = missing_message.structure['detail']
                mime = caps.get_structure(0).get_name()
                return tags, mime, have_audio
            raise exceptions.ScannerError(error)
        elif message.type == gst.MESSAGE_EOS:
            return tags, mime, have_audio
        elif message.type == gst.MESSAGE_ASYNC_DONE:
            if message.src == pipeline:
                return tags, mime, have_audio
        elif message.type == gst.MESSAGE_TAG:
            taglist = message.parse_tag()
            # Note that this will only keep the last tag.
            tags.update(utils.convert_taglist(taglist))

        now = clock.get_time()
        timeout -= now - previous
        previous = now

    raise exceptions.ScannerError('Timeout after %dms' % timeout_ms)
Ejemplo n.º 6
0
    def scan(self, uri):
        """
        Scan the given uri collecting relevant metadata.

        :param uri: URI of the resource to scan.
        :type event: string
        :return: Dictionary of tags, duration, mtime and uri information.
        """
        try:
            self._setup(uri)
            tags = self._collect()  # Ensure collect before queries.
            data = {
                'uri': uri,
                'tags': tags,
                'mtime': self._query_mtime(uri),
                'duration': self._query_duration()
            }
        finally:
            self._reset()

        if self._min_duration_ms is None:
            return data
        elif data['duration'] >= self._min_duration_ms * gst.MSECOND:
            return data

        raise exceptions.ScannerError('Rejecting file with less than %dms '
                                      'audio data.' % self._min_duration_ms)
Ejemplo n.º 7
0
def test_translate_uri_when_scanner_fails(scanner, provider, caplog):
    scanner.scan.side_effect = exceptions.ScannerError('foo failed')

    result = provider.translate_uri('bar')

    assert result is None
    assert 'Problem scanning URI bar: foo failed' in caplog.text()
Ejemplo n.º 8
0
def _setup_pipeline(uri, proxy_config=None):
    src = Gst.Element.make_from_uri(Gst.URIType.SRC, uri)
    if not src:
        raise exceptions.ScannerError('GStreamer can not open: %s' % uri)

    if proxy_config:
        utils.setup_proxy(src, proxy_config)

    signals = utils.Signals()
    pipeline = Gst.ElementFactory.make('pipeline')
    pipeline.add(src)

    if _has_src_pads(src):
        _setup_decodebin(src, src.get_static_pad('src'), pipeline, signals)
    elif _has_dynamic_src_pad(src):
        signals.connect(src, 'pad-added', _setup_decodebin, pipeline, signals)
    else:
        raise exceptions.ScannerError('No pads found in source element.')

    return pipeline, signals
Ejemplo n.º 9
0
    def test_scan_fails_and_playlist_parsing_fails(self, scanner, provider,
                                                   caplog):

        scanner.scan.side_effect = exceptions.ScannerError('some failure')
        responses.add(responses.GET,
                      STREAM_URI,
                      body=b'some audio data',
                      content_type='audio/mpeg')

        result = provider.translate_uri(STREAM_URI)

        assert 'Unwrapping stream from URI: %s' % STREAM_URI
        assert ('GStreamer failed scanning URI (%s)' % STREAM_URI
                in caplog.text())
        assert (
            'Failed parsing URI (%s) as playlist; found potential stream.' %
            STREAM_URI in caplog.text())
        assert result == STREAM_URI
Ejemplo n.º 10
0
def _setup_pipeline(uri, proxy_config=None):
    src = gst.element_make_from_uri(gst.URI_SRC, uri)
    if not src:
        raise exceptions.ScannerError('GStreamer can not open: %s' % uri)

    typefind = gst.element_factory_make('typefind')
    decodebin = gst.element_factory_make('decodebin2')

    pipeline = gst.element_factory_make('pipeline')
    pipeline.add_many(src, typefind, decodebin)
    gst.element_link_many(src, typefind, decodebin)

    if proxy_config:
        utils.setup_proxy(src, proxy_config)

    typefind.connect('have-type', _have_type, decodebin)
    decodebin.connect('pad-added', _pad_added, pipeline)

    return pipeline
Ejemplo n.º 11
0
    def scan(self, uri):
        """
        Scan the given uri collecting relevant metadata.

        :param uri: URI of the resource to scan.
        :type event: string
        :return: Dictionary of tags, duration, mtime and uri information.
        """
        try:
            self._setup(uri)
            data = self._collect()
            # Make sure uri and duration does not come from tags.
            data[b'uri'] = uri
            data[b'mtime'] = self._query_mtime(uri)
            data[gst.TAG_DURATION] = self._query_duration()
        finally:
            self._reset()

        if data[gst.TAG_DURATION] < self.min_duration_ms * gst.MSECOND:
            raise exceptions.ScannerError('Rejecting file with less than %dms '
                                          'audio data.' % self.min_duration_ms)
        return data
Ejemplo n.º 12
0
    def test_scan_fails_but_playlist_parsing_succeeds(self, scanner, provider,
                                                      caplog):

        scanner.scan.side_effect = [
            # Scanning playlist
            exceptions.ScannerError('some failure'),
            # Scanning stream
            mock.Mock(mime='audio/mpeg', playable=True),
        ]
        responses.add(responses.GET,
                      PLAYLIST_URI,
                      body=BODY,
                      content_type='audio/x-mpegurl')

        result = provider.translate_uri(PLAYLIST_URI)

        assert 'Unwrapping stream from URI: %s' % PLAYLIST_URI
        assert ('GStreamer failed scanning URI (%s)' % PLAYLIST_URI
                in caplog.text())
        assert 'Parsed playlist (%s)' % PLAYLIST_URI in caplog.text()
        assert ('Unwrapped potential audio/mpeg stream: %s' % STREAM_URI
                in caplog.text())
        assert result == STREAM_URI
Ejemplo n.º 13
0
def _setup_pipeline(uri, proxy_config=None):
    src = Gst.Element.make_from_uri(Gst.URIType.SRC, uri)
    if not src:
        raise exceptions.ScannerError('GStreamer can not open: %s' % uri)

    typefind = Gst.ElementFactory.make('typefind')
    decodebin = Gst.ElementFactory.make('decodebin')

    pipeline = Gst.ElementFactory.make('pipeline')
    for e in (src, typefind, decodebin):
        pipeline.add(e)
    src.link(typefind)
    typefind.link(decodebin)

    if proxy_config:
        utils.setup_proxy(src, proxy_config)

    signals = utils.Signals()
    signals.connect(typefind, 'have-type', _have_type, decodebin)
    signals.connect(decodebin, 'pad-added', _pad_added, pipeline)
    signals.connect(decodebin, 'autoplug-select', _autoplug_select)

    return pipeline, signals
Ejemplo n.º 14
0
def _process(pipeline, timeout_ms):
    bus = pipeline.get_bus()
    tags = {}
    mime = None
    have_audio = False
    missing_message = None
    duration = None

    types = (Gst.MessageType.ELEMENT | Gst.MessageType.APPLICATION
             | Gst.MessageType.ERROR | Gst.MessageType.EOS
             | Gst.MessageType.ASYNC_DONE | Gst.MessageType.DURATION_CHANGED
             | Gst.MessageType.TAG)

    timeout = timeout_ms
    start = int(time.time() * 1000)
    while timeout > 0:
        msg = bus.timed_pop_filtered(timeout * Gst.MSECOND, types)
        if msg is None:
            break

        if logger.isEnabledFor(log.TRACE_LOG_LEVEL) and msg.get_structure():
            debug_text = msg.get_structure().to_string()
            if len(debug_text) > 77:
                debug_text = debug_text[:77] + '...'
            _trace('element %s: %s', msg.src.get_name(), debug_text)

        if msg.type == Gst.MessageType.ELEMENT:
            if GstPbutils.is_missing_plugin_message(msg):
                missing_message = msg
        elif msg.type == Gst.MessageType.APPLICATION:
            if msg.get_structure().get_name() == 'have-type':
                mime = msg.get_structure().get_value('caps').get_name()
                if mime and (mime.startswith('text/')
                             or mime == 'application/xml'):
                    return tags, mime, have_audio, duration
            elif msg.get_structure().get_name() == 'have-audio':
                have_audio = True
        elif msg.type == Gst.MessageType.ERROR:
            error = encoding.locale_decode(msg.parse_error()[0])
            if missing_message and not mime:
                caps = missing_message.get_structure().get_value('detail')
                mime = caps.get_structure(0).get_name()
                return tags, mime, have_audio, duration
            raise exceptions.ScannerError(error)
        elif msg.type == Gst.MessageType.EOS:
            return tags, mime, have_audio, duration
        elif msg.type == Gst.MessageType.ASYNC_DONE:
            success, duration = _query_duration(pipeline)
            if tags and success:
                return tags, mime, have_audio, duration

            # Don't try workaround for non-seekable sources such as mmssrc:
            if not _query_seekable(pipeline):
                return tags, mime, have_audio, duration

            # Workaround for upstream bug which causes tags/duration to arrive
            # after pre-roll. We get around this by starting to play the track
            # and then waiting for a duration change.
            # https://bugzilla.gnome.org/show_bug.cgi?id=763553
            logger.debug('Using workaround for duration missing before play.')
            result = pipeline.set_state(Gst.State.PLAYING)
            if result == Gst.StateChangeReturn.FAILURE:
                return tags, mime, have_audio, duration

        elif msg.type == Gst.MessageType.DURATION_CHANGED and tags:
            # VBR formats sometimes seem to not have a duration by the time we
            # go back to paused. So just try to get it right away.
            success, duration = _query_duration(pipeline)
            pipeline.set_state(Gst.State.PAUSED)
            if success:
                return tags, mime, have_audio, duration
        elif msg.type == Gst.MessageType.TAG:
            taglist = msg.parse_tag()
            # Note that this will only keep the last tag.
            tags.update(tags_lib.convert_taglist(taglist))

        timeout = timeout_ms - (int(time.time() * 1000) - start)

    raise exceptions.ScannerError('Timeout after %dms' % timeout_ms)
Ejemplo n.º 15
0
def test_playlistlibraryprovider_scan_track_error(backend, scanner, caplog):
    caplog.set_level(logging.DEBUG)
    scanner.scan.side_effect = exceptions.ScannerError(message="error")
    track = backend.library._scan_track("http://example.com/track1.mp3")
    assert track is None
Ejemplo n.º 16
0
 def test_simplifies_track_name_when_scan_fails(self):
     scan.Scanner.scan = MagicMock(
         side_effect=exceptions.ScannerError('error'))
     prov = MplayerLibraryProvider(backend=None)
     track = prov.lookup('mplayer:http://test_uri')[0]
     self.assertEquals(track.name, 'test_uri')
Ejemplo n.º 17
0
def _process(pipeline, timeout_ms):
    bus = pipeline.get_bus()
    tags = {}
    mime = None
    have_audio = False
    missing_message = None
    duration = None

    types = (Gst.MessageType.ELEMENT | Gst.MessageType.APPLICATION
             | Gst.MessageType.ERROR | Gst.MessageType.EOS
             | Gst.MessageType.ASYNC_DONE | Gst.MessageType.DURATION_CHANGED
             | Gst.MessageType.TAG)

    timeout = timeout_ms
    start = int(time.time() * 1000)
    while timeout > 0:
        message = bus.timed_pop_filtered(timeout * Gst.MSECOND, types)

        if message is None:
            break
        elif message.type == Gst.MessageType.ELEMENT:
            if GstPbutils.is_missing_plugin_message(message):
                missing_message = message
        elif message.type == Gst.MessageType.APPLICATION:
            if message.get_structure().get_name() == 'have-type':
                mime = message.get_structure().get_value('caps').get_name()
                if mime and (mime.startswith('text/')
                             or mime == 'application/xml'):
                    return tags, mime, have_audio, duration
            elif message.get_structure().get_name() == 'have-audio':
                have_audio = True
        elif message.type == Gst.MessageType.ERROR:
            error = encoding.locale_decode(message.parse_error()[0])
            if missing_message and not mime:
                caps = missing_message.get_structure().get_value('detail')
                mime = caps.get_structure(0).get_name()
                return tags, mime, have_audio, duration
            raise exceptions.ScannerError(error)
        elif message.type == Gst.MessageType.EOS:
            return tags, mime, have_audio, duration
        elif message.type == Gst.MessageType.ASYNC_DONE:
            success, duration = _query_duration(pipeline)
            if tags and success:
                return tags, mime, have_audio, duration

            # Workaround for upstream bug which causes tags/duration to arrive
            # after pre-roll. We get around this by starting to play the track
            # and then waiting for a duration change.
            # https://bugzilla.gnome.org/show_bug.cgi?id=763553
            result = pipeline.set_state(Gst.State.PLAYING)
            if result == Gst.StateChangeReturn.FAILURE:
                return tags, mime, have_audio, duration

        elif message.type == Gst.MessageType.DURATION_CHANGED:
            # duration will be read after ASYNC_DONE received; for now
            # just give it a non-None value to flag that we have a duration:
            duration = 0
        elif message.type == Gst.MessageType.TAG:
            taglist = message.parse_tag()
            # Note that this will only keep the last tag.
            tags.update(tags_lib.convert_taglist(taglist))

        timeout = timeout_ms - (int(time.time() * 1000) - start)

        # workaround for https://bugzilla.gnome.org/show_bug.cgi?id=763553:
        # if we got what we want then stop playing (and wait for ASYNC_DONE)
        if tags and duration is not None:
            pipeline.set_state(Gst.State.PAUSED)

    raise exceptions.ScannerError('Timeout after %dms' % timeout_ms)