def _bufferprobe(self, pad, inbuf):
     start = inbuf.timestamp
     if start == gst.CLOCK_TIME_NONE:
         pad.warning("Got buffer without timestamp ! Forwarding")
         return True
     if inbuf.duration == gst.CLOCK_TIME_NONE:
         stop = inbuf.timestamp + (inbuf.size * gst.SECOND) / self.bitrate
     else:
         stop = inbuf.timestamp + inbuf.duration
     pad.debug("inbuf %s %s" % (gst.TIME_ARGS(start), gst.TIME_ARGS(stop)))
     clip, nstart, nstop = self.segment.clip(gst.FORMAT_TIME, start, stop)
     pad.debug("clip:%r, nstart:%s, nstop:%s" %
               (clip, gst.TIME_ARGS(nstart), gst.TIME_ARGS(nstop)))
     if clip is True:
         if nstart != start or nstop != stop:
             pad.debug("clipping")
             nduration = nstop - nstart
             # clip the buffer
             offset = (nstart - start) * self.bitrate
             # size
             nsize = nduration * self.bitrate
             pad.debug("changing data")
             #inbuf.data = inbuf.data[offset:offset+nsize]
             #inbuf = inbuf.create_sub(offset, nsize)
             inbuf.timestamp = nstart
             inbuf.duration = nstop - nstart
             pad.debug("buffer clipped")
             return False
     return clip
 def _chain(self, pad, inbuf):
     pad.debug("inbuf %r %s %s" % (inbuf, gst.TIME_ARGS(
         inbuf.timestamp), gst.TIME_ARGS(inbuf.duration)))
     start = inbuf.timestamp
     if inbuf.duration == gst.CLOCK_TIME_NONE:
         stop = inbuf.timestamp + (inbuf.size * gst.SECOND) / self.bitrate
     else:
         stop = inbuf.timestamp + inbuf.duration
     clip, nstart, nstop = self.segment.clip(gst.FORMAT_TIME, start, stop)
     self.debug("clip:%r, nstart:%s, nstop:%s" %
                (clip, gst.TIME_ARGS(nstart), gst.TIME_ARGS(nstop)))
     if clip is True:
         if nstart != start and nstop != stop:
             self.debug("clipping")
             nduration = nstop - nstart
             # clip the buffer
             offset = (nstart - start) * self.bitrate
             # size
             nsize = nduration * self.bitrate
             b2 = inbuf.create_sub(offset, nsize)
             b2.timestamp = nstart
             b2.duration = nstop - nstart
             self.debug("buffer clipped")
             return self.srcpad.push(b2)
         self.debug("buffer untouched, just pushing forward")
         return self.srcpad.push(inbuf)
     self.debug("buffer dropped")
     return gst.FLOW_OK
Пример #3
0
    def _src_event(self, unused_pad, event):
        # for the moment we just push it upstream
        self.debug("event %r" % event)
        if event.type == gst.EVENT_SEEK:
            flags = event.parse_seek()[2]
            self.debug("Handling seek event %r" % flags)
            if flags & gst.SEEK_FLAG_FLUSH:
                self.debug("sending flush_start event")
                self.srcpad.push_event(gst.event_new_flush_start())
            self._segment.set_seek(*event.parse_seek())
            self.debug("_segment start:%s stop:%s" % (gst.TIME_ARGS(
                self._segment.start), gst.TIME_ARGS(self._segment.stop)))
            # create a new initial seek
            self.debug("pausing task")
            self.srcpad.pause_task()
            self.debug("task paused")

            self._needsegment = True
            self.debug("Sending FLUS_STOP event")
            if flags & gst.SEEK_FLAG_FLUSH:
                self.srcpad.push_event(gst.event_new_flush_stop())
            self.debug("Restarting our task")
            self.srcpad.start_task(self._our_task)
            self.debug("Returning True")
            return True

        return self.sinkpad.push_event(event)
