def __init__(self, action=None, pipeline=None): """ @param action: Specific action to use instead of auto-created one @type action: L{ViewAction} """ gtk.VBox.__init__(self) self.set_border_width(SPACING) Loggable.__init__(self) self.log("New PitiviViewer") self.seeker = Seeker(80) self.seeker.connect('seek', self._seekerSeekCb) self.action = action self.pipeline = pipeline self.current_time = long(0) self._initial_seek = None self.current_frame = -1 self.currentState = gst.STATE_PAUSED self._haveUI = False self._createUi() self.setAction(action) self.setPipeline(pipeline)
def __init__(self, hadj): gtk.Layout.__init__(self) Zoomable.__init__(self) Loggable.__init__(self) self.log("Creating new ScaleRule") self.add_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK) self.set_hadjustment(hadj) # double-buffering properties self.pixmap = None # all values are in pixels self.pixmap_offset = 0 self.pixmap_visible_width = 0 self.pixmap_allocated_width = 0 self.pixmap_old_allocated_width = -1 # This is the number of visible_width we allocate for the pixmap self.pixmap_multiples = 2 # position is in nanoseconds self.position = 0 self.pressed = False self.shaded_duration = gst.CLOCK_TIME_NONE self.max_duration = gst.CLOCK_TIME_NONE self.seeker = Seeker(80) self.seeker.connect('seek', self._seekerSeekCb) self.min_frame_spacing = 5.0 self.frame_height = 5.0 self.frame_rate = gst.Fraction(1 / 1)
def __init__(self, app, undock_action=None, action=None, pipeline=None): gtk.VBox.__init__(self) self.set_border_width(SPACING) self.settings = app.settings self.app = app Loggable.__init__(self) self.log("New PitiviViewer") self.seeker = Seeker(80) self.seeker.connect('seek', self._seekerSeekCb) self.action = action self.pipeline = pipeline self.sink = None self.docked = True self.current_time = long(0) self._initial_seek = None self.current_frame = -1 self.currentState = gst.STATE_PAUSED self._haveUI = False self._createUi() self.target = self.internal self.setAction(action) self.setPipeline(pipeline) self.undock_action = undock_action if undock_action: self.undock_action.connect("activate", self._toggleDocked) if not self.settings.viewerDocked: self.undock()
def __init__(self, name="", uri=None, **kwargs): """ @param name: the name of the project @param uri: the uri of the project """ Loggable.__init__(self) self.log("name:%s, uri:%s", name, uri) self.name = name self.settings = None self.description = "" self.uri = uri self.urichanged = False self.format = None self.sources = SourceList() self.sources.connect("source-added", self._sourceAddedCb) self.sources.connect("source-removed", self._sourceRemovedCb) self._dirty = False self.timeline = Timeline() self.factory = TimelineSourceFactory(self.timeline) self.pipeline = Pipeline() self.view_action = ViewAction() self.view_action.addProducers(self.factory) self.seeker = Seeker(80) self.settings = ExportSettings() self._videocaps = self.settings.getVideoCaps()
def __init__(self, instance, ui_manager): gtk.Table.__init__(self, rows=2, columns=1, homogeneous=False) Loggable.__init__(self) Zoomable.__init__(self) self.log("Creating Timeline") self.project = None self.ui_manager = ui_manager self.app = instance self._temp_objects = None self._factories = None self._finish_drag = False self._position = 0 self._state = gst.STATE_NULL self._createUI() self._prev_duration = 0 self.shrink = True self.rate = gst.Fraction(1, 1) self._seeker = Seeker(80) self._seeker.connect('seek', self._seekerSeekCb)
def __init__(self, app, undock_action=None, action=None, pipeline=None): """ @param action: Specific action to use instead of auto-created one @type action: L{ViewAction} """ gtk.VBox.__init__(self) self.set_border_width(SPACING) self.settings = app.settings self.app = app Loggable.__init__(self) self.log("New PitiviViewer") self.seeker = Seeker(80) self.seeker.connect('seek', self._seekerSeekCb) self.action = action self.pipeline = pipeline self.sink = None self.docked = True self.current_time = long(0) self._initial_seek = None self.current_frame = -1 self.currentState = gst.STATE_PAUSED self._haveUI = False self._createUi() self.target = self.internal self.setAction(action) self.setPipeline(pipeline) self.undock_action = undock_action if undock_action: self.undock_action.connect("activate", self._toggleDocked) if not self.settings.viewerDocked: self.undock()
class PitiviViewer(gtk.VBox, Loggable): __gtype_name__ = 'PitiviViewer' __gsignals__ = { "activate-playback-controls": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_BOOLEAN, )), } """ A Widget to control and visualize a Pipeline @cvar pipeline: The current pipeline @type pipeline: L{Pipeline} @cvar action: The action controlled by this Pipeline @type action: L{ViewAction} """ def __init__(self, action=None, pipeline=None): """ @param action: Specific action to use instead of auto-created one @type action: L{ViewAction} """ gtk.VBox.__init__(self) Loggable.__init__(self) self.log("New PitiviViewer") self.seeker = Seeker(80) self.seeker.connect('seek', self._seekerSeekCb) self.action = action self.pipeline = pipeline self.current_time = long(0) self._initial_seek = None self.current_frame = -1 self.currentState = gst.STATE_PAUSED self._haveUI = False self._createUi() self.setAction(action) self.setPipeline(pipeline) def setPipeline(self, pipeline): """ Set the Viewer to the given Pipeline. Properly switches the currently set action to that new Pipeline. @param pipeline: The Pipeline to switch to. @type pipeline: L{Pipeline}. """ self.debug("self.pipeline:%r, pipeline:%r", self.pipeline, pipeline) if pipeline is not None and pipeline == self.pipeline: return if self.pipeline != None: # remove previously set Pipeline self._disconnectFromPipeline() # make ui inactive self._setUiActive(False) # finally remove previous pipeline self.pipeline = None self.currentState = gst.STATE_PAUSED self.playpause_button.setPause() self._connectToPipeline(pipeline) self.pipeline = pipeline if self.pipeline is not None: self._setUiActive() def setAction(self, action): """ Set the controlled action. @param action: The Action to set. If C{None}, a default L{ViewAction} will be used. @type action: L{ViewAction} or C{None} """ self.debug("self.action:%r, action:%r", self.action, action) if action is not None and action == self.action: return if self.action != None: # if there was one previously, remove it self._disconnectFromAction() if action == None: # get the default action action = self._getDefaultAction() self._connectToAction(action) self.showControls() def _connectToPipeline(self, pipeline): self.debug("pipeline:%r", pipeline) if self.pipeline != None: raise ViewerError("previous pipeline wasn't disconnected") self.pipeline = pipeline if self.pipeline == None: return self.pipeline.connect('position', self._posCb) self.pipeline.activatePositionListener() self.pipeline.connect('state-changed', self._currentStateCb) self.pipeline.connect('element-message', self._elementMessageCb) self.pipeline.connect('duration-changed', self._durationChangedCb) self.pipeline.connect('eos', self._eosCb) # if we have an action set it to that new pipeline if self.action: self.pipeline.setAction(self.action) self.action.activate() def _disconnectFromPipeline(self): self.debug("pipeline:%r", self.pipeline) if self.pipeline == None: # silently return, there's nothing to disconnect from return if self.action and (self.action in self.pipeline.actions): # if we have an action, properly remove it from pipeline if self.action.isActive(): self.pipeline.stop() self.action.deactivate() self.pipeline.removeAction(self.action) self.pipeline.disconnect_by_function(self._posCb) self.pipeline.disconnect_by_function(self._elementMessageCb) #self.deactivatePositionListener() self.pipeline.stop() self.pipeline = None def _connectToAction(self, action): self.debug("action: %r", action) # not sure what we need to do ... self.action = action # FIXME: fix this properly? self.drawingarea.action = action dar = float(4 / 3) try: producer = action.producers[0] self.debug("producer:%r", producer) for stream in producer.output_streams: self.warning("stream:%r", stream) for stream in producer.getOutputStreams(VideoStream): self.debug("stream:%r", stream) if stream.dar: dar = stream.dar continue except: dar = float(4 / 3) self.setDisplayAspectRatio(dar) self.showControls() def _disconnectFromAction(self): self.action = None def _setUiActive(self, active=True): self.debug("active %r", active) self.set_sensitive(active) if self._haveUI: for item in [ self.slider, self.rewind_button, self.back_button, self.playpause_button, self.next_button, self.forward_button, self.timelabel ]: item.set_sensitive(active) if active: self.emit("activate-playback-controls", True) def _getDefaultAction(self): return ViewAction() def _createUi(self): """ Creates the Viewer GUI """ # drawing area self.aframe = gtk.AspectFrame(xalign=0.5, yalign=0.5, ratio=4.0 / 3.0, obey_child=False) self.pack_start(self.aframe, expand=True) self.drawingarea = ViewerWidget(self.action) self.aframe.add(self.drawingarea) # Slider self.posadjust = gtk.Adjustment() self.slider = gtk.HScale(self.posadjust) self.slider.set_draw_value(False) self.slider.connect("button-press-event", self._sliderButtonPressCb) self.slider.connect("button-release-event", self._sliderButtonReleaseCb) self.slider.connect("scroll-event", self._sliderScrollCb) self.pack_start(self.slider, expand=False) self.moving_slider = False self.slider.set_sensitive(False) # Buttons/Controls bbox = gtk.HBox() boxalign = gtk.Alignment(xalign=0.5, yalign=0.5) boxalign.add(bbox) self.pack_start(boxalign, expand=False) self.rewind_button = gtk.ToolButton(gtk.STOCK_MEDIA_REWIND) self.rewind_button.connect("clicked", self._rewindCb) self.rewind_button.set_sensitive(False) bbox.pack_start(self.rewind_button, expand=False) self.back_button = gtk.ToolButton(gtk.STOCK_MEDIA_PREVIOUS) self.back_button.connect("clicked", self._backCb) self.back_button.set_sensitive(False) bbox.pack_start(self.back_button, expand=False) self.playpause_button = PlayPauseButton() self.playpause_button.connect("play", self._playButtonCb) bbox.pack_start(self.playpause_button, expand=False) self.playpause_button.set_sensitive(False) self.next_button = gtk.ToolButton(gtk.STOCK_MEDIA_NEXT) self.next_button.connect("clicked", self._nextCb) self.next_button.set_sensitive(False) bbox.pack_start(self.next_button, expand=False) self.forward_button = gtk.ToolButton(gtk.STOCK_MEDIA_FORWARD) self.forward_button.connect("clicked", self._forwardCb) self.forward_button.set_sensitive(False) bbox.pack_start(self.forward_button, expand=False) # current time self.timelabel = gtk.Label() self.timelabel.set_markup("<tt>00:00:00.000</tt>") self.timelabel.set_alignment(1.0, 0.5) bbox.pack_start(self.timelabel, expand=False, padding=10) self._haveUI = True screen = gdk.screen_get_default() height = screen.get_height() if height >= 800: # show the controls and force the aspect frame to have at least the same # width (+110, which is a magic number to minimize dead padding). bbox.show_all() width, height = bbox.size_request() width += 110 height = int(width / self.aframe.props.ratio) self.aframe.set_size_request(width, height) def showControls(self): if not self.action: return if True: self.rewind_button.show() self.back_button.show() self.playpause_button.show() self.next_button.show() self.forward_button.show() self.slider.show() else: self.rewind_button.hide() self.back_button.hide() self.playpause_button.hide() self.next_button.hide() self.forward_button.hide() self.slider.hide() def setDisplayAspectRatio(self, ratio): """ Sets the DAR of the Viewer to the given ratio. @arg ratio: The aspect ratio to set on the viewer @type ratio: L{float} """ self.debug("Setting ratio of %f [%r]", float(ratio), ratio) try: self.aframe.set_property("ratio", float(ratio)) except: self.warning("could not set ratio !") ## gtk.HScale callbacks for self.slider def _sliderButtonPressCb(self, slider, unused_event): self.info("button pressed") self.moving_slider = True self.valuechangedid = slider.connect("value-changed", self._sliderValueChangedCb) self.pipeline.pause() return False def _sliderButtonReleaseCb(self, slider, unused_event): self.info("slider button release at %s", time_to_string(long(slider.get_value()))) self.moving_slider = False if self.valuechangedid: slider.disconnect(self.valuechangedid) self.valuechangedid = 0 # revert to previous state if self.currentState == gst.STATE_PAUSED: self.pipeline.pause() else: self.pipeline.play() return False def _sliderValueChangedCb(self, slider): """ seeks when the value of the slider has changed """ value = long(slider.get_value()) self.info(gst.TIME_ARGS(value)) if self.moving_slider: self.seek(value) def _sliderScrollCb(self, unused_slider, event): if event.direction == gtk.gdk.SCROLL_LEFT: amount = -gst.SECOND else: amount = gst.SECOND self.seekRelative(amount) def seek(self, position, format=gst.FORMAT_TIME): try: self.seeker.seek(position, format) except: self.warning("seek failed") def _seekerSeekCb(self, seeker, position, format): try: self.pipeline.seek(position, format) except PipelineError: self.error("seek failed %s %s", gst.TIME_ARGS(position), format) 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 ## active Timeline calllbacks 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) ## Control gtk.Button callbacks def _rewindCb(self, unused_button): self.seek(0) def _backCb(self, unused_button): self.seekRelative(-gst.SECOND) def _playButtonCb(self, unused_button, isplaying): self.togglePlayback() def _nextCb(self, unused_button): self.seekRelative(gst.SECOND) def _forwardCb(self, unused_button): try: dur = self.pipeline.getDuration() self.seek(dur - 1) except: self.warning("couldn't get duration") ## public methods for controlling playback def play(self): self.pipeline.play() def pause(self): self.pipeline.pause() def togglePlayback(self): if self.pipeline is None: return self.pipeline.togglePlayback() def seekRelative(self, time): try: self.pipeline.seekRelative(time) except: self.warning("seek failed") def _posCb(self, unused_pipeline, pos): self._newTime(pos) def _currentStateCb(self, unused_pipeline, state): self.info("current state changed : %s", state) if state == int(gst.STATE_PLAYING): self.playpause_button.setPause() elif state == int(gst.STATE_PAUSED): self.playpause_button.setPlay() self.currentState = state def _eosCb(self, unused_pipeline): self.playpause_button.setPlay() def _elementMessageCb(self, unused_pipeline, message): name = message.structure.get_name() self.log('message:%s / %s', message, name) if name == 'prepare-xwindow-id': sink = message.src sink.set_xwindow_id(self.drawingarea.window_xid)
class PitiviViewer(gtk.VBox, Loggable): __gtype_name__ = 'PitiviViewer' __gsignals__ = { "activate-playback-controls": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_BOOLEAN,)), } """ A Widget to control and visualize a Pipeline @cvar pipeline: The current pipeline @type pipeline: L{Pipeline} @cvar action: The action controlled by this Pipeline @type action: L{ViewAction} """ def __init__(self, app, undock_action=None, action=None, pipeline=None): """ @param action: Specific action to use instead of auto-created one @type action: L{ViewAction} """ gtk.VBox.__init__(self) self.set_border_width(SPACING) self.settings = app.settings self.app = app Loggable.__init__(self) self.log("New PitiviViewer") self.seeker = Seeker(80) self.seeker.connect('seek', self._seekerSeekCb) self.action = action self.pipeline = pipeline self.sink = None self.docked = True self.current_time = long(0) self._initial_seek = None self.current_frame = -1 self.currentState = gst.STATE_PAUSED self._haveUI = False self._createUi() self.target = self.internal self.setAction(action) self.setPipeline(pipeline) self.undock_action = undock_action if undock_action: self.undock_action.connect("activate", self._toggleDocked) if not self.settings.viewerDocked: self.undock() def setPipeline(self, pipeline): """ Set the Viewer to the given Pipeline. Properly switches the currently set action to that new Pipeline. @param pipeline: The Pipeline to switch to. @type pipeline: L{Pipeline}. """ self.debug("self.pipeline:%r, pipeline:%r", self.pipeline, pipeline) if pipeline is not None and pipeline == self.pipeline: return if self.pipeline != None: # remove previously set Pipeline self._disconnectFromPipeline() # make ui inactive self._setUiActive(False) # finally remove previous pipeline self.pipeline = None self.currentState = gst.STATE_PAUSED self.playpause_button.setPause() self._connectToPipeline(pipeline) self.pipeline = pipeline if self.pipeline is not None: self._setUiActive() def setAction(self, action): """ Set the controlled action. @param action: The Action to set. If C{None}, a default L{ViewAction} will be used. @type action: L{ViewAction} or C{None} """ self.debug("self.action:%r, action:%r", self.action, action) if action is not None and action == self.action: return if self.action != None: # if there was one previously, remove it self._disconnectFromAction() if action == None: # get the default action action = self._getDefaultAction() self._connectToAction(action) self.showControls() def _connectToPipeline(self, pipeline): self.debug("pipeline:%r", pipeline) if self.pipeline != None: raise ViewerError("previous pipeline wasn't disconnected") self.pipeline = pipeline if self.pipeline == None: return self.pipeline.connect('position', self._posCb) self.pipeline.activatePositionListener() self.pipeline.connect('state-changed', self._currentStateCb) self.pipeline.connect('element-message', self._elementMessageCb) self.pipeline.connect('duration-changed', self._durationChangedCb) self.pipeline.connect('eos', self._eosCb) self.pipeline.connect("state-changed", self.internal.currentStateCb) # if we have an action set it to that new pipeline if self.action: self.pipeline.setAction(self.action) self.action.activate() def _disconnectFromPipeline(self): self.debug("pipeline:%r", self.pipeline) if self.pipeline == None: # silently return, there's nothing to disconnect from return if self.action and (self.action in self.pipeline.actions): # if we have an action, properly remove it from pipeline if self.action.isActive(): self.pipeline.stop() self.action.deactivate() self.pipeline.removeAction(self.action) self.pipeline.disconnect_by_function(self._posCb) self.pipeline.disconnect_by_function(self._currentStateCb) self.pipeline.disconnect_by_function(self._elementMessageCb) self.pipeline.disconnect_by_function(self._durationChangedCb) self.pipeline.disconnect_by_function(self._eosCb) self.pipeline.disconnect_by_function(self.internal.currentStateCb) self.pipeline.stop() self.pipeline = None def _connectToAction(self, action): self.debug("action: %r", action) # not sure what we need to do ... self.action = action # FIXME: fix this properly? self.internal.action = action dar = float(4 / 3) try: producer = action.producers[0] self.debug("producer:%r", producer) for stream in producer.output_streams: self.warning("stream:%r", stream) for stream in producer.getOutputStreams(VideoStream): self.debug("stream:%r", stream) if stream.dar: dar = stream.dar continue except: dar = float(4 / 3) self.setDisplayAspectRatio(dar) self.showControls() def _disconnectFromAction(self): self.action = None def _setUiActive(self, active=True): self.debug("active %r", active) self.set_sensitive(active) if self._haveUI: for item in [self.slider, self.goToStart_button, self.back_button, self.playpause_button, self.forward_button, self.goToEnd_button, self.timecode_entry]: item.set_sensitive(active) if active: self.emit("activate-playback-controls", True) def _getDefaultAction(self): return ViewAction() def _externalWindowDeleteCb(self, window, event): self.dock() return True def _externalWindowConfigureCb(self, window, event): self.settings.viewerWidth = event.width self.settings.viewerHeight = event.height self.settings.viewerX = event.x self.settings.viewerY = event.y def _createUi(self): """ Creates the Viewer GUI """ # drawing area self.aframe = gtk.AspectFrame(xalign=0.5, yalign=0.5, ratio=4.0 / 3.0, obey_child=False) self.internal = ViewerWidget(self.action, self.app.settings) self.internal.init_transformation_events() self.internal.show() self.aframe.add(self.internal) self.pack_start(self.aframe, expand=True) self.external_window = gtk.Window() vbox = gtk.VBox() vbox.set_spacing(SPACING) self.external_window.add(vbox) self.external = ViewerWidget(self.action, self.app.settings) vbox.pack_start(self.external) self.external_window.connect("delete-event", self._externalWindowDeleteCb) self.external_window.connect("configure-event", self._externalWindowConfigureCb) self.external_vbox = vbox self.external_vbox.show_all() # Slider self.posadjust = gtk.Adjustment() self.slider = gtk.HScale(self.posadjust) self.slider.set_draw_value(False) self.slider.connect("button-press-event", self._sliderButtonPressCb) self.slider.connect("button-release-event", self._sliderButtonReleaseCb) self.slider.connect("scroll-event", self._sliderScrollCb) self.pack_start(self.slider, expand=False) self.moving_slider = False self.slider.set_sensitive(False) # Buttons/Controls bbox = gtk.HBox() boxalign = gtk.Alignment(xalign=0.5, yalign=0.5) boxalign.add(bbox) self.pack_start(boxalign, expand=False) self.goToStart_button = gtk.ToolButton(gtk.STOCK_MEDIA_PREVIOUS) self.goToStart_button.connect("clicked", self._goToStartCb) self.goToStart_button.set_tooltip_text(_("Go to the beginning of the timeline")) self.goToStart_button.set_sensitive(False) bbox.pack_start(self.goToStart_button, expand=False) self.back_button = gtk.ToolButton(gtk.STOCK_MEDIA_REWIND) self.back_button.connect("clicked", self._backCb) self.back_button.set_tooltip_text(_("Go back one second")) self.back_button.set_sensitive(False) bbox.pack_start(self.back_button, expand=False) self.playpause_button = PlayPauseButton() self.playpause_button.connect("play", self._playButtonCb) bbox.pack_start(self.playpause_button, expand=False) self.playpause_button.set_sensitive(False) self.forward_button = gtk.ToolButton(gtk.STOCK_MEDIA_FORWARD) self.forward_button.connect("clicked", self._forwardCb) self.forward_button.set_tooltip_text(_("Go forward one second")) self.forward_button.set_sensitive(False) bbox.pack_start(self.forward_button, expand=False) self.goToEnd_button = gtk.ToolButton(gtk.STOCK_MEDIA_NEXT) self.goToEnd_button.connect("clicked", self._goToEndCb) self.goToEnd_button.set_tooltip_text(_("Go to the end of the timeline")) self.goToEnd_button.set_sensitive(False) bbox.pack_start(self.goToEnd_button, expand=False) # current time self.timecode_entry = TimeWidget() self.timecode_entry.setWidgetValue(0) self.timecode_entry.connect("value-changed", self._jumpToTimecodeCb) self.timecode_entry.connectFocusEvents(self._entryFocusInCb, self._entryFocusOutCb) bbox.pack_start(self.timecode_entry, expand=False, padding=10) self._haveUI = True screen = gdk.screen_get_default() height = screen.get_height() if height >= 800: # show the controls and force the aspect frame to have at least the same # width (+110, which is a magic number to minimize dead padding). bbox.show_all() width, height = bbox.size_request() width += 110 height = int(width / self.aframe.props.ratio) self.aframe.set_size_request(width, height) self.show_all() self.buttons = boxalign _showingSlider = True def showSlider(self): self._showingSlider = True self.slider.show() def hideSlider(self): self._showingSlider = False self.slider.hide() def showControls(self): if not self.action: return if True: self.goToStart_button.show() self.back_button.show() self.playpause_button.show() self.forward_button.show() self.goToEnd_button.show() if self._showingSlider: self.slider.show() else: self.goToStart_button.hide() self.back_button.hide() self.playpause_button.hide() self.forward_button.hide() self.goToEnd_button.hide() self.slider.hide() def setDisplayAspectRatio(self, ratio): """ Sets the DAR of the Viewer to the given ratio. @arg ratio: The aspect ratio to set on the viewer @type ratio: L{float} """ self.debug("Setting ratio of %f [%r]", float(ratio), ratio) try: self.aframe.set_property("ratio", float(ratio)) except: self.warning("could not set ratio !") ## gtk.HScale callbacks for self.slider def _entryFocusInCb(self, entry, event): sensitive_actions = self.app.gui.sensitive_actions self.app.gui.setActionsSensitive(sensitive_actions, False) self.app.gui.setActionsSensitive(['DeleteObj'], False) def _entryFocusOutCb(self, entry, event): sensitive_actions = self.app.gui.sensitive_actions self.app.gui.setActionsSensitive(sensitive_actions, True) self.app.gui.setActionsSensitive(['DeleteObj'], True) def _sliderButtonPressCb(self, slider, event): # borrow totem hack for seek-on-click behavior event.button = 2 self.info("button pressed") self.moving_slider = True self.valuechangedid = slider.connect("value-changed", self._sliderValueChangedCb) self.pipeline.pause() return False def _sliderButtonReleaseCb(self, slider, event): # borrow totem hack for seek-on-click behavior event.button = 2 self.info("slider button release at %s", time_to_string(long(slider.get_value()))) self.moving_slider = False if self.valuechangedid: slider.disconnect(self.valuechangedid) self.valuechangedid = 0 # revert to previous state if self.currentState == gst.STATE_PAUSED: self.pipeline.pause() else: self.pipeline.play() return False def _sliderValueChangedCb(self, slider): """ seeks when the value of the slider has changed """ value = long(slider.get_value()) self.info(gst.TIME_ARGS(value)) if self.moving_slider: self.seek(value) def _sliderScrollCb(self, unused_slider, event): if event.direction == gtk.gdk.SCROLL_LEFT: amount = -gst.SECOND else: amount = gst.SECOND self.seekRelative(amount) def seek(self, position, format=gst.FORMAT_TIME): try: self.seeker.seek(position, format) except: self.warning("seek failed") def _seekerSeekCb(self, seeker, position, format): try: self.pipeline.seek(position, format) except PipelineError: self.error("seek failed %s %s", gst.TIME_ARGS(position), format) 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 ## active Timeline calllbacks 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) ## Control gtk.Button callbacks def setZoom(self, zoom): if self.target.box: maxSize = self.target.area width = int(float(maxSize.width) * zoom) height = int(float(maxSize.height) * zoom) area = gtk.gdk.Rectangle((maxSize.width - width) / 2, (maxSize.height - height) / 2, width, height) self.sink.set_render_rectangle(*area) self.target.box.update_size(area) self.target.zoom = zoom self.target.sink = self.sink self.target.renderbox() def _goToStartCb(self, unused_button): self.seek(0) def _backCb(self, unused_button): self.seekRelative(-gst.SECOND) def _playButtonCb(self, unused_button, isplaying): self.togglePlayback() def _forwardCb(self, unused_button): self.seekRelative(gst.SECOND) def _goToEndCb(self, unused_button): try: dur = self.pipeline.getDuration() self.seek(dur - 1) except: self.warning("couldn't get duration") ## Callback for jumping to a specific timecode def _jumpToTimecodeCb(self, widget): nanoseconds = widget.getWidgetValue() self.seek(nanoseconds) ## public methods for controlling playback def play(self): self.pipeline.play() def pause(self): self.pipeline.pause() def togglePlayback(self): if self.pipeline is None: return self.pipeline.togglePlayback() def undock(self): if not self.undock_action: self.error("Cannot undock because undock_action is missing.") return if not self.docked: return self.docked = False self.settings.viewerDocked = False self.undock_action.set_label(_("Dock Viewer")) self.remove(self.buttons) self.remove(self.slider) self.external_vbox.pack_end(self.slider, False, False) self.external_vbox.pack_end(self.buttons, False, False) self.external_window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_UTILITY) self.external_window.show() self.target = self.external # if we are playing, switch output immediately if self.sink: self._switch_output_window() self.hide() self.external_window.move(self.settings.viewerX, self.settings.viewerY) self.external_window.resize(self.settings.viewerWidth, self.settings.viewerHeight) def dock(self): if not self.undock_action: self.error("Cannot dock because undock_action is missing.") return if self.docked: return self.docked = True self.settings.viewerDocked = True self.undock_action.set_label(_("Undock Viewer")) self.target = self.internal self.external_vbox.remove(self.slider) self.external_vbox.remove(self.buttons) self.pack_end(self.slider, False, False) self.pack_end(self.buttons, False, False) self.show() # if we are playing, switch output immediately if self.sink: self._switch_output_window() self.external_window.hide() def _toggleDocked(self, action): if self.docked: self.undock() else: self.dock() def seekRelative(self, time): try: self.pipeline.seekRelative(time) except: self.warning("seek failed") def _posCb(self, unused_pipeline, pos): self._newTime(pos) def _currentStateCb(self, unused_pipeline, state): self.info("current state changed : %s", state) if state == int(gst.STATE_PLAYING): self.playpause_button.setPause() elif state == int(gst.STATE_PAUSED): self.playpause_button.setPlay() else: self.sink = None self.currentState = state def _eosCb(self, unused_pipeline): self.playpause_button.setPlay() def _elementMessageCb(self, unused_pipeline, message): name = message.structure.get_name() self.log('message:%s / %s', message, name) if name == 'prepare-xwindow-id': sink = message.src self.sink = sink self._switch_output_window() def _switch_output_window(self): gtk.gdk.threads_enter() self.sink.set_xwindow_id(self.target.window_xid) self.sink.expose() gtk.gdk.threads_leave()
class Timeline(gtk.Table, Loggable, Zoomable): # the screen width of the current unit unit_width = 10 # specific levels of zoom, in (multiplier, unit) pairs which # from zoomed out to zoomed in def __init__(self, instance, ui_manager): gtk.Table.__init__(self, rows=2, columns=1, homogeneous=False) Loggable.__init__(self) Zoomable.__init__(self) self.log("Creating Timeline") self.project = None self.ui_manager = ui_manager self.app = instance self._temp_objects = None self._factories = None self._finish_drag = False self._position = 0 self._state = gst.STATE_NULL self._createUI() self._prev_duration = 0 self.shrink = True self.rate = gst.Fraction(1, 1) self._seeker = Seeker(80) self._seeker.connect('seek', self._seekerSeekCb) def _createUI(self): self.leftSizeGroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) self.hadj = gtk.Adjustment() self.vadj = gtk.Adjustment() # controls for tracks and layers self._controls = TimelineControls() controlwindow = gtk.ScrolledWindow(None, self.vadj) controlwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) controlwindow.add_with_viewport(self._controls) controlwindow.set_size_request(-1, 1) self.attach(controlwindow, 0, 1, 1, 2, xoptions=0) # timeline ruler self.ruler = ruler.ScaleRuler(self.hadj) self.ruler.set_size_request(0, 25) self.ruler.set_border_width(2) self.ruler.connect("key-press-event", self._keyPressEventCb) self.attach(self.ruler, 1, 2, 0, 1, yoptions=0) # proportional timeline self._canvas = TimelineCanvas(self.app) timelinewindow = gtk.ScrolledWindow(self.hadj, self.vadj) timelinewindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) timelinewindow.add(self._canvas) #FIXME: remove padding between scrollbar and scrolled window self.attach(timelinewindow, 1, 2, 1, 2) # drag and drop self.drag_dest_set(gtk.DEST_DEFAULT_MOTION, [dnd.FILESOURCE_TUPLE], gtk.gdk.ACTION_COPY) self.connect("drag-data-received", self._dragDataReceivedCb) self.connect("drag-leave", self._dragLeaveCb) self.connect("drag-drop", self._dragDropCb) self.connect("drag-motion", self._dragMotionCb) # toolbar actions actions = ( ("ZoomIn", gtk.STOCK_ZOOM_IN, None, None, ZOOM_IN, self._zoomInCb), ("ZoomOut", gtk.STOCK_ZOOM_OUT, None, None, ZOOM_OUT, self._zoomOutCb), ) selection_actions = ( ("DeleteObj", gtk.STOCK_DELETE, None, "Delete", DELETE, self.deleteSelected), ("UnlinkObj", "pitivi-unlink", None, "<Shift><Control>L", UNLINK, self.unlinkSelected), ("LinkObj", "pitivi-link", None, "<Control>L", LINK, self.linkSelected), ("UngroupObj", "pitivi-ungroup", None, "<Shift><Control>G", UNGROUP, self.ungroupSelected), ("GroupObj", "pitivi-group", None, "<Control>G", GROUP, self.groupSelected), ) toggle_actions = (("Razor", "pitivi-split", _("Razor"), "<Ctrl>R", RAZOR, self.toggleRazor), ) actiongroup = gtk.ActionGroup("timelinepermanent") actiongroup.add_actions(actions) actiongroup.add_toggle_actions(toggle_actions) self.ui_manager.insert_action_group(actiongroup, 0) actiongroup = gtk.ActionGroup("timelineselection") actiongroup.add_actions(selection_actions) self.link_action = actiongroup.get_action("LinkObj") self.unlink_action = actiongroup.get_action("UnlinkObj") self.group_action = actiongroup.get_action("GroupObj") self.ungroup_action = actiongroup.get_action("UngroupObj") self.delete_action = actiongroup.get_action("DeleteObj") self.ui_manager.insert_action_group(actiongroup, -1) self.ui_manager.add_ui_from_string(ui) # drag and drop self.drag_dest_set(gtk.DEST_DEFAULT_MOTION, [dnd.FILESOURCE_TUPLE], gtk.gdk.ACTION_COPY) self.connect("drag-data-received", self._dragDataReceivedCb) self.connect("drag-leave", self._dragLeaveCb) self.connect("drag-drop", self._dragDropCb) self.connect("drag-motion", self._dragMotionCb) self._canvas.connect("button-press-event", self._buttonPress) self._canvas.connect("button-release-event", self._buttonRelease) self._canvas.connect("key-press-event", self._keyPressEventCb) ## Event callbacks def _keyPressEventCb(self, unused_widget, event): kv = event.keyval self.debug("kv:%r", kv) if kv not in [gtk.keysyms.Left, gtk.keysyms.Right]: return False mod = event.get_state() try: if mod & gtk.gdk.CONTROL_MASK: now = self.project.pipeline.getPosition() ltime, rtime = self.project.timeline.edges.closest(now) if kv == gtk.keysyms.Left: if mod & gtk.gdk.SHIFT_MASK: self._seekRelative(-gst.SECOND) elif mod & gtk.gdk.CONTROL_MASK: self._seeker.seek(ltime + 1) else: self._seekRelative(-long(self.rate * gst.SECOND)) elif kv == gtk.keysyms.Right: if mod & gtk.gdk.SHIFT_MASK: self._seekRelative(gst.SECOND) elif mod & gtk.gdk.CONTROL_MASK: self._seeker.seek(rtime + 1) else: self._seekRelative(long(self.rate * gst.SECOND)) finally: return True def _seekRelative(self, time): pipeline = self.project.pipeline seekvalue = max( 0, min(pipeline.getPosition() + time, pipeline.getDuration())) self._seeker.seek(seekvalue) def _seekerSeekCb(self, seeker, position, format): self.project.pipeline.seek(position, format) def _buttonPress(self, window, event): self.shrink = False def _buttonRelease(self, window, event): self.shrink = True self._timelineStartDurationChanged(self.timeline, self.timeline.duration) ## Drag and Drop callbacks def _dragMotionCb(self, unused, context, x, y, timestamp): self.warning("self._factories:%r, self._temp_objects:%r", not not self._factories, not not self._temp_objects) if self._factories is None: atom = gtk.gdk.atom_intern(dnd.FILESOURCE_TUPLE[0]) self.drag_get_data(context, atom, timestamp) self.drag_highlight() else: # actual drag-and-drop if not self._temp_objects: self.timeline.disableUpdates() self._add_temp_source() focus = self._temp_objects[0] self._move_context = MoveContext(self.timeline, focus, set(self._temp_objects[1:])) self._move_temp_source(self.hadj.props.value + x, y) return True def _dragLeaveCb(self, unused_layout, unused_context, unused_tstamp): if self._temp_objects: try: for obj in self._temp_objects: self.timeline.removeTimelineObject(obj, deep=True) finally: self._temp_objects = None self.drag_unhighlight() self.timeline.enableUpdates() def _dragDropCb(self, widget, context, x, y, timestamp): self.app.action_log.begin("add clip") self._add_temp_source() focus = self._temp_objects[0] self._move_context = MoveContext(self.timeline, focus, set(self._temp_objects[1:])) self._move_temp_source(self.hadj.props.value + x, y) self._move_context.finish() self.app.action_log.commit() context.drop_finish(True, timestamp) self._factories = None self._temp_objects = None return True def _dragDataReceivedCb(self, unused_layout, context, x, y, selection, targetType, timestamp): self.log("SimpleTimeline, targetType:%d, selection.data:%s" % (targetType, selection.data)) # FIXME: let's have just one target type, call it # TYPE_PITIVI_OBJECTFACTORY. # TODO: handle uri targets by doign an import-add. This would look # something like this: # tell current project to import the uri # wait for source-added signal, meanwhile ignore dragMotion signals # when ready, add factories to the timeline. if targetType == dnd.TYPE_PITIVI_FILESOURCE: uris = selection.data.split("\n") else: context.finish(False, False, timestamp) self._factories = [self.project.sources.getUri(uri) for uri in uris] context.drag_status(gtk.gdk.ACTION_COPY, timestamp) return True def _add_temp_source(self): self._temp_objects = [ self.timeline.addSourceFactory(factory) for factory in self._factories ] def _move_temp_source(self, x, y): x1, y1, x2, y2 = self._controls.get_allocation() offset = 10 + (x2 - x1) x, y = self._canvas.convert_from_pixels(x - offset, y) priority = int((y // (LAYER_HEIGHT_EXPANDED + LAYER_SPACING))) delta = Zoomable.pixelToNs(x) self._move_context.editTo(delta, priority) ## Zooming and Scrolling def zoomChanged(self): # this has to be in a timeout, because the resize hasn't actually # completed yet, and so the canvas can't actually complete the scroll gobject.idle_add(self.scrollToPlayhead) def timelinePositionChanged(self, position): self._position = position self.ruler.timelinePositionChanged(position) self._canvas._position = position if self._state == gst.STATE_PLAYING: self.scrollToPlayhead() def stateChanged(self, state): self._state = state def scrollToPlayhead(self): width = self.get_allocation().width new_pos = Zoomable.nsToPixel(self._position) scroll_pos = self.hadj.get_value() if (new_pos < scroll_pos) or (new_pos > scroll_pos + width): self.scrollToPosition(new_pos - width / 2) return False def scrollToPosition(self, position): if position > self.hadj.upper: # we can't perform the scroll because the canvas needs to be # updated gobject.idle_add(self._scrollToPosition, position) else: self._scrollToPosition(position) def _scrollToPosition(self, position): self.hadj.set_value(position) return False ## Project callbacks def _setProject(self): if self.project: self.timeline = self.project.timeline self._controls.timeline = self.timeline self._canvas.timeline = self.timeline self._canvas.zoomChanged() self.ruler.setProjectFrameRate( self.project.getSettings().videorate) self.ruler.zoomChanged() self._settingsChangedCb(self.project) project = receiver(_setProject) @handler(project, "settings-changed") def _settingsChangedCb(self, project): rate = self.project.getSettings().videorate self.rate = float(1 / rate) self.ruler.setProjectFrameRate(rate) ## Timeline callbacks def _setTimeline(self): if self.timeline: self._timelineSelectionChanged(self.timeline) self._timelineStartDurationChanged(self.timeline, self.timeline.duration) self._controls.timeline = self.timeline timeline = receiver(_setTimeline) @handler(timeline, "duration-changed") def _timelineStartDurationChanged(self, unused_timeline, duration): if self.shrink: self._prev_duration = duration self.ruler.setMaxDuration(duration + 60 * gst.SECOND) self._canvas.setMaxDuration(duration + 60 * gst.SECOND) self.ruler.setShadedDuration(duration) else: # only resize if new size is larger if duration > self._prev_duration: self._prev_duration = duration self.ruler.setMaxDuration(duration) self._canvas.setMaxDuration(duration) #self.ruler.setShadedDuration(duration) @handler(timeline, "selection-changed") def _timelineSelectionChanged(self, timeline): delete = False link = False unlink = False group = False ungroup = False timeline_objects = {} if timeline.selection: delete = True if len(timeline.selection) > 1: link = True group = True start = None duration = None for obj in self.timeline.selection: if obj.link: unlink = True if len(obj.track_objects) > 1: ungroup = True if start is not None and duration is not None: if obj.start != start or obj.duration != duration: group = False else: start = obj.start duration = obj.duration self.delete_action.set_sensitive(delete) self.link_action.set_sensitive(link) self.unlink_action.set_sensitive(unlink) self.group_action.set_sensitive(group) self.ungroup_action.set_sensitive(ungroup) ## ToolBar callbacks def hide(self): self.actiongroup.set_visible(False) gtk.Vbox.hide(self) def _zoomInCb(self, unused_action): Zoomable.zoomIn() def _zoomOutCb(self, unused_action): Zoomable.zoomOut() def deleteSelected(self, unused_action): if self.timeline: self.app.action_log.begin("delete clip") self.timeline.deleteSelection() self.app.action_log.commit() def unlinkSelected(self, unused_action): if self.timeline: self.timeline.unlinkSelection() def linkSelected(self, unused_action): if self.timeline: self.timeline.linkSelection() def ungroupSelected(self, unused_action): if self.timeline: self.timeline.ungroupSelection() def groupSelected(self, unused_action): if self.timeline: self.timeline.groupSelection() def toggleRazor(self, action): if action.props.active: self._canvas.activateRazor(action) else: self._canvas.deactivateRazor()
class ScaleRuler(gtk.Layout, Zoomable, Loggable): __gsignals__ = { "expose-event": "override", "size-allocate": "override", "realize": "override", "button-press-event": "override", "button-release-event": "override", "motion-notify-event": "override", "scroll-event": "override", "seek": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [gobject.TYPE_UINT64]) } border = 0 min_tick_spacing = 3 scale = [0, 0, 0, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 3600] subdivide = ((1, 1.0), (2, 0.5), (10, .25)) def __init__(self, hadj): gtk.Layout.__init__(self) Zoomable.__init__(self) Loggable.__init__(self) self.log("Creating new ScaleRule") self.add_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK) self.set_hadjustment(hadj) # double-buffering properties self.pixmap = None # all values are in pixels self.pixmap_offset = 0 self.pixmap_visible_width = 0 self.pixmap_allocated_width = 0 self.pixmap_old_allocated_width = -1 # This is the number of visible_width we allocate for the pixmap self.pixmap_multiples = 2 # position is in nanoseconds self.position = 0 self.pressed = False self.shaded_duration = gst.CLOCK_TIME_NONE self.max_duration = gst.CLOCK_TIME_NONE self.seeker = Seeker(80) self.seeker.connect('seek', self._seekerSeekCb) self.min_frame_spacing = 5.0 self.frame_height = 5.0 self.frame_rate = gst.Fraction(1 / 1) ## Zoomable interface override def zoomChanged(self): self.queue_resize() self.doPixmap() self.queue_draw() ## timeline position changed method def timelinePositionChanged(self, value, unused_frame=None): self.debug("value : %r", value) ppos = max(self.nsToPixel(self.position) - 1, 0) self.position = value npos = max(self.nsToPixel(self.position) - 1, 0) height = self.get_allocation().height self.bin_window.invalidate_rect((ppos, 0, 2, height), True) self.bin_window.invalidate_rect((npos, 0, 2, height), True) ## gtk.Widget overrides def do_size_allocate(self, allocation): self.debug("ScaleRuler got %s", list(allocation)) gtk.Layout.do_size_allocate(self, allocation) width = max(self.getMaxDurationWidth(), allocation.width) self.debug("Setting layout size to %d x %d", width, allocation.height) self.set_size(width, allocation.height) # the size has changed, therefore we want to redo our pixmap self.doPixmap() def do_realize(self): gtk.Layout.do_realize(self) # we want to create our own pixmap here self.doPixmap() def do_expose_event(self, event): self.debug("exposing ScaleRuler %s", list(event.area)) x, y, width, height = event.area if (x < self.pixmap_offset) or ( x + width > self.pixmap_offset + self.pixmap_allocated_width): self.debug("exposing outside boundaries !") self.pixmap_offset = max( 0, x + (width / 2) - (self.pixmap_allocated_width / 2)) self.debug("offset is now %d", self.pixmap_offset) self.doPixmap() width = self.pixmap_allocated_width # double buffering power ! self.bin_window.draw_drawable(self.style.fg_gc[gtk.STATE_NORMAL], self.pixmap, x - self.pixmap_offset, y, x, y, width, height) # draw the position context = self.bin_window.cairo_create() self.drawPosition(context, self.get_allocation()) return False def do_button_press_event(self, event): self.debug("button pressed at x:%d", event.x) if self.getShadedDuration() <= 0: self.debug("no timeline to seek on, ignoring") self.pressed = True # seek at position cur = self.pixelToNs(event.x) self._doSeek(cur) return True def do_button_release_event(self, event): self.debug("button released at x:%d", event.x) self.pressed = False return False def do_motion_notify_event(self, event): self.debug("motion at event.x %d", event.x) if self.pressed: # seek at position cur = self.pixelToNs(event.x) self._doSeek(cur) return False def do_scroll_event(self, event): if event.direction == gtk.gdk.SCROLL_UP: Zoomable.zoomIn() elif event.direction == gtk.gdk.SCROLL_DOWN: Zoomable.zoomOut() # TODO: seek timeline back/forward elif event.direction == gtk.gdk.SCROLL_LEFT: pass elif event.direction == gtk.gdk.SCROLL_RIGHT: pass ## Seeking methods def _seekerSeekCb(self, seeker, position, format): # clamping values within acceptable range duration = self.getShadedDuration() if duration in (0, gst.CLOCK_TIME_NONE): return if position > duration: position = duration - (1 * gst.MSECOND) elif position < 0: position = 0 self.emit('seek', position) return False def _doSeek(self, value, format=gst.FORMAT_TIME, on_idle=False): self.seeker.seek(value, format, on_idle) ## Drawing methods def doPixmap(self): """ (re)create the buffered drawable for the Widget """ # we can't create the pixmap if we're not realized if not self.flags() & gtk.REALIZED: return # We want to benefit from double-buffering (so as not to recreate the # ruler graphics all the time) yet we don't want to allocate insanely # big pixmaps (which would result in big memory usage, or even not being # able to allocate such a big pixmap). # # We therefore create a pixmap with a width of 2 times the maximum viewable # width (allocation.width) allocation = self.get_allocation() lwidth, lheight = self.get_size() self.pixmap_visible_width = allocation.width self.pixmap_allocated_width = self.pixmap_visible_width * self.pixmap_multiples allocation.width = self.pixmap_allocated_width if (allocation.width != self.pixmap_old_allocated_width): if self.pixmap: del self.pixmap self.pixmap = gtk.gdk.Pixmap(self.bin_window, allocation.width, allocation.height) self.pixmap_old_allocated_width = allocation.width self.drawBackground(allocation) self.drawRuler(allocation) def setProjectFrameRate(self, rate): self.frame_rate = rate # set the lowest scale based on project framerate self.scale[0] = float(2 / rate) self.scale[1] = float(5 / rate) self.scale[2] = float(10 / rate) self.queue_resize() def setShadedDuration(self, duration): self.info("start/duration changed") self.queue_resize() self.shaded_duration = duration if duration < self.position: position = duration - gst.NSECOND else: position = self.position self._doSeek(position, gst.FORMAT_TIME, on_idle=True) def getShadedDuration(self): return self.shaded_duration def getShadedDurationWidth(self): return self.nsToPixel(self.getShadedDuration()) def setMaxDuration(self, duration): self.queue_resize() self.max_duration = duration def getMaxDuration(self): return self.max_duration def getMaxDurationWidth(self): return self.nsToPixel(self.getMaxDuration()) def getPixelPosition(self): return 0 def drawBackground(self, allocation): self.pixmap.draw_rectangle(self.style.bg_gc[gtk.STATE_NORMAL], True, 0, 0, allocation.width, allocation.height) offset = int(Zoomable.nsToPixel( self.getShadedDuration())) - self.pixmap_offset if offset > 0: self.pixmap.draw_rectangle(self.style.bg_gc[gtk.STATE_ACTIVE], True, 0, 0, offset, allocation.height) def drawRuler(self, allocation): layout = self.create_pango_layout(time_to_string(0)) textwidth, textheight = layout.get_pixel_size() for scale in self.scale: spacing = Zoomable.zoomratio * scale if spacing >= textwidth * 1.5: break offset = self.pixmap_offset % spacing zoomRatio = self.zoomratio self.drawFrameBoundaries(allocation) self.drawTicks(allocation, offset, spacing, scale) self.drawTimes(allocation, offset, spacing, scale, layout) def drawTick(self, allocation, paintpos, height): paintpos = int(paintpos) height = allocation.height - int(allocation.height * height) self.pixmap.draw_line(self.style.fg_gc[gtk.STATE_NORMAL], paintpos, height, paintpos, allocation.height) def drawTicks(self, allocation, offset, spacing, scale): for subdivide, height in self.subdivide: spc = spacing / float(subdivide) dur = scale / float(subdivide) if spc < self.min_tick_spacing: break paintpos = float(self.border) + 0.5 if offset > 0: paintpos += spacing - offset while paintpos < allocation.width: self.drawTick(allocation, paintpos, height) paintpos += spc def drawTimes(self, allocation, offset, spacing, scale, layout): # figure out what the optimal offset is interval = long(gst.SECOND * scale) seconds = self.pixelToNs(self.pixmap_offset) paintpos = float(self.border) + 2 if offset > 0: seconds = seconds - (seconds % interval) + interval paintpos += spacing - offset shaded = self.getShadedDurationWidth() while paintpos < allocation.width: timevalue = time_to_string(long(seconds)) layout.set_text(timevalue) if paintpos < shaded: state = gtk.STATE_ACTIVE else: state = gtk.STATE_NORMAL self.pixmap.draw_layout(self.style.fg_gc[state], int(paintpos), 0, layout) paintpos += spacing seconds += interval def drawFrameBoundaries(self, allocation): ns_per_frame = float(1 / self.frame_rate) * gst.SECOND frame_width = self.nsToPixel(ns_per_frame) if frame_width >= self.min_frame_spacing: offset = self.pixmap_offset % frame_width paintpos = float(self.border) + 0.5 height = allocation.height y = int(height - self.frame_height) states = [gtk.STATE_ACTIVE, gtk.STATE_PRELIGHT] paintpos += frame_width - offset frame_num = int(paintpos // frame_width) % 2 while paintpos < allocation.width: self.pixmap.draw_rectangle(self.style.bg_gc[states[frame_num]], True, int(paintpos), y, frame_width, height) frame_num = (frame_num + 1) % 2 paintpos += frame_width def drawPosition(self, context, allocation): if self.getShadedDuration() <= 0: return # a simple RED line will do for now xpos = self.nsToPixel(self.position) + self.border context.save() context.set_line_width(1.5) context.set_source_rgb(1.0, 0, 0) context.move_to(xpos, 0) context.line_to(xpos, allocation.height) context.stroke() context.restore()
class PitiviViewer(gtk.VBox, Loggable): __gtype_name__ = 'PitiviViewer' __gsignals__ = { "activate-playback-controls" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_BOOLEAN,)), } """ A Widget to control and visualize a Pipeline @cvar pipeline: The current pipeline @type pipeline: L{Pipeline} @cvar action: The action controlled by this Pipeline @type action: L{ViewAction} """ def __init__(self, action=None, pipeline=None): """ @param action: Specific action to use instead of auto-created one @type action: L{ViewAction} """ gtk.VBox.__init__(self) Loggable.__init__(self) self.log("New PitiviViewer") self.seeker = Seeker(80) self.seeker.connect('seek', self._seekerSeekCb) self.action = action self.pipeline = pipeline self.current_time = long(0) self._initial_seek = None self.current_frame = -1 self.currentState = gst.STATE_PAUSED self._haveUI = False self._createUi() self.setAction(action) self.setPipeline(pipeline) def setPipeline(self, pipeline): """ Set the Viewer to the given Pipeline. Properly switches the currently set action to that new Pipeline. @param pipeline: The Pipeline to switch to. @type pipeline: L{Pipeline}. """ self.debug("self.pipeline:%r, pipeline:%r", self.pipeline, pipeline) if pipeline is not None and pipeline == self.pipeline: return if self.pipeline != None: # remove previously set Pipeline self._disconnectFromPipeline() # make ui inactive self._setUiActive(False) # finally remove previous pipeline self.pipeline = None self.currentState = gst.STATE_PAUSED self.playpause_button.setPause() self._connectToPipeline(pipeline) self.pipeline = pipeline if self.pipeline is not None: self._setUiActive() def setAction(self, action): """ Set the controlled action. @param action: The Action to set. If C{None}, a default L{ViewAction} will be used. @type action: L{ViewAction} or C{None} """ self.debug("self.action:%r, action:%r", self.action, action) if action is not None and action == self.action: return if self.action != None: # if there was one previously, remove it self._disconnectFromAction() if action == None: # get the default action action = self._getDefaultAction() self._connectToAction(action) self.showControls() def _connectToPipeline(self, pipeline): self.debug("pipeline:%r", pipeline) if self.pipeline != None: raise ViewerError("previous pipeline wasn't disconnected") self.pipeline = pipeline if self.pipeline == None: return self.pipeline.connect('position', self._posCb) self.pipeline.activatePositionListener() self.pipeline.connect('state-changed', self._currentStateCb) self.pipeline.connect('element-message', self._elementMessageCb) self.pipeline.connect('duration-changed', self._durationChangedCb) self.pipeline.connect('eos', self._eosCb) # if we have an action set it to that new pipeline if self.action: self.pipeline.setAction(self.action) self.action.activate() def _disconnectFromPipeline(self): self.debug("pipeline:%r", self.pipeline) if self.pipeline == None: # silently return, there's nothing to disconnect from return if self.action and (self.action in self.pipeline.actions): # if we have an action, properly remove it from pipeline if self.action.isActive(): self.pipeline.stop() self.action.deactivate() self.pipeline.removeAction(self.action) self.deactivatePositionListener() self.pipeline.disconnect_by_function(self._posCb) self.pipeline.disconnect_by_function(self._currentStateCb) self.pipeline.disconnect_by_function(self._elementMessageCb) self.pipeline.disconnect_by_function(self._durationChangedCb) self.pipeline.disconnect_by_function(self._eosCb) self.pipeline.stop() self.pipeline = None def _connectToAction(self, action): self.debug("action: %r", action) # not sure what we need to do ... self.action = action # FIXME: fix this properly? self.drawingarea.action = action dar = float(4/3) try: producer = action.producers[0] self.debug("producer:%r", producer) for stream in producer.output_streams: self.warning("stream:%r", stream) for stream in producer.getOutputStreams(VideoStream): self.debug("stream:%r", stream) if stream.dar: dar = stream.dar continue except: dar = float(4/3) self.setDisplayAspectRatio(dar) self.showControls() def _disconnectFromAction(self): self.action = None def _setUiActive(self, active=True): self.debug("active %r", active) self.set_sensitive(active) if self._haveUI: for item in [self.slider, self.rewind_button, self.back_button, self.playpause_button, self.next_button, self.forward_button, self.timelabel]: item.set_sensitive(active) if active: self.emit("activate-playback-controls", True) def _getDefaultAction(self): return ViewAction() def _createUi(self): """ Creates the Viewer GUI """ # drawing area self.aframe = gtk.AspectFrame(xalign=0.5, yalign=0.5, ratio=4.0/3.0, obey_child=False) self.pack_start(self.aframe, expand=True) self.drawingarea = ViewerWidget(self.action) self.aframe.add(self.drawingarea) # Slider self.posadjust = gtk.Adjustment() self.slider = gtk.HScale(self.posadjust) self.slider.set_draw_value(False) self.slider.connect("button-press-event", self._sliderButtonPressCb) self.slider.connect("button-release-event", self._sliderButtonReleaseCb) self.slider.connect("scroll-event", self._sliderScrollCb) self.pack_start(self.slider, expand=False) self.moving_slider = False self.slider.set_sensitive(False) # Buttons/Controls bbox = gtk.HBox() boxalign = gtk.Alignment(xalign=0.5, yalign=0.5) boxalign.add(bbox) self.pack_start(boxalign, expand=False) self.rewind_button = gtk.ToolButton(gtk.STOCK_MEDIA_REWIND) self.rewind_button.connect("clicked", self._rewindCb) self.rewind_button.set_sensitive(False) bbox.pack_start(self.rewind_button, expand=False) self.back_button = gtk.ToolButton(gtk.STOCK_MEDIA_PREVIOUS) self.back_button.connect("clicked", self._backCb) self.back_button.set_sensitive(False) bbox.pack_start(self.back_button, expand=False) self.playpause_button = PlayPauseButton() self.playpause_button.connect("play", self._playButtonCb) bbox.pack_start(self.playpause_button, expand=False) self.playpause_button.set_sensitive(False) self.next_button = gtk.ToolButton(gtk.STOCK_MEDIA_NEXT) self.next_button.connect("clicked", self._nextCb) self.next_button.set_sensitive(False) bbox.pack_start(self.next_button, expand=False) self.forward_button = gtk.ToolButton(gtk.STOCK_MEDIA_FORWARD) self.forward_button.connect("clicked", self._forwardCb) self.forward_button.set_sensitive(False) bbox.pack_start(self.forward_button, expand=False) # current time self.timelabel = gtk.Label() self.timelabel.set_markup("<tt>00:00:00.000</tt>") self.timelabel.set_alignment(1.0, 0.5) bbox.pack_start(self.timelabel, expand=False, padding=10) self._haveUI = True screen = gdk.screen_get_default() height = screen.get_height() if height >= 800: # show the controls and force the aspect frame to have at least the same # width (+110, which is a magic number to minimize dead padding). bbox.show_all() width, height = bbox.size_request() width += 110 height = int(width / self.aframe.props.ratio) self.aframe.set_size_request(width , height) self.show_all() _showingSlider = True def showSlider(self): self._showingSlider = True self.slider.show() def hideSlider(self): self._showingSlider = False self.slider.hide() def showControls(self): if not self.action: return if True: self.rewind_button.show() self.back_button.show() self.playpause_button.show() self.next_button.show() self.forward_button.show() if self._showingSlider: self.slider.show() else: self.rewind_button.hide() self.back_button.hide() self.playpause_button.hide() self.next_button.hide() self.forward_button.hide() self.slider.hide() def setDisplayAspectRatio(self, ratio): """ Sets the DAR of the Viewer to the given ratio. @arg ratio: The aspect ratio to set on the viewer @type ratio: L{float} """ self.debug("Setting ratio of %f [%r]", float(ratio), ratio) try: self.aframe.set_property("ratio", float(ratio)) except: self.warning("could not set ratio !") ## gtk.HScale callbacks for self.slider def _sliderButtonPressCb(self, slider, event): # borrow totem hack for seek-on-click behavior event.button = 2 self.info("button pressed") self.moving_slider = True self.valuechangedid = slider.connect("value-changed", self._sliderValueChangedCb) self.pipeline.pause() return False def _sliderButtonReleaseCb(self, slider, event): # borrow totem hack for seek-on-click behavior event.button = 2 self.info("slider button release at %s", time_to_string(long(slider.get_value()))) self.moving_slider = False if self.valuechangedid: slider.disconnect(self.valuechangedid) self.valuechangedid = 0 # revert to previous state if self.currentState == gst.STATE_PAUSED: self.pipeline.pause() else: self.pipeline.play() return False def _sliderValueChangedCb(self, slider): """ seeks when the value of the slider has changed """ value = long(slider.get_value()) self.info(gst.TIME_ARGS(value)) if self.moving_slider: self.seek(value) def _sliderScrollCb(self, unused_slider, event): if event.direction == gtk.gdk.SCROLL_LEFT: amount = -gst.SECOND else: amount = gst.SECOND self.seekRelative(amount) def seek(self, position, format=gst.FORMAT_TIME): try: self.seeker.seek(position, format) except: self.warning("seek failed") def _seekerSeekCb(self, seeker, position, format): try: self.pipeline.seek(position, format) except PipelineError: self.error("seek failed %s %s", gst.TIME_ARGS(position), format) 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 ## active Timeline calllbacks 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) ## Control gtk.Button callbacks def _rewindCb(self, unused_button): self.seek(0) def _backCb(self, unused_button): self.seekRelative(-gst.SECOND) def _playButtonCb(self, unused_button, isplaying): self.togglePlayback() def _nextCb(self, unused_button): self.seekRelative(gst.SECOND) def _forwardCb(self, unused_button): try: dur = self.pipeline.getDuration() self.seek(dur - 1) except: self.warning("couldn't get duration") ## public methods for controlling playback def play(self): self.pipeline.play() def pause(self): self.pipeline.pause() def togglePlayback(self): if self.pipeline is None: return self.pipeline.togglePlayback() def seekRelative(self, time): try: self.pipeline.seekRelative(time) except: self.warning("seek failed") def _posCb(self, unused_pipeline, pos): self._newTime(pos) def _currentStateCb(self, unused_pipeline, state): self.info("current state changed : %s", state) if state == int(gst.STATE_PLAYING): self.playpause_button.setPause() elif state == int(gst.STATE_PAUSED): self.playpause_button.setPlay() self.currentState = state def _eosCb(self, unused_pipeline): self.playpause_button.setPlay() def _elementMessageCb(self, unused_pipeline, message): name = message.structure.get_name() self.log('message:%s / %s', message, name) if name == 'prepare-xwindow-id': sink = message.src sink.set_xwindow_id(self.drawingarea.window_xid)