Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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)
Exemplo n.º 3
0
    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()
Exemplo n.º 4
0
    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()
Exemplo n.º 5
0
    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)
Exemplo n.º 6
0
    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()
Exemplo n.º 7
0
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)
Exemplo n.º 8
0
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()
Exemplo n.º 9
0
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()
Exemplo n.º 10
0
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()
Exemplo n.º 11
0
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)