Пример #4
0
    def chainfunc(self, pad, buf, srcpad):
        self.log("Input %s timestamp: %s, %s" %
                 (srcpad is self.audiosrc and 'audio' or 'video',
                  gst.TIME_ARGS(buf.timestamp), gst.TIME_ARGS(buf.duration)))

        if not self._sendNewSegment:
            self._send_new_segment()

        try:
            self._lock.acquire()
            # Discard buffers outside the configured segment
            if buf.timestamp < self._syncOffset:
                self.warning("Could not clip buffer to segment")
                return gst.FLOW_OK
            if buf.timestamp == gst.CLOCK_TIME_NONE:
                return gst.FLOW_OK
            # Get the input stream time of the buffer
            buf.timestamp -= self._syncOffset
            # Set the accumulated stream time
            buf.timestamp += self._syncTimestamp
            duration = 0
            if buf.duration != gst.CLOCK_TIME_NONE:
                duration = buf.duration
            self._totalTime = max(buf.timestamp + duration, self._totalTime)

            self.log(
                "Output %s timestamp: %s, %s" %
                (srcpad is self.audiosrc and 'audio' or 'video',
                 gst.TIME_ARGS(buf.timestamp), gst.TIME_ARGS(buf.duration)))
        finally:
            self._lock.release()

        srcpad.push(buf)
        return gst.FLOW_OK
Пример #5
0
                def timestampDiscont():
                    prevTs = s["prev-timestamp"]
                    prevDuration = s["prev-duration"]
                    curTs = s["cur-timestamp"]

                    if prevTs == gst.CLOCK_TIME_NONE:
                        self.debug("no previous timestamp")
                        return
                    if prevDuration == gst.CLOCK_TIME_NONE:
                        self.debug("no previous duration")
                        return
                    if curTs == gst.CLOCK_TIME_NONE:
                        self.debug("no current timestamp")
                        return

                    discont = curTs - (prevTs + prevDuration)
                    dSeconds = discont / float(gst.SECOND)
                    self.debug(
                        "we have a discont on eater %s of %.9f s "
                        "between %s and %s ", eater.eaterAlias, dSeconds,
                        gst.TIME_ARGS(prevTs + prevDuration),
                        gst.TIME_ARGS(curTs))

                    eater.timestampDiscont(dSeconds,
                                           float(curTs) / float(gst.SECOND))
Пример #6
0
 def _keyframeTimeValueChanged(self, kf, ptime, value):
     self.debug("kf.time:%s, ptime:%s, value:%r", gst.TIME_ARGS(kf.time),
                gst.TIME_ARGS(ptime), value)
     if kf.time != ptime:
         self._controller.unset(self._property.name, kf.time)
     self._controller.set(self._property.name, ptime, value)
     self.emit("keyframe-moved", kf)
Пример #7
0
def data_probe(pad, data, section=""):
    """Callback to use for gst.Pad.add_*_probe.

    The extra argument will be used to prefix the debug messages
    """
    if section == "":
        section = "%s:%s" % (pad.get_parent().get_name(), pad.get_name())
    if isinstance(data, gst.Buffer):
        log.debug(
            "probe",
            "%s BUFFER timestamp:%s , duration:%s , size:%d , offset:%d , offset_end:%d",
            section, gst.TIME_ARGS(data.timestamp),
            gst.TIME_ARGS(data.duration), data.size, data.offset,
            data.offset_end)
        if data.flags & gst.BUFFER_FLAG_DELTA_UNIT:
            log.debug("probe", "%s DELTA_UNIT", section)
        if data.flags & gst.BUFFER_FLAG_DISCONT:
            log.debug("probe", "%s DISCONT", section)
        if data.flags & gst.BUFFER_FLAG_GAP:
            log.debug("probe", "%s GAP", section)
        log.debug("probe", "%s flags:%r", section, data.flags)
    else:
        log.debug("probe", "%s EVENT %s", section, data.type)
        if data.type == gst.EVENT_NEWSEGMENT:
            upd, rat, fmt, start, stop, pos = data.parse_new_segment()
            log.debug(
                "probe",
                "%s Update:%r rate:%f fmt:%s, start:%s, stop:%s, pos:%s",
                section, upd, rat, fmt, gst.TIME_ARGS(start),
                gst.TIME_ARGS(stop), gst.TIME_ARGS(pos))
    return True
