def _setThumbnail(self, time, pixbuf): # TODO: is "time" guaranteed to be nanosecond precise? # => __tim says: "that's how it should be" # => also see gst-plugins-good/tests/icles/gdkpixbufsink-test # => Daniel: It is *not* nanosecond precise when we remove the videorate # element from the pipeline if time in self.queue: self.queue.remove(time) self.thumb_cache[time] = pixbuf if time in self.thumbs: self.thumbs[time].set_from_gdkpixbuf_animated(pixbuf) else: sorted_times = self.thumbs.keys() sorted_times.sort() index = bisect.bisect(sorted_times, time) time = sorted_times[index] thumb = self.thumbs[sorted_times[index]] if thumb.has_pixel_data: if len(sorted_times) < index + 1: self.warning("timestamp %s does not look like anything we " "requested" % print_ns(time)) return # It might actually be the follwoing thumbnail we were time = sorted_times[index + 1] thumb = self.thumbs[time] if thumb.has_pixel_data: self.warning("Surrounding thumbnails are already set " "for timestamp %s" % print_ns(time)) return self.thumbs[time].set_from_gdkpixbuf_animated(pixbuf)
def simple_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" % print_ns(position)) else: self.debug("position : %d , format:%d" % (position, format)) # clamp between [0, duration] if format == Gst.Format.TIME: position = max(0, min(position, self.getDuration()) - 1) res = self._pipeline.seek(1.0, format, Gst.SeekFlags.FLUSH, Gst.SeekType.SET, position, Gst.SeekType.NONE, -1) if not res: self.debug("seeking failed") raise PipelineError("seek failed") self.debug("seeking successful") self.emit('position', position)
def _seekTimeoutCb(self, relative=False): self.pending_seek_id = None if relative: try: self.emit('seek-relative', self._time) except PipelineError: self.error("Error while seeking %s relative", self._time) # if an exception happened while seeking, properly # reset ourselves return False self._time = None elif self.position is not None and self.format is not None: position, self.position = self.position, None format, self.format = self.format, None try: self.emit('seek', position, format) except PipelineError as e: self.error( "Error while seeking to position:%s format: %r, reason: %s", print_ns(position), format, e) # if an exception happened while seeking, properly # reset ourselves return False return False
def _seekTimeoutCb(self, relative=False): self.pending_seek_id = None if relative: try: self.emit('seek-relative', self._time) except PipelineError: self.error("Error while seeking %s relative", self._time) # if an exception happened while seeking, properly # reset ourselves return False self._time = None elif self.position is not None and self.format is not None: position, self.position = self.position, None format, self.format = self.format, None try: self.emit('seek', position, format) except PipelineError as e: self.error("Error while seeking to position:%s format: %r, reason: %s", print_ns(position), format, e) # if an exception happened while seeking, properly # reset ourselves return False if self.pending_position: self.seek(self.pending_position, on_idle=True) self.pending_position = None return False
def getDuration(self, format=Gst.Format.TIME): """ Get the duration of the C{Pipeline}. """ self.log("format %r" % format) dur = self._getDuration(format) self.log("Got duration %s" % print_ns(dur)) if self._duration != dur: self.emit("duration-changed", dur) self._duration = dur return dur
def getDuration(self, format=Gst.Format.TIME): """ Get the duration of the C{Pipeline}. """ self.log("format %r" % format) dur = self._getDuration(format) if dur is None: self.error("Invalid duration: None") else: self.log("Got duration %s" % print_ns(dur)) if self._duration != dur: self.emit("duration-changed", dur) self._duration = dur return dur
def simple_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 self._waiting_for_async_done is True: self._next_seek = (position, format) self.info("Setting next seek to %s" % (str(self._next_seek))) return if format == Gst.Format.TIME: self.debug("position : %s" % print_ns(position)) else: self.debug("position : %d , format:%d" % (position, format)) # clamp between [0, duration] if format == Gst.Format.TIME: position = max(0, min(position, self.getDuration()) - 1) res = self._pipeline.seek(1.0, format, Gst.SeekFlags.FLUSH, Gst.SeekType.SET, position, Gst.SeekType.NONE, -1) self._waiting_for_async_done = True if self._timeout_async_id: GLib.source_remove(self._timeout_async_id) self._timeout_async_id = 0 self._timeout_async_id = GLib.timeout_add(1000, self._resetWaitingForAsyncDone) if not res: self.debug("seeking failed") raise PipelineError("seek failed") self.lastPosition = position self.debug("seeking successful") self.emit('position', position)
def clipTrimPreview(self, tl_obj, position): """ While a clip is being trimmed, show a live preview of it. """ if isinstance(tl_obj, GES.TitleClip) or tl_obj.props.is_image or not hasattr(tl_obj, "get_uri"): self.log("%s is an image or has no URI, so not previewing trim" % tl_obj) return False clip_uri = tl_obj.props.uri cur_time = time() if not self._tmp_pipeline: self.debug("Creating temporary pipeline for clip %s, position %s", clip_uri, print_ns(position)) self._oldTimelinePos = self.pipeline.getPosition() self._tmp_pipeline = Gst.ElementFactory.make("playbin", None) self._tmp_pipeline.set_property("uri", clip_uri) self.setPipeline(SimplePipeline(self._tmp_pipeline)) self._lastClipTrimTime = cur_time if (cur_time - self._lastClipTrimTime) > 0.2: # Do not seek more than once every 200 ms (for performance) self._tmp_pipeline.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, position) self._lastClipTrimTime = cur_time
def clipTrimPreview(self, tl_obj, position): """ While a clip is being trimmed, show a live preview of it. """ if isinstance(tl_obj, GES.TitleClip) or tl_obj.props.is_image or not hasattr(tl_obj, "get_uri"): self.log("%s is an image or has no URI, so not previewing trim" % tl_obj) return False clip_uri = tl_obj.props.uri cur_time = time() if not self._tmp_pipeline: self.debug("Creating temporary pipeline for clip %s, position %s", clip_uri, print_ns(position)) self._oldTimelinePos = self.pipeline.getPosition() self._tmp_pipeline = Gst.ElementFactory.make("playbin", None) self._tmp_pipeline.set_property("uri", clip_uri) self.setPipeline(SimplePipeline(self._tmp_pipeline, self._tmp_pipeline)) self._lastClipTrimTime = cur_time if (cur_time - self._lastClipTrimTime) > 0.2: # Do not seek more than once every 200 ms (for performance) self._tmp_pipeline.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, position) self._lastClipTrimTime = cur_time
def getDuration(self, format=Gst.Format.TIME): """ Get the duration of the C{Pipeline}. """ self.log("format %r" % format) try: res, dur = self._pipeline.query_duration(format) except Exception, e: self.handleException(e) raise PipelineError("Couldn't get duration") if not res: raise PipelineError("Couldn't get duration") self.log("Got duration %s" % print_ns(dur)) if self._duration != dur: self.emit("duration-changed", dur) self._duration = dur return dur def activatePositionListener(self, interval=300): """ Activate the position listener. When activated, the Pipeline will emit the 'position' signal at the specified interval when it is the PLAYING or PAUSED state. @see: L{deactivatePositionListener}
class SimplePipeline(Signallable, Loggable): """ The Pipeline is only responsible for: - State changes - Position seeking - Position Querying - Along with an periodic callback (optional) Signals: - C{state-change} : The state of the pipeline changed. - C{position} : The current position of the pipeline changed. - C{eos} : The Pipeline has finished playing. - C{error} : An error happened. """ __signals__ = { "state-change": ["state"], "position": ["position"], "duration-changed": ["duration"], "eos": [], "error": ["message", "details"] } def __init__(self, pipeline, video_overlay): Loggable.__init__(self) Signallable.__init__(self) self._pipeline = pipeline self._bus = self._pipeline.get_bus() self._bus.add_signal_watch() self._bus.connect("message", self._busMessageCb) self._listening = False # for the position handler self._listeningInterval = 300 # default 300ms self._listeningSigId = 0 self._duration = Gst.CLOCK_TIME_NONE self.video_overlay = video_overlay def release(self): """ Release the L{Pipeline} and all used L{ObjectFactory} and L{Action}s. Call this method when the L{Pipeline} is no longer used. Forgetting to do so will result in memory loss. @postcondition: The L{Pipeline} will no longer be usable. """ self.deactivatePositionListener() self._bus.disconnect_by_func(self._busMessageCb) self._bus.remove_signal_watch() self._pipeline.setState(Gst.State.NULL) self._bus = None def flushSeek(self): self.pause() try: self.seekRelative(0) except PipelineError: pass def setState(self, state): """ Set the L{Pipeline} to the given state. @raises PipelineError: If the C{Gst.Pipeline} could not be changed to the requested state. """ self.debug("state:%r" % state) res = self._pipeline.set_state(state) if res == Gst.StateChangeReturn.FAILURE: # reset to NULL self._pipeline.set_state(Gst.State.NULL) raise PipelineError( "Failure changing state of the Gst.Pipeline to %r, currently reset to NULL" % state) def getState(self): """ Query the L{Pipeline} for the current state. @see: L{setState} This will do an actual query to the underlying GStreamer Pipeline. @return: The current state. @rtype: C{State} """ change, state, pending = self._pipeline.get_state(0) self.debug("change:%r, state:%r, pending:%r" % (change, state, pending)) return state def play(self): """ Sets the L{Pipeline} to PLAYING """ self.setState(Gst.State.PLAYING) def pause(self): """ Sets the L{Pipeline} to PAUSED """ self.setState(Gst.State.PAUSED) # When the pipeline has been paused we need to update the # timeline/playhead position, as the 'position' signal # is only emitted every 300ms and the playhead jumps # during the playback. try: self.emit("position", self.getPosition()) except PipelineError: # Getting the position failed pass def stop(self): """ Sets the L{Pipeline} to READY """ self.setState(Gst.State.READY) def togglePlayback(self): if self.getState() == Gst.State.PLAYING: self.pause() else: self.play() #{ Position and Seeking methods def getPosition(self, format=Gst.Format.TIME): """ Get the current position of the L{Pipeline}. @param format: The format to return the current position in @type format: C{Gst.Format} @return: The current position or Gst.CLOCK_TIME_NONE @rtype: L{long} @raise PipelineError: If the position couldn't be obtained. """ self.log("format %r" % format) try: res, cur = self._pipeline.query_position(format) except Exception, e: self.handleException(e) raise PipelineError("Couldn't get position") if not res: raise PipelineError("Couldn't get position") self.log("Got position %s" % print_ns(cur)) return cur