Пример #8
0
    def _our_task(self, something):
        if self._buffer == None:
            self.warning("We were started without a buffer, exiting")
            self.srcpad.pause_task()
            return

        #this is where we repeatedly output our buffer
        self.debug("self:%r, something:%r" % (self, something))

        self.debug("needsegment: %r" % self._needsegment)
        if self._needsegment:
            self.debug("Need to output a new segment")
            segment = gst.event_new_new_segment(False, self._segment.rate,
                                                self._segment.format,
                                                self._segment.start,
                                                self._segment.stop,
                                                self._segment.start)
            self.srcpad.push_event(segment)
            # calculate offset
            # offset is int(segment.start / outputrate)
            self._offset = int(self._segment.start * self._outputrate.num /
                               self._outputrate.denom / gst.SECOND)
            self._needsegment = False
            self.debug("Newsegment event pushed")

        # new position
        position = self._offset * gst.SECOND * self._outputrate.denom / self._outputrate.num
        if self._segment.stop != -1 and position > self._segment.stop:
            self.debug(
                "end of configured segment (position:%s / segment_stop:%s)" %
                (gst.TIME_ARGS(position), gst.TIME_ARGS(self._segment.stop)))
            # end of stream/segment handling
            if self._segment.flags & gst.SEEK_FLAG_SEGMENT:
                # emit a gst.MESSAGE_SEGMENT_DONE
                self.post_message(
                    gst.message_new_segment_done(self, gst.FORMAT_TIME,
                                                 self._segment.stop))
            else:
                self.srcpad.push_event(gst.event_new_eos())
            self.last_return = gst.FLOW_WRONG_STATE
            self.srcpad.pause_task()

        # we need to update the caps here !
        obuf = self._buffer.make_metadata_writable()
        ok, nstart, nstop = self._segment.clip(gst.FORMAT_TIME, position,
                                               position + self._bufferduration)
        if ok:
            obuf.timestamp = nstart
            obuf.duration = nstop - nstart
            obuf.caps = self._srccaps
            self.debug("Pushing out buffer %s" % gst.TIME_ARGS(obuf.timestamp))
            self.last_return = self.srcpad.push(obuf)
        self._offset += 1

        if self.last_return != gst.FLOW_OK:
            self.debug("Pausing ourself, last_return : %s" %
                       gst.flow_get_name(self.last_return))
            self.srcpad.pause_task()
Пример #9
0
def _dataprobe(unused_pad, data):
    if isinstance(data, gst.Buffer):
        print "Buffer", gst.TIME_ARGS(data.timestamp), gst.TIME_ARGS(
            data.duration), data.caps.to_string()
    else:
        print "Event", data.type
        if data.type == gst.EVENT_NEWSEGMENT:
            print data.parse_new_segment()
    return True
Пример #10
0
    def _sink_chain(self, unused_pad, buf):
        self.debug("buffer %s %s" %
                   (gst.TIME_ARGS(buf.timestamp), gst.TIME_ARGS(buf.duration)))
        if self._buffer != None:
            self.debug(
                "already have a buffer ! Returning GST_FLOW_WRONG_STATE")
            return gst.FLOW_WRONG_STATE

        self._buffer = buf
        self.srcpad.start_task(self._our_task)
        return gst.FLOW_WRONG_STATE
Пример #11
0
 def _update_sync_point(self, start, position):
     # Only update the sync point if we haven't received any buffer
     # (totalTime == 0) or we received a reset
     if not self._totalTime and not self._resetReceived:
         return
     self._syncTimestamp = self._totalTime
     if position >= start:
         self._syncOffset = start + (position - start)
     else:
         self._syncOffset = start
     self._resetReceived = False
     self.info("Update sync point to % r, offset to %r" %
               (gst.TIME_ARGS(self._syncTimestamp),
                (gst.TIME_ARGS(self._syncOffset))))
Пример #12
0
    def switch(self, padname):
        switch = self.pipeline.get_by_name('s')
        stop_time = switch.emit('block')
        newpad = switch.get_static_pad(padname)
        start_time = newpad.get_property('running-time')
        
        gst.warning('stop time = %d' % (stop_time,))
        gst.warning('stop time = %s' % (gst.TIME_ARGS(stop_time),))

        gst.warning('start time = %d' % (start_time,))
        gst.warning('start time = %s' % (gst.TIME_ARGS(start_time),))

        gst.warning('switching from %r to %r'
                    % (switch.get_property('active-pad'), padname))
        switch.emit('switch', newpad, stop_time, start_time)
Пример #13
0
    def seek(self, position, format=gst.FORMAT_TIME):
        """
        Seeks in the L{Pipeline} to the given position.

        @param position: Position to seek to
        @type position: L{long}
        @param format: The C{Format} of the seek position
        @type format: C{gst.Format}
        @raise PipelineError: If seek failed
        """
        if format == gst.FORMAT_TIME:
            self.debug("position : %s", gst.TIME_ARGS(position))
        else:
            self.debug("position : %d , format:%d", position, format)
        # FIXME : temporarily deactivate position listener
        #self._listenToPosition(False)

        # clamp between [0, duration]
        if format == gst.FORMAT_TIME:
            position = max(0, min(position, self.getDuration()))

        res = self._pipeline.seek(1.0, format, gst.SEEK_FLAG_FLUSH,
                                  gst.SEEK_TYPE_SET, position,
                                  gst.SEEK_TYPE_NONE, -1)
        if not res:
            self.debug("seeking failed")
            raise PipelineError("seek failed")
        self.debug("seeking succesfull")
        self.emit('position', position)
Пример #14
0
    def _append_file(self, location):
        """
        Appends the given file to the composition, along with the proper mixer effect
        """
        self.debug("location:%s" % location)
        start = self._lastposition
        if self._lastadded:
            start += self._levels[self._lastadded][2]
            start -= self._levels[location][1]

        gnls = self._new_gnl_source(location, start)
        self._composition.add(gnls)

        if self._lastadded:
            # create the mixer
            duration = self._levels[self._lastadded][3] - self._levels[
                self._lastadded][2] + self._levels[location][1]
            mixer = self._new_mixer(start, duration)
            self._composition.add(mixer)

        self._lastposition = start
        self._lastadded = location

        self.debug("lastposition:%s , lastadded:%s" %
                   (gst.TIME_ARGS(self._lastposition), self._lastadded))
Пример #15
0
    def _process_fragment(self, fragment):

        if not self._stream_setup:
            sink = self.get_element("sink")
            pad = sink.get_pad("sink")
            caps = pad.get_negotiated_caps()
            name = caps.get_structure(0).get_name()
            self._setup_stream_type(name)

        self._fragmentsCount = self._fragmentsCount + 1

        # Wait hls-min-window fragments to set the component 'happy'
        if self._fragmentsCount == self._minWindow:
            self.info("%d fragments received. Changing mood to 'happy'",
                      self._fragmentsCount)
            self.setMood(moods.happy)
            self._ready = True

        b = fragment.get_property('buffer')
        index = fragment.get_property('index')
        duration = fragment.get_property('duration')

        if index < self._last_index:
            self.warning(
                "Found a discontinuity last index is %s but current "
                "one is %s", self._last_index, index)
            self.soft_restart()

        fragName = self.hlsring.addFragment(
            b.data, index, round(duration / float(gst.SECOND)))
        self.info('Added fragment "%s", index=%s, duration=%s', fragName,
                  index, gst.TIME_ARGS(duration))
Пример #16
0
 def _startThumbnail(self, timestamp):
     RandomAccessPreviewer._startThumbnail(self, timestamp)
     self.log("timestamp : %s", gst.TIME_ARGS(timestamp))
     self.videopipeline.seek(1.0, gst.FORMAT_TIME,
                             gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE,
                             gst.SEEK_TYPE_SET, timestamp,
                             gst.SEEK_TYPE_NONE, -1)
Пример #17
0
        def probe_cb(pad, buffer):
            """
            Periodically scheduled buffer probe, that ensures that we're
            currently actually having dataflow through our eater
            elements.

            Called from GStreamer threads.

            @param pad:       The gst.Pad srcpad for one eater in this
                              component.
            @param buffer:    A gst.Buffer that has arrived on this pad
            """
            self._last_data_time = time.time()

            self.logMessage('buffer probe on %s has timestamp %s', self.name,
                            gst.TIME_ARGS(buffer.timestamp))

            deferred, probe_id = self._probe_id.pop("id", (None, None))
            if probe_id:
                # This will be None only if detach() has been called.
                self._pad.remove_buffer_probe(probe_id)

                reactor.callFromThread(deferred.callback, None)
                # Data received! Return to happy ASAP:
                reactor.callFromThread(self.watch_poller.run)

            self._first = False

            # let the buffer through
            return True
Пример #18
0
 def _newTime(self, value, frame=-1):
     self.info("value:%s, frame:%d", gst.TIME_ARGS(value), frame)
     self.current_time = value
     self.current_frame = frame
     self.timelabel.set_markup("<tt>%s</tt>" % time_to_string(value))
     if not self.moving_slider:
         self.posadjust.set_value(float(value))
     return False
Пример #19
0
def on_message_application(cutter, message, loop):
    global count
    s = message.structure
    which = 'below'
    if s['above']: which = 'above'
    print "%s: %s threshold" % (gst.TIME_ARGS(s['timestamp']), which)
    if s['above']: count += 1
    if count > 2: loop.quit()
Пример #20
0
 def _newTime(self, value, frame=-1):
     self.info("value:%s, frame:%d", gst.TIME_ARGS(value), frame)
     self.current_time = value
     self.current_frame = frame
     self.timecode_entry.setWidgetValue(value, False)
     if not self.moving_slider:
         self.posadjust.set_value(float(value))
     return False
Пример #21
0
    def do_switch(self, feed=None):
        if feed == None:
            feed = self.idealFeed

        self.clearWarning('temporary-switch-problem')
        if feed == self.activeFeed:
            self.debug("already streaming from feed %r", feed)
            return
        if feed not in self.logicalFeeds:
            self.warning("unknown logical feed: %s", feed)
            return

        # (pad, switch)
        pairs = [self.switchPads[alias] for alias in self.logicalFeeds[feed]]

        stop_times = [e.emit('block') for p, e in pairs]
        start_times = [p.get_property('running-time') for p, e in pairs]

        stop_time = max(stop_times)
        self.debug('stop time = %d', stop_time)
        self.debug('stop time = %s', gst.TIME_ARGS(stop_time))

        if stop_time != gst.CLOCK_TIME_NONE:
            diff = float(max(stop_times) - min(stop_times))
            if diff > gst.SECOND * 10:
                fmt = N_("When switching to %s, feed timestamps out"
                         " of sync by %us")
                self.addWarning('large-timestamp-difference',
                                fmt,
                                feed,
                                diff / gst.SECOND,
                                priority=40)

        start_time = min(start_times)
        self.debug('start time = %s', gst.TIME_ARGS(start_time))

        self.debug('switching from %r to %r', self.activeFeed, feed)
        for p, e in pairs:
            self.debug("switching to pad %r", p)
            e.emit('switch', p, stop_time, start_time)

        self.activeFeed = feed
        self.uiState.set("active-eater", feed)
    def do_render(self, buf):
        self.log("buffer %s %d" %
                 (gst.TIME_ARGS(buf.timestamp), len(buf.data)))
        b = array.array("B")
        b.fromstring(buf)
        pixb = cairo.ImageSurface.create_for_data(b, cairo.FORMAT_ARGB32,
                                                  self.width, self.height,
                                                  self.width * 4)

        self.emit('thumbnail', pixb, buf.timestamp)
        return gst.FLOW_OK
Пример #23
0
    def _videoPadSeekCb(self, pad):
        try:
            duration = self.pipeline.query_duration(gst.FORMAT_TIME)[0]
        except gst.QueryError:
            duration = 0

        self.debug("doing thumbnail seek at %s", gst.TIME_ARGS(duration))

        if duration:
            self.pipeline.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH,
                                      duration / 3)

        pad.set_blocked_async(False, self._videoPadBlockCb)
Пример #24
0
    def _leveller_done_cb(self, l, reason, file):
        if reason != sources.EOS:
            gst.debug("Error: %s" % reason)
            return

        gst.debug("in: %s, out: %s" %
                  (gst.TIME_ARGS(l.mixin), gst.TIME_ARGS(l.mixout)))
        gst.debug("rms: %f, %f dB" % (l.rms, l.rmsdB))

        # store infos
        self._levels[file] = (l.rms, l.mixin, l.mixout, l.length)

        gst.debug("writing level pickle")
        file = open(self._picklepath, "w")
        pickle.dump(self._levels, file)
        file.close()

        self._check_prerolled()
        self._scan()

        # clean up leveller after this handler
        gobject.timeout_add(0, l.clean)
Пример #25
0
    def splitObject(self, position):
        start = self.gnl_object.props.start
        duration = self.gnl_object.props.duration
        if position <= start or position >= start + duration:
            raise TrackError("can't split at position %s" %
                             gst.TIME_ARGS(position))

        other = self.copy()

        other.trimObjectStart(position)
        self.setObjectDuration(position - self.gnl_object.props.start)
        self.setObjectMediaDuration(position - self.gnl_object.props.start)

        return other
Пример #26
0
    def _durationChangedCb(self, unused_pipeline, duration):
        self.debug("duration : %s", gst.TIME_ARGS(duration))
        position = self.posadjust.get_value()
        if duration < position:
            self.posadjust.set_value(float(duration))
        self.posadjust.upper = float(duration)

        if duration == 0:
            self._setUiActive(False)
        else:
            self._setUiActive(True)

        if self._initial_seek is not None:
            seek, self._initial_seek = self._initial_seek, None
            self.pipeline.seek(seek)
Пример #27
0
 def _seekTimeoutCb(self):
     self.pending_seek_id = None
     if self.position != None and self.format != None:
         position, self.position = self.position, None
         format, self.format = self.format, None
         try:
             self.emit('seek', position, format)
         except:
             log.doLog(log.ERROR, None, "seeker",
                       "Error while seeking to position:%s format:%r",
                       (gst.TIME_ARGS(position), format))
             # if an exception happened while seeking, properly
             # reset ourselves
             return False
     return False
Пример #28
0
 def _timelineSeekCb(self, ruler, position, format):
     self.debug("position:%s", gst.TIME_ARGS (position))
     if self.viewer.action != self.project.view_action:
         self.viewer.setPipeline(None)
         self.viewer.hideSlider()
         self.viewer.setAction(self.project.view_action)
         self.viewer.setPipeline(self.project.pipeline)
         # get the pipeline settings and set the DAR of the viewer
         sett = self.project.getSettings()
         self.viewer.setDisplayAspectRatio(float(sett.videopar * sett.videowidth) / float(sett.videoheight))
     # everything above only needs to be done if the viewer isn't already
     # set to the pipeline.
     self.project.pipeline.pause()
     try:
         self.project.pipeline.seek(position, format)
     except:
         self.debug("Seeking failed")
Пример #29
0
    def do_render(self, buf):
        self.log("buffer %s %d" %
                 (gst.TIME_ARGS(buf.timestamp), len(buf.data)))
        b = array.array("b")
        b.fromstring(buf)
        pixb = cairo.ImageSurface.create_for_data(
            b,
            # We don't use FORMAT_ARGB32 because Cairo uses premultiplied
            # alpha, and gstreamer does not.  Discarding the alpha channel
            # is not ideal, but the alternative would be to compute the
            # conversion in python (slow!).
            cairo.FORMAT_RGB24,
            self.width,
            self.height,
            self.width * 4)

        self.emit('thumbnail', pixb, buf.timestamp)
        return gst.FLOW_OK
Пример #30
0
    def _notify_caps_cb(self, pad, args):
        self.discovered_cond.acquire()

        caps = pad.get_negotiated_caps()
        if not caps:
            pad.info("no negotiated caps available")
            self.discovered = True
            self.discovered_cond.notify()
            self.discovered_cond.release()
            return
        # the caps are fixed
        # We now get the total length of that stream
        q = gst.query_new_duration(gst.FORMAT_TIME)
        pad.info("sending duration query")
        if pad.get_peer().query(q):
            format, length = q.parse_duration()
            if format == gst.FORMAT_TIME:
                pad.info("got duration (time) : %s" % (gst.TIME_ARGS(length),))
            else:
                pad.info("got duration : %d [format:%d]" % (length, format))
        else:
            length = -1
            gst.warning("duration query failed")

        # We store the caps and length in the proper location
        if "audio" in caps.to_string():
            self.input_samplerate = caps[0]["rate"]
            if not self.output_samplerate:
                self.output_samplerate = self.input_samplerate
            self.input_channels = caps[0]["channels"]
            if not self.output_channels:
                self.output_channels = self.input_channels
            self.input_duration = length / gst.SECOND

            self.input_totalframes = int(
                self.input_duration * self.input_samplerate)
            if "x-raw-float" in caps.to_string():
                self.input_width = caps[0]["width"]
            else:
                self.input_width = caps[0]["depth"]

        self.discovered = True
        self.discovered_cond.notify()
        self.discovered_cond.release()