Exemplo n.º 1
0
    def test_get_clip(self):
        """ Test the Clip.get method """

        clip = Clip.get(id=self.clip_ids[1])
        self.assertTrue(clip)

        # Do not find a clip
        clip = Clip.get(id="invalidID")
        self.assertEqual(clip, None)
    def getMenu(self):
        # Build menu for selection button
        menu = QMenu(self)

        # Get translation object
        _ = get_app()._tr

        # Look up item for more info
        if self.item_type == "clip":
            self.item_name = Clip.get(id=self.item_id).title()
        elif self.item_type == "transition":
            self.item_name = Transition.get(id=self.item_id).title()
        elif self.item_type == "effect":
            self.item_name = Effect.get(id=self.item_id).title()

        # Add selected clips
        for item_id in get_app().window.selected_clips:
            clip = Clip.get(id=item_id)
            item_name = clip.title()
            item_icon = QIcon(QPixmap(clip.data.get('image')))
            action = menu.addAction(item_name)
            action.setIcon(item_icon)
            action.setData({'item_id':item_id, 'item_type':'clip'})
            action.triggered.connect(self.Action_Triggered)

            # Add effects for these clips (if any)
            for effect in clip.data.get('effects'):
                item_name = Effect.get(id=effect.get('id')).title()
                item_icon = QIcon(QPixmap(os.path.join(info.PATH, "effects", "icons", "%s.png" % effect.get('class_name').lower())))
                action = menu.addAction('  >  %s' % _(item_name))
                action.setIcon(item_icon)
                action.setData({'item_id': effect.get('id'), 'item_type': 'effect'})
                action.triggered.connect(self.Action_Triggered)

        # Add selected transitions
        for item_id in get_app().window.selected_transitions:
            trans = Transition.get(id=item_id)
            item_name = _(trans.title())
            item_icon = QIcon(QPixmap(trans.data.get('reader',{}).get('path')))
            action = menu.addAction(_(item_name))
            action.setIcon(item_icon)
            action.setData({'item_id': item_id, 'item_type': 'transition'})
            action.triggered.connect(self.Action_Triggered)

        # Add selected effects
        for item_id in get_app().window.selected_effects:
            effect = Effect.get(id=item_id)
            item_name = _(effect.title())
            item_icon = QIcon(QPixmap(os.path.join(info.PATH, "effects", "icons", "%s.png" % effect.data.get('class_name').lower())))
            action = menu.addAction(_(item_name))
            action.setIcon(item_icon)
            action.setData({'item_id': item_id, 'item_type': 'effect'})
            action.triggered.connect(self.Action_Triggered)

        # Return the menu object
        return menu
Exemplo n.º 3
0
    def test_get_clip(self):
        """ Test the Clip.get method """

        # Import additional classes that need the app defined first
        from classes.query import Clip

        # Find a clip named file1
        clip = Clip.get(id=TestQueryClass.clip_ids[1])
        self.assertTrue(clip)

        # Do not find a clip
        clip = Clip.get(id="invalidID")
        self.assertEqual(clip, None)
Exemplo n.º 4
0
    def test_get_clip(self):
        """ Test the Clip.get method """

        # Import additional classes that need the app defined first
        from classes.query import Clip

        # Find a clip named file1
        clip = Clip.get(id=TestQueryClass.clip_ids[1])
        self.assertTrue(clip)

        # Do not find a clip
        clip = Clip.get(id="invalidID")
        self.assertEqual(clip, None)
Exemplo n.º 5
0
    def updateProperty(self, id, frame_number, property_key, new_value):
        """Update a keyframe property to a new value, adding or updating keyframes as needed"""
        found_point = False
        clip_updated = False

        c = Clip.get(id=id)
        if not c:
            # No clip found
            return

        for point in c.data[property_key]["Points"]:
            log.info("looping points: co.X = %s" % point["co"]["X"])

            if point["co"]["X"] == frame_number:
                found_point = True
                clip_updated = True
                point["interpolation"] = openshot.BEZIER
                point["co"]["Y"] = float(new_value)

        if not found_point and new_value != None:
            clip_updated = True
            log.info("Created new point at X=%s" % frame_number)
            c.data[property_key]["Points"].append({'co': {'X': frame_number, 'Y': new_value}, 'interpolation': openshot.BEZIER})

        # Reduce # of clip properties we are saving (performance boost)
        c.data = {property_key: c.data.get(property_key)}

        # Save changes
        if clip_updated:
            # Save
            c.save()

            # Update the preview
            get_app().window.refreshFrameSignal.emit()
Exemplo n.º 6
0
    def transformTriggered(self, clip_id):
        """Handle the transform signal when it's emitted"""
        need_refresh = False
        # Disable Transform UI
        if self and self.transforming_clip:
            # Is this the same clip_id already being transformed?
            if not clip_id:
                # Clear transform
                self.transforming_clip = None
                need_refresh = True

        # Get new clip for transform
        if clip_id:
            self.transforming_clip = Clip.get(id=clip_id)

            if self.transforming_clip:
                self.transforming_clip_object = None
                clips = get_app().window.timeline_sync.timeline.Clips()
                for clip in clips:
                    if clip.Id() == self.transforming_clip.id:
                        self.transforming_clip_object = clip
                        need_refresh = True
                        break

        # Update the preview and reselct current frame in properties
        if need_refresh:
            get_app().window.refreshFrameSignal.emit()
            get_app().window.propertyTableView.select_frame(get_app().window.preview_thread.player.Position())
Exemplo n.º 7
0
    def transformTriggered(self, clip_id):
        """Handle the transform signal when it's emitted"""
        need_refresh = False
        # Disable Transform UI
        if self and self.transforming_clip:
            # Is this the same clip_id already being transformed?
            if not clip_id:
                # Clear transform
                self.transforming_clip = None
                need_refresh = True

        # Get new clip for transform
        if clip_id:
            self.transforming_clip = Clip.get(id=clip_id)

            if self.transforming_clip:
                self.transforming_clip_object = None
                clips = get_app().window.timeline_sync.timeline.Clips()
                for clip in clips:
                    if clip.Id() == self.transforming_clip.id:
                        self.transforming_clip_object = clip
                        need_refresh = True
                        break

        # Update the preview and reselct current frame in properties
        if need_refresh:
            get_app().window.refreshFrameSignal.emit()
            get_app().window.propertyTableView.select_frame(
                get_app().window.preview_thread.player.Position())
Exemplo n.º 8
0
    def updateProperty(self, id, frame_number, property_key, new_value):
        """Update a keyframe property to a new value, adding or updating keyframes as needed"""
        found_point = False
        clip_updated = False

        c = Clip.get(id=id)

        for point in c.data[property_key]["Points"]:
            log.info("looping points: co.X = %s" % point["co"]["X"])

            if point["co"]["X"] == frame_number:
                found_point = True
                clip_updated = True
                point["interpolation"] = openshot.BEZIER
                point["co"]["Y"] = float(new_value)

        if not found_point and new_value != None:
            clip_updated = True
            log.info("Created new point at X=%s" % frame_number)
            c.data[property_key]["Points"].append({'co': {'X': frame_number, 'Y': new_value}, 'interpolation': openshot.BEZIER})

        # Reduce # of clip properties we are saving (performance boost)
        c.data = {property_key: c.data.get(property_key)}

        # Save changes
        if clip_updated:
            # Save
            c.save()

            # Update the preview
            get_app().window.refreshFrameSignal.emit()
Exemplo n.º 9
0
    def test_delete_clip(self):
        """ Test the Clip.delete method """

        delete_id = self.clip_ids[4]
        clip = Clip.get(id=delete_id)
        self.assertTrue(clip)

        clip.delete()

        # Verify deleted data
        deleted_clip = Clip.get(id=delete_id)
        self.assertFalse(deleted_clip)

        # Delete clip again (should do nothing)
        clip.delete()
        deleted_clip = Clip.get(id=delete_id)
        self.assertFalse(deleted_clip)
Exemplo n.º 10
0
    def rect_select_clicked(self, widget, param):
        """Rect select button clicked"""
        self.context[param["setting"]].update({"button-clicked": True})

        # show dialog
        from windows.region import SelectRegion
        from classes.query import File, Clip

        c = Clip.get(id=self.clip_id)
        reader_path = c.data.get('reader', {}).get('path','')
        f = File.get(path=reader_path)
        if f:
            win = SelectRegion(f, self.clip_instance)
            # Run the dialog event loop - blocking interaction on this window during that time
            result = win.exec_()
            if result == QDialog.Accepted:
                # self.first_frame = win.current_frame
                # Region selected (get coordinates if any)
                topLeft = win.videoPreview.regionTopLeftHandle
                bottomRight = win.videoPreview.regionBottomRightHandle
                viewPortSize = win.viewport_rect
                curr_frame_size = win.videoPreview.curr_frame_size

                x1 = topLeft.x() / curr_frame_size.width()
                y1 = topLeft.y() / curr_frame_size.height()
                x2 = bottomRight.x() / curr_frame_size.width()
                y2 = bottomRight.y() / curr_frame_size.height()

                # Get QImage of region
                if win.videoPreview.region_qimage:
                    region_qimage = win.videoPreview.region_qimage

                    # Resize QImage to match button size
                    resized_qimage = region_qimage.scaled(widget.size(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)

                    # Draw Qimage onto QPushButton (to display region selection to user)
                    palette = widget.palette()
                    palette.setBrush(widget.backgroundRole(), QBrush(resized_qimage))
                    widget.setFlat(True)
                    widget.setAutoFillBackground(True)
                    widget.setPalette(palette)

                    # Remove button text (so region QImage is more visible)
                    widget.setText("")

                # If data found, add to context
                if topLeft and bottomRight:
                    self.context[param["setting"]].update({"normalized_x": x1, "normalized_y": y1,
                                                           "normalized_width": x2-x1,
                                                           "normalized_height": y2-y1,
                                                           "first-frame": win.current_frame,
                                                           })
                    log.info(self.context)

        else:
            log.error('No file found with path: %s' % reader_path)
Exemplo n.º 11
0
    def test_update_clip(self):
        """ Test the Clip.save method """

        update_id = self.clip_ids[0]
        clip = Clip.get(id=update_id)
        self.assertTrue(clip)

        # Update clip
        clip.data["layer"] = 2
        clip.data["title"] = "My Title"
        clip.save()

        # Verify updated data
        clip = Clip.get(id=update_id)
        self.assertEqual(clip.data["layer"], 2)
        self.assertEqual(clip.data["title"], "My Title")

        clips = Clip.filter(layer=2)
        self.assertEqual(len(clips), 1)
Exemplo n.º 12
0
    def test_update_clip(self):
        """ Test the Clip.save method """

        # Import additional classes that need the app defined first
        from classes.query import Clip

        # Find a clip named file1
        update_id = TestQueryClass.clip_ids[0]
        clip = Clip.get(id=update_id)
        self.assertTrue(clip)

        # Update clip
        clip.data["layer"] = 2
        clip.data["title"] = "My Title"
        clip.save()

        # Verify updated data
        # Get clip again
        clip = Clip.get(id=update_id)
        self.assertEqual(clip.data["layer"], 2)
        self.assertEqual(clip.data["title"], "My Title")
Exemplo n.º 13
0
    def update_item_timeout(self):
        # Get the next item id, and type
        self.item_id = self.next_item_id
        self.item_type = self.next_item_type
        self.item_name = None
        self.item_icon = None

        # Stop timer
        self.update_timer.stop()

        # Get translation object
        _ = get_app()._tr

        # Look up item for more info
        if self.item_type == "clip":
            clip = Clip.get(id=self.item_id)
            if clip:
                self.item_name = clip.title()
                self.item_icon = QIcon(QPixmap(clip.data.get('image')))
        elif self.item_type == "transition":
            trans = Transition.get(id=self.item_id)
            if trans:
                self.item_name = _(trans.title())
                self.item_icon = QIcon(
                    QPixmap(trans.data.get('reader', {}).get('path')))
        elif self.item_type == "effect":
            effect = Effect.get(id=self.item_id)
            if effect:
                self.item_name = _(effect.title())
                self.item_icon = QIcon(
                    QPixmap(
                        os.path.join(
                            info.PATH, "effects", "icons",
                            "%s.png" % effect.data.get('class_name').lower())))

        # Truncate long text
        if self.item_name and len(self.item_name) > 25:
            self.item_name = "%s..." % self.item_name[:22]

        # Set label
        if self.item_id:
            self.lblSelection.setText("<strong>%s</strong>" % _("Selection:"))
            self.btnSelectionName.setText(self.item_name)
            self.btnSelectionName.setVisible(True)
            if self.item_icon:
                self.btnSelectionName.setIcon(self.item_icon)
        else:
            self.lblSelection.setText("<strong>%s</strong>" %
                                      _("No Selection"))
            self.btnSelectionName.setVisible(False)

        # Set the menu on the button
        self.btnSelectionName.setMenu(self.getMenu())
Exemplo n.º 14
0
    def test_update_clip(self):
        """ Test the Clip.save method """

        # Import additional classes that need the app defined first
        from classes.query import Clip

        # Find a clip named file1
        update_id = TestQueryClass.clip_ids[0]
        clip = Clip.get(id=update_id)
        self.assertTrue(clip)

        # Update clip
        clip.data["layer"] = 2
        clip.data["title"] = "My Title"
        clip.save()

        # Verify updated data
        # Get clip again
        clip = Clip.get(id=update_id)
        self.assertEqual(clip.data["layer"], 2)
        self.assertEqual(clip.data["title"], "My Title")
Exemplo n.º 15
0
    def test_delete_clip(self):
        """ Test the Clip.delete method """

        # Import additional classes that need the app defined first
        from classes.query import Clip

        # Find a clip named file1
        delete_id = TestQueryClass.clip_ids[4]
        clip = Clip.get(id=delete_id)
        self.assertTrue(clip)

        # Delete clip
        clip.delete()

        # Verify deleted data
        deleted_clip = Clip.get(id=delete_id)
        self.assertFalse(deleted_clip)

        # Delete clip again (should do nothing)
        clip.delete()

        # Verify deleted data
        deleted_clip = Clip.get(id=delete_id)
        self.assertFalse(deleted_clip)
Exemplo n.º 16
0
    def test_delete_clip(self):
        """ Test the Clip.delete method """

        # Import additional classes that need the app defined first
        from classes.query import Clip

        # Find a clip named file1
        delete_id = TestQueryClass.clip_ids[4]
        clip = Clip.get(id=delete_id)
        self.assertTrue(clip)

        # Delete clip
        clip.delete()

        # Verify deleted data
        deleted_clip = Clip.get(id=delete_id)
        self.assertFalse(deleted_clip)

        # Delete clip again (should do nothing)
        clip.delete()

        # Verify deleted data
        deleted_clip = Clip.get(id=delete_id)
        self.assertFalse(deleted_clip)
Exemplo n.º 17
0
    def update_item_timeout(self):
        # Get the next item id, and type
        self.item_id = self.next_item_id
        self.item_type = self.next_item_type
        self.item_name = None
        self.item_icon = None

        # Stop timer
        self.update_timer.stop()

        # Get translation object
        _ = get_app()._tr

        # Look up item for more info
        if self.item_type == "clip":
            clip = Clip.get(id=self.item_id)
            self.item_name = clip.title()
            self.item_icon = QIcon(QPixmap(clip.data.get('image')))
        elif self.item_type == "transition":
            trans = Transition.get(id=self.item_id)
            self.item_name = _(trans.title())
            self.item_icon = QIcon(QPixmap(trans.data.get('reader', {}).get('path')))
        elif self.item_type == "effect":
            effect = Effect.get(id=self.item_id)
            self.item_name = _(effect.title())
            self.item_icon = QIcon(QPixmap(os.path.join(info.PATH, "effects", "icons", "%s.png" % effect.data.get('class_name').lower())))

        # Truncate long text
        if self.item_name and len(self.item_name) > 25:
            self.item_name = "%s..." % self.item_name[:22]

        # Set label
        if self.item_id:
            self.lblSelection.setText("<strong>%s</strong>" % _("Selection:"))
            self.btnSelectionName.setText(self.item_name)
            self.btnSelectionName.setVisible(True)
            if self.item_icon:
                self.btnSelectionName.setIcon(self.item_icon)
        else:
            self.lblSelection.setText("<strong>%s</strong>" % _("No Selection"))
            self.btnSelectionName.setVisible(False)

        # Set the menu on the button
        self.btnSelectionName.setMenu(self.getMenu())
Exemplo n.º 18
0
    def remove_keyframe(self, item):
        """Remove an existing keyframe (if any)"""

        # Determine what was changed
        property = self.model.item(item.row(), 0).data()
        property_name = property[1]["name"]
        property_type = property[1]["type"]
        closest_point_x = property[1]["closest_point_x"]
        property_type = property[1]["type"]
        property_key = property[0]
        clip_id, item_type = item.data()

        # Find this clip
        c = None
        clip_updated = False

        if item_type == "clip":
            # Get clip object
            c = Clip.get(id=clip_id)
        elif item_type == "transition":
            # Get transition object
            c = Transition.get(id=clip_id)
        elif item_type == "effect":
            # Get effect object
            c = Effect.get(id=clip_id)

        if c:
            # Update clip attribute
            if property_key in c.data:
                log.info("remove keyframe: %s" % c.data)

                # Determine type of keyframe (normal or color)
                keyframe_list = []
                if property_type == "color":
                    keyframe_list = [
                        c.data[property_key]["red"],
                        c.data[property_key]["blue"],
                        c.data[property_key]["green"]
                    ]
                else:
                    keyframe_list = [c.data[property_key]]

                # Loop through each keyframe (red, blue, and green)
                for keyframe in keyframe_list:

                    # Keyframe
                    # Loop through points, find a matching points on this frame
                    closest_point = None
                    point_to_delete = None
                    for point in keyframe["Points"]:
                        if point["co"]["X"] == self.frame_number:
                            # Found point, Update value
                            clip_updated = True
                            point_to_delete = point
                            break
                        if point["co"]["X"] == closest_point_x:
                            closest_point = point

                    # If no point found, use closest point x
                    if not point_to_delete:
                        point_to_delete = closest_point

                    # Delete point (if needed)
                    if point_to_delete:
                        clip_updated = True
                        log.info("Found point to delete at X=%s" %
                                 point_to_delete["co"]["X"])
                        keyframe["Points"].remove(point_to_delete)

                # Reduce # of clip properties we are saving (performance boost)
                c.data = {property_key: c.data[property_key]}

                # Save changes
                if clip_updated:
                    # Save
                    c.save()

                    # Update the preview
                    get_app().window.refreshFrameSignal.emit()

                # Clear selection
                self.parent.clearSelection()
Exemplo n.º 19
0
    def value_updated(self, item, interpolation=-1, value=None, interpolation_details=[]):
        """ Table cell change event - also handles context menu to update interpolation value """

        if self.ignore_update_signal:
            return

        # Get translation method
        _ = get_app()._tr

        # Determine what was changed
        property = self.model.item(item.row(), 0).data()
        property_name = property[1]["name"]
        closest_point_x = property[1]["closest_point_x"]
        previous_point_x = property[1]["previous_point_x"]
        property_type = property[1]["type"]
        property_key = property[0]
        clip_id, item_type = item.data()

        # Get value (if any)
        if item.text():
            # Set and format value based on property type
            if value != None:
                # Override value
                new_value = value
            elif property_type == "string":
                # Use string value
                new_value = item.text()
            elif property_type == "bool":
                # Use boolean value
                if item.text() == _("False"):
                    new_value = False
                else:
                    new_value = True
            elif property_type == "int":
                # Use int value
                new_value = QLocale().system().toInt(item.text())[0]
            else:
                # Use decimal value
                new_value = QLocale().system().toFloat(item.text())[0]
        else:
            new_value = None

        log.info("%s for %s changed to %s at frame %s with interpolation: %s at closest x: %s" % (property_key, clip_id, new_value, self.frame_number, interpolation, closest_point_x))


        # Find this clip
        c = None
        clip_updated = False

        if item_type == "clip":
            # Get clip object
            c = Clip.get(id=clip_id)
        elif item_type == "transition":
            # Get transition object
            c = Transition.get(id=clip_id)
        elif item_type == "effect":
            # Get effect object
            c = Effect.get(id=clip_id)

        if c:
            # Update clip attribute
            if property_key in c.data:
                log.info("value updated: %s" % c.data)

                # Check the type of property (some are keyframe, and some are not)
                if property_type != "reader" and type(c.data[property_key]) == dict:
                    # Keyframe
                    # Loop through points, find a matching points on this frame
                    found_point = False
                    point_to_delete = None
                    for point in c.data[property_key]["Points"]:
                        log.info("looping points: co.X = %s" % point["co"]["X"])
                        if interpolation == -1 and point["co"]["X"] == self.frame_number:
                            # Found point, Update value
                            found_point = True
                            clip_updated = True
                            # Update or delete point
                            if new_value != None:
                                point["co"]["Y"] = float(new_value)
                                log.info("updating point: co.X = %s to value: %s" % (point["co"]["X"], float(new_value)))
                            else:
                                point_to_delete = point
                            break

                        elif interpolation > -1 and point["co"]["X"] == previous_point_x:
                            # Only update interpolation type (and the LEFT side of the curve)
                            found_point = True
                            clip_updated = True
                            point["interpolation"] = interpolation
                            if interpolation == 0:
                                point["handle_right"] = point.get("handle_right") or {"Y": 0.0, "X": 0.0}
                                point["handle_right"]["X"] = interpolation_details[0]
                                point["handle_right"]["Y"] = interpolation_details[1]

                            log.info("updating interpolation mode point: co.X = %s to %s" % (point["co"]["X"], interpolation))
                            log.info("use interpolation preset: %s" % str(interpolation_details))

                        elif interpolation > -1 and point["co"]["X"] == closest_point_x:
                            # Only update interpolation type (and the RIGHT side of the curve)
                            found_point = True
                            clip_updated = True
                            point["interpolation"] = interpolation
                            if interpolation == 0:
                                point["handle_left"] = point.get("handle_left") or {"Y": 0.0, "X": 0.0}
                                point["handle_left"]["X"] = interpolation_details[2]
                                point["handle_left"]["Y"] = interpolation_details[3]

                            log.info("updating interpolation mode point: co.X = %s to %s" % (point["co"]["X"], interpolation))
                            log.info("use interpolation preset: %s" % str(interpolation_details))

                    # Delete point (if needed)
                    if point_to_delete:
                        clip_updated = True
                        log.info("Found point to delete at X=%s" % point_to_delete["co"]["X"])
                        c.data[property_key]["Points"].remove(point_to_delete)

                    # Create new point (if needed)
                    elif not found_point and new_value != None:
                        clip_updated = True
                        log.info("Created new point at X=%s" % self.frame_number)
                        c.data[property_key]["Points"].append({'co': {'X': self.frame_number, 'Y': new_value}, 'interpolation': 1})

            if not clip_updated:
                # If no keyframe was found, set a basic property
                if property_type == "int":
                    # Integer
                    clip_updated = True
                    c.data[property_key] = int(new_value)

                elif property_type == "float":
                    # Float
                    clip_updated = True
                    c.data[property_key] = new_value

                elif property_type == "bool":
                    # Boolean
                    clip_updated = True
                    c.data[property_key] = bool(new_value)

                elif property_type == "string":
                    # String
                    clip_updated = True
                    c.data[property_key] = str(new_value)

                elif property_type == "reader":
                    # Reader
                    clip_updated = True

                    # Transition
                    try:
                        clip_object = openshot.Clip(value)
                        clip_object.Open()
                        c.data[property_key] = json.loads(clip_object.Reader().Json())
                        clip_object.Close()
                        clip_object = None
                    except:
                        log.info('Failed to load %s into Clip object for reader property' % value)

            # Reduce # of clip properties we are saving (performance boost)
            c.data = {property_key: c.data.get(property_key)}

            # Save changes
            if clip_updated:
                # Save
                c.save()

                # Update the preview
                get_app().window.refreshFrameSignal.emit()

            # Clear selection
            self.parent.clearSelection()
Exemplo n.º 20
0
    def refreshTriggered(self):
        """Signal to refresh viewport (i.e. a property might have changed that effects the preview)"""

        # Update reference to clip
        if self and self.transforming_clip:
            self.transforming_clip = Clip.get(id=self.transforming_clip.id)
Exemplo n.º 21
0
    def mouseMoveEvent(self, event):
        # Get data model and selection
        model = self.clip_properties_model.model

        # Do not change selected row during mouse move
        if self.lock_selection and self.prev_row:
            row = self.prev_row
        else:
            row = self.indexAt(event.pos()).row()
            self.prev_row = row
            self.lock_selection = True

        if row is None:
            return

        if model.item(row, 0):
            self.selected_label = model.item(row, 0)
            self.selected_item = model.item(row, 1)

        # Is the user dragging on the value column
        if self.selected_label and self.selected_item:
            # Get the position of the cursor and % value
            value_column_x = self.columnViewportPosition(1)
            cursor_value = event.x() - value_column_x
            cursor_value_percent = cursor_value / self.columnWidth(1)

            try:
                cur_property = self.selected_label.data()
            except Exception:
                # If item is deleted during this drag... an exception can occur
                # Just ignore, since this is harmless
                return

            property_key = cur_property[0]
            property_name = cur_property[1]["name"]
            property_type = cur_property[1]["type"]
            property_max = cur_property[1]["max"]
            property_min = cur_property[1]["min"]
            readonly = cur_property[1]["readonly"]
            item_id, item_type = self.selected_item.data()

            # Bail if readonly
            if readonly:
                return

            # Get the original data of this item (prior to any updates, for the undo/redo system)
            if not self.original_data:
                # Ignore undo/redo history temporarily (to avoid a huge pile of undo/redo history)
                get_app().updates.ignore_history = True

                # Find this clip
                c = None
                if item_type == "clip":
                    # Get clip object
                    c = Clip.get(id=item_id)
                elif item_type == "transition":
                    # Get transition object
                    c = Transition.get(id=item_id)
                elif item_type == "effect":
                    # Get effect object
                    c = Effect.get(id=item_id)

                if c:
                    if property_key in c.data:
                        # Grab the original data for this item/property
                        self.original_data = c.data

            # For numeric values, apply percentage within parameter's allowable range
            if property_type in ["float", "int"] and property_name != "Track":

                if self.previous_x == -1:
                    # Start tracking movement (init diff_length and previous_x)
                    self.diff_length = 10
                    self.previous_x = event.x()

                # Calculate # of pixels dragged
                drag_diff = self.previous_x - event.x()

                # update previous x
                self.previous_x = event.x()

                # Ignore small initial movements
                if abs(drag_diff) < self.diff_length:
                    # Lower threshold to 0 incrementally, to guarantee it'll eventually be exceeded
                    self.diff_length = max(0, self.diff_length - 1)
                    return

                # Compute size of property's possible values range
                min_max_range = float(property_max) - float(property_min)

                if min_max_range < 1000.0:
                    # Small range - use cursor to calculate new value as percentage of total range
                    self.new_value = property_min + (min_max_range *
                                                     cursor_value_percent)
                else:
                    # range is unreasonably long (such as position, start, end, etc.... which can be huge #'s)

                    # Get the current value and apply fixed adjustments in response to motion
                    self.new_value = QLocale().system().toDouble(
                        self.selected_item.text())[0]

                    if drag_diff > 0:
                        # Move to the left by a small amount
                        self.new_value -= 0.50
                    elif drag_diff < 0:
                        # Move to the right by a small amount
                        self.new_value += 0.50

                # Clamp value between min and max (just incase user drags too big)
                self.new_value = max(property_min, self.new_value)
                self.new_value = min(property_max, self.new_value)

                # Update value of this property
                self.clip_properties_model.value_updated(
                    self.selected_item, -1, self.new_value)

                # Repaint
                self.viewport().update()
Exemplo n.º 22
0
    def contextMenuEvent(self, event):
        """ Display context menu """
        # Get property being acted on
        index = self.indexAt(event.pos())
        if not index.isValid():
            event.ignore()
            return

        # Get data model and selection
        idx = self.indexAt(event.pos())
        row = idx.row()
        selected_label = idx.model().item(row, 0)
        selected_value = idx.model().item(row, 1)
        self.selected_item = selected_value
        frame_number = self.clip_properties_model.frame_number

        # Get translation object
        _ = get_app()._tr

        # If item selected
        if selected_label:
            # Get data from selected item
            cur_property = selected_label.data()
            property_name = cur_property[1]["name"]
            self.property_type = cur_property[1]["type"]
            points = cur_property[1]["points"]
            self.choices = cur_property[1]["choices"]
            property_key = cur_property[0]
            clip_id, item_type = selected_value.data()
            log.info("Context menu shown for %s (%s) for clip %s on frame %s" % (property_name, property_key, clip_id, frame_number))
            log.info("Points: %s" % points)

            # Clear menu if models updated
            if self.menu_reset:
                self.choices = []
                self.menu_reset = False

            # Handle parent effect options
            if property_key == "parent_effect_id" and not self.choices:
                clip_choices = [{
                    "name": "None",
                    "value": "None",
                    "selected": False,
                    "icon": QIcon()
                }]
                # Instantiate the timeline
                timeline_instance = get_app().window.timeline_sync.timeline
                # Instantiate this effect
                effect = timeline_instance.GetClipEffect(clip_id)
                effect_json = json.loads(effect.Json())

                # Loop through timeline's clips
                for clip_instance in timeline_instance.Clips():
                    clip_instance_id = clip_instance.Id()
                    # Avoid parent a clip effect to it's own effect
                    if (clip_instance_id != effect.ParentClipId()):
                        # Clip's propertyJSON data
                        clip_instance_data = Clip.get(id = clip_instance_id).data
                        # Path to the clip file
                        clip_instance_path = clip_instance_data["reader"]["path"]
                        # Iterate through all clip files on the timeline
                        for clip_number in range(self.files_model.rowCount()):
                            clip_index = self.files_model.index(clip_number, 0)
                            clip_name = clip_index.sibling(clip_number, 1).data()
                            clip_path = os.path.join(clip_index.sibling(clip_number, 4).data(), clip_name)
                            # Check if the timeline's clip file name matches the clip the user selected
                            if (clip_path == clip_instance_path):
                                # Generate the clip icon to show in the selection menu
                                clip_instance_icon = clip_index.data(Qt.DecorationRole)
                        effect_choices = [{"name": "None",
                                            "value": "None",
                                            "selected": False,
                                            "icon": QIcon()}]
                        # Iterate through clip's effects
                        for effect_data in clip_instance_data["effects"]:
                            # Make sure the user can only set a parent effect of the same type as this effect
                            if effect_data['class_name'] == effect_json['class_name']:
                                effect_id = effect_data["id"]
                                effect_name = effect_data['class_name']
                                effect_icon = QIcon(QPixmap(os.path.join(info.PATH, "effects", "icons", "%s.png" % effect_data['class_name'].lower())))
                                effect_choices.append({"name": effect_id,
                                                "value": effect_id,
                                                "selected": False,
                                                "icon": effect_icon})
                        self.choices.append({"name": _(clip_instance_data["title"]),
                                            "value": effect_choices,
                                            "selected": False,
                                            "icon": clip_instance_icon})


            # Handle selected object options (ObjectDetection effect)
            if property_key == "selected_object_index" and not self.choices:
                # Get all visible object's indexes
                timeline_instance = get_app().window.timeline_sync.timeline
                # Instantiate the effect
                effect = timeline_instance.GetClipEffect(clip_id)
                # Get the indexes and IDs of the visible objects
                visible_objects = json.loads(effect.GetVisibleObjects(frame_number))
                # Add visible objects as choices
                object_index_choices = []
                for object_index in visible_objects["visible_objects_index"]:
                    object_index_choices.append({
                                "name": str(object_index),
                                "value": str(object_index),
                                "selected": False,
                                "icon": QIcon()
                            })
                self.choices.append({"name": _("Detected Objects"), "value": object_index_choices, "selected": False, "icon": None})

            # Handle property to set the Tracked Object's child clip
            if property_key == "child_clip_id" and not self.choices:
                clip_choices = [{
                    "name": "None",
                    "value": "None",
                    "selected": False,
                    "icon": QIcon()
                }]
                # Instantiate the timeline
                timeline_instance = get_app().window.timeline_sync.timeline
                current_effect = timeline_instance.GetClipEffect(clip_id)

                # Loop through timeline's clips
                for clip_instance in timeline_instance.Clips():
                    clip_instance_id = clip_instance.Id()

                    # Avoid attach a clip to it's own object
                    if (clip_instance_id != clip_id and (current_effect and clip_instance_id != current_effect.ParentClip().Id())):
                        # Clip's propertyJSON data
                        clip_instance_data = Clip.get(id = clip_instance_id).data
                        # Path to the clip file
                        clip_instance_path = clip_instance_data["reader"]["path"]
                        # Iterate through all clip files on the timeline
                        for clip_number in range(self.files_model.rowCount()):
                            clip_index = self.files_model.index(clip_number, 0)
                            clip_name = clip_index.sibling(clip_number, 1).data()
                            clip_path = os.path.join(clip_index.sibling(clip_number, 4).data(), clip_name)
                            # Check if the timeline's clip file name matches the clip the user selected
                            if (clip_path == clip_instance_path):
                                # Generate the clip icon to show in the selection menu
                                clip_instance_icon = clip_index.data(Qt.DecorationRole)
                                self.choices.append({"name": clip_instance_data["title"],
                                              "value": clip_instance_id,
                                              "selected": False,
                                              "icon": clip_instance_icon})

            # Handle clip attach options
            if property_key == "parentObjectId" and not self.choices:
                # Add all Clips as choices - initialize with None
                tracked_choices = [{
                    "name": "None",
                    "value": "None",
                    "selected": False,
                    "icon": QIcon()
                }]
                clip_choices = [{
                    "name": "None",
                    "value": "None",
                    "selected": False,
                    "icon": QIcon()
                }]
                # Instantiate the timeline
                timeline_instance = get_app().window.timeline_sync.timeline
                # Loop through timeline's clips
                for clip_instance in timeline_instance.Clips():
                    clip_instance_id = clip_instance.Id()
                    # Avoid attach a clip to it's own object
                    if (clip_instance_id != clip_id):
                        # Clip's propertyJSON data
                        clip_instance_data = Clip.get(id = clip_instance_id).data
                        # Path to the clip file
                        clip_instance_path = clip_instance_data["reader"]["path"]
                        # Iterate through all clip files on the timeline
                        for clip_number in range(self.files_model.rowCount()):
                            clip_index = self.files_model.index(clip_number, 0)
                            clip_name = clip_index.sibling(clip_number, 1).data()
                            clip_path = os.path.join(clip_index.sibling(clip_number, 4).data(), clip_name)
                            # Check if the timeline's clip file name matches the clip the user selected
                            if (clip_path == clip_instance_path):
                                # Generate the clip icon to show in the selection menu
                                clip_instance_icon = clip_index.data(Qt.DecorationRole)
                                clip_choices.append({"name": clip_instance_data["title"],
                                              "value": clip_instance_id,
                                              "selected": False,
                                              "icon": clip_instance_icon})
                        # Get the pixmap of the clip icon
                        icon_size = 72
                        icon_pixmap = clip_instance_icon.pixmap(icon_size, icon_size)
                        # Add tracked objects to the selection menu
                        tracked_objects = []
                        for effect in clip_instance_data["effects"]:
                            # Check if effect has a tracked object
                            if effect.get("has_tracked_object"):
                                # Instantiate the effect
                                effect_instance = timeline_instance.GetClipEffect(effect["id"])
                                # Get the visible object's ids
                                visible_objects_id = json.loads(effect_instance.GetVisibleObjects(frame_number))["visible_objects_id"]
                                for object_id in visible_objects_id:
                                    # Get the Tracked Object properties
                                    object_properties = json.loads(timeline_instance.GetTrackedObjectValues(object_id, 0))
                                    x1 = object_properties['x1']
                                    y1 = object_properties['y1']
                                    x2 = object_properties['x2']
                                    y2 = object_properties['y2']
                                    # Get the tracked object's icon from the clip's icon
                                    tracked_object_icon = icon_pixmap.copy(QRect(x1*icon_size, y1*icon_size, (x2-x1)*icon_size, (y2-y1)*icon_size)).scaled(icon_size, icon_size)
                                    tracked_objects.append({"name": str(object_id),
                                                            "value": str(object_id),
                                                            "selected": False,
                                                            "icon": QIcon(tracked_object_icon)})
                        tracked_choices.append({"name": clip_instance_data["title"],
                                              "value": tracked_objects,
                                              "selected": False,
                                              "icon": clip_instance_icon})
                self.choices.append({"name": _("Tracked Objects"), "value": tracked_choices, "selected": False, "icon": None})
                self.choices.append({"name": _("Clips"), "value": clip_choices, "selected": False, "icon": None})

            # Handle reader type values
            if self.property_type == "reader" and not self.choices:
                # Add all files
                file_choices = []
                for i in range(self.files_model.rowCount()):
                    idx = self.files_model.index(i, 0)
                    if not idx.isValid():
                        continue
                    icon = idx.data(Qt.DecorationRole)
                    name = idx.sibling(i, 1).data()
                    path = os.path.join(idx.sibling(i, 4).data(), name)

                    # Append file choice
                    file_choices.append({"name": name,
                                         "value": path,
                                         "selected": False,
                                         "icon": icon
                                         })

                # Add root file choice
                self.choices.append({"name": _("Files"), "value": file_choices, "selected": False, icon: None})

                # Add all transitions
                trans_choices = []
                for i in range(self.transition_model.rowCount()):
                    idx = self.transition_model.index(i, 0)
                    if not idx.isValid():
                        continue
                    icon = idx.data(Qt.DecorationRole)
                    name = idx.sibling(i, 1).data()
                    path = idx.sibling(i, 3).data()

                    # Append transition choice
                    trans_choices.append({"name": name,
                                          "value": path,
                                          "selected": False,
                                          "icon": icon
                                          })

                # Add root transitions choice
                self.choices.append({"name": _("Transitions"), "value": trans_choices, "selected": False})

            # Handle reader type values
            if property_name == "Track" and self.property_type == "int" and not self.choices:
                # Populate all display track names
                all_tracks = get_app().project.get("layers")
                display_count = len(all_tracks)
                for track in reversed(sorted(all_tracks, key=itemgetter('number'))):
                    # Append track choice
                    track_name = track.get("label") or _("Track %s") % display_count
                    self.choices.append({"name": track_name, "value": track.get("number"), "selected": False, "icon": None})
                    display_count -= 1
                return

            elif self.property_type == "font":
                # Get font from user
                current_font_name = cur_property[1].get("memo", "sans")
                current_font = QFont(current_font_name)
                font, ok = QFontDialog.getFont(current_font, caption=("Change Font"))

                # Update font
                if ok and font:
                    fontinfo = QFontInfo(font)
                    self.clip_properties_model.value_updated(self.selected_item, value=fontinfo.family())

            # Define bezier presets
            bezier_presets = [
                (0.250, 0.100, 0.250, 1.000, _("Ease (Default)")),
                (0.420, 0.000, 1.000, 1.000, _("Ease In")),
                (0.000, 0.000, 0.580, 1.000, _("Ease Out")),
                (0.420, 0.000, 0.580, 1.000, _("Ease In/Out")),

                (0.550, 0.085, 0.680, 0.530, _("Ease In (Quad)")),
                (0.550, 0.055, 0.675, 0.190, _("Ease In (Cubic)")),
                (0.895, 0.030, 0.685, 0.220, _("Ease In (Quart)")),
                (0.755, 0.050, 0.855, 0.060, _("Ease In (Quint)")),
                (0.470, 0.000, 0.745, 0.715, _("Ease In (Sine)")),
                (0.950, 0.050, 0.795, 0.035, _("Ease In (Expo)")),
                (0.600, 0.040, 0.980, 0.335, _("Ease In (Circ)")),
                (0.600, -0.280, 0.735, 0.045, _("Ease In (Back)")),

                (0.250, 0.460, 0.450, 0.940, _("Ease Out (Quad)")),
                (0.215, 0.610, 0.355, 1.000, _("Ease Out (Cubic)")),
                (0.165, 0.840, 0.440, 1.000, _("Ease Out (Quart)")),
                (0.230, 1.000, 0.320, 1.000, _("Ease Out (Quint)")),
                (0.390, 0.575, 0.565, 1.000, _("Ease Out (Sine)")),
                (0.190, 1.000, 0.220, 1.000, _("Ease Out (Expo)")),
                (0.075, 0.820, 0.165, 1.000, _("Ease Out (Circ)")),
                (0.175, 0.885, 0.320, 1.275, _("Ease Out (Back)")),

                (0.455, 0.030, 0.515, 0.955, _("Ease In/Out (Quad)")),
                (0.645, 0.045, 0.355, 1.000, _("Ease In/Out (Cubic)")),
                (0.770, 0.000, 0.175, 1.000, _("Ease In/Out (Quart)")),
                (0.860, 0.000, 0.070, 1.000, _("Ease In/Out (Quint)")),
                (0.445, 0.050, 0.550, 0.950, _("Ease In/Out (Sine)")),
                (1.000, 0.000, 0.000, 1.000, _("Ease In/Out (Expo)")),
                (0.785, 0.135, 0.150, 0.860, _("Ease In/Out (Circ)")),
                (0.680, -0.550, 0.265, 1.550, _("Ease In/Out (Back)"))
            ]

            # Add menu options for keyframes
            menu = QMenu(self)
            if self.property_type == "color":
                Color_Action = menu.addAction(_("Select a Color"))
                Color_Action.triggered.connect(functools.partial(self.Color_Picker_Triggered, cur_property))
                menu.addSeparator()
            if points > 1:
                # Menu items only for multiple points
                Bezier_Menu = menu.addMenu(self.bezier_icon, _("Bezier"))
                for bezier_preset in bezier_presets:
                    preset_action = Bezier_Menu.addAction(bezier_preset[4])
                    preset_action.triggered.connect(functools.partial(
                        self.Bezier_Action_Triggered, bezier_preset))
                Linear_Action = menu.addAction(self.linear_icon, _("Linear"))
                Linear_Action.triggered.connect(self.Linear_Action_Triggered)
                Constant_Action = menu.addAction(self.constant_icon, _("Constant"))
                Constant_Action.triggered.connect(self.Constant_Action_Triggered)
                menu.addSeparator()
            if points >= 1:
                # Menu items for one or more points
                Insert_Action = menu.addAction(_("Insert Keyframe"))
                Insert_Action.triggered.connect(self.Insert_Action_Triggered)
                Remove_Action = menu.addAction(_("Remove Keyframe"))
                Remove_Action.triggered.connect(self.Remove_Action_Triggered)
                menu.popup(event.globalPos())

            # Menu for choices
            if not self.choices:
                return
            for choice in self.choices:
                if type(choice["value"]) != list:
                    # Just add root choice item
                    Choice_Action = menu.addAction(_(choice["name"]))
                    Choice_Action.setData(choice["value"])
                    Choice_Action.triggered.connect(self.Choice_Action_Triggered)
                    continue

                # Add sub-choice items (for nested choice lists)
                # Divide into smaller QMenus (since large lists cover the entire screen)
                # For example: Transitions -> 1 -> sub items
                SubMenu = None
                if choice["icon"] is not None:
                    SubMenuRoot = menu.addMenu(choice["icon"], choice["name"])
                else:
                    SubMenuRoot = menu.addMenu(choice["name"])

                SubMenuSize = 25
                SubMenuNumber = 0
                if len(choice["value"]) > SubMenuSize:
                    SubMenu = SubMenuRoot.addMenu(str(SubMenuNumber))
                else:
                    SubMenu = SubMenuRoot
                for i, sub_choice in enumerate(choice["value"], 1):
                    # Divide SubMenu if it's item is a list
                    if type(sub_choice["value"]) == list:
                        SubSubMenu = SubMenu.addMenu(sub_choice["icon"], sub_choice["name"])
                        for sub_sub_choice in sub_choice["value"]:
                            Choice_Action = SubSubMenu.addAction(
                                sub_sub_choice["icon"], sub_sub_choice["name"])
                            Choice_Action.setData(sub_sub_choice["value"])
                            Choice_Action.triggered.connect(self.Choice_Action_Triggered)
                    else:
                        if i % SubMenuSize == 0:
                            SubMenuNumber += 1
                            SubMenu = SubMenuRoot.addMenu(str(SubMenuNumber))
                        Choice_Action = SubMenu.addAction(
                            sub_choice["icon"], _(sub_choice["name"]))
                        Choice_Action.setData(sub_choice["value"])
                        Choice_Action.triggered.connect(self.Choice_Action_Triggered)

            # Show choice menuk
            menu.popup(event.globalPos())
Exemplo n.º 23
0
    def refreshTriggered(self):
        """Signal to refresh viewport (i.e. a property might have changed that effects the preview)"""

        # Update reference to clip
        if self and self.transforming_clip:
            self.transforming_clip = Clip.get(id=self.transforming_clip.id)
Exemplo n.º 24
0
    def value_updated(self,
                      item,
                      interpolation=-1,
                      value=None,
                      interpolation_details=[]):
        """ Table cell change event - also handles context menu to update interpolation value """

        if self.ignore_update_signal:
            return

        # Get translation method
        _ = get_app()._tr

        # Determine what was changed
        property = self.model.item(item.row(), 0).data()
        closest_point_x = property[1]["closest_point_x"]
        previous_point_x = property[1]["previous_point_x"]
        property_type = property[1]["type"]
        property_key = property[0]
        object_id = property[1]["object_id"]
        clip_id, item_type = item.data()

        # Get value (if any)
        if item.text():
            # Set and format value based on property type
            if value is not None:
                # Override value
                new_value = value
            elif property_type == "string":
                # Use string value
                new_value = item.text()
            elif property_type == "bool":
                # Use boolean value
                if item.text() == _("False"):
                    new_value = False
                else:
                    new_value = True
            elif property_type == "int":
                # Use int value
                new_value = QLocale().system().toInt(item.text())[0]
            else:
                # Use decimal value
                new_value = QLocale().system().toFloat(item.text())[0]
        else:
            new_value = None

        log.info(
            "%s for %s changed to %s at frame %s with interpolation: %s at closest x: %s"
            % (property_key, clip_id, new_value, self.frame_number,
               interpolation, closest_point_x))

        # Find this clip
        c = None
        clip_updated = False

        if item_type == "clip":
            # Get clip object
            c = Clip.get(id=clip_id)
        elif item_type == "transition":
            # Get transition object
            c = Transition.get(id=clip_id)
        elif item_type == "effect":
            # Get effect object
            c = Effect.get(id=clip_id)

        if c:

            # Create reference
            clip_data = c.data
            if object_id:
                clip_data = c.data.get('objects').get(object_id)

            # Update clip attribute
            if property_key in clip_data:
                log_id = "{}/{}".format(clip_id,
                                        object_id) if object_id else clip_id
                log.debug("%s: update property %s. %s", log_id, property_key,
                          clip_data.get(property_key))

                # Check the type of property (some are keyframe, and some are not)
                if property_type != "reader" and type(
                        clip_data[property_key]) == dict:
                    # Keyframe
                    # Loop through points, find a matching points on this frame
                    found_point = False
                    point_to_delete = None
                    for point in clip_data[property_key].get('Points', []):
                        log.debug("looping points: co.X = %s" %
                                  point["co"]["X"])
                        if interpolation == -1 and point["co"][
                                "X"] == self.frame_number:
                            # Found point, Update value
                            found_point = True
                            clip_updated = True
                            # Update or delete point
                            if new_value is not None:
                                point["co"]["Y"] = float(new_value)
                                log.debug(
                                    "updating point: co.X = %d to value: %.3f",
                                    point["co"]["X"], float(new_value))
                            else:
                                point_to_delete = point
                            break

                        elif interpolation > -1 and point["co"][
                                "X"] == previous_point_x:
                            # Only update interpolation type (and the LEFT side of the curve)
                            found_point = True
                            clip_updated = True
                            point["interpolation"] = interpolation
                            if interpolation == 0:
                                point["handle_right"] = point.get(
                                    "handle_right") or {
                                        "Y": 0.0,
                                        "X": 0.0
                                    }
                                point["handle_right"][
                                    "X"] = interpolation_details[0]
                                point["handle_right"][
                                    "Y"] = interpolation_details[1]

                            log.debug(
                                "updating interpolation mode point: co.X = %d to %d",
                                point["co"]["X"], interpolation)
                            log.debug("use interpolation preset: %s",
                                      str(interpolation_details))

                        elif interpolation > -1 and point["co"][
                                "X"] == closest_point_x:
                            # Only update interpolation type (and the RIGHT side of the curve)
                            found_point = True
                            clip_updated = True
                            point["interpolation"] = interpolation
                            if interpolation == 0:
                                point["handle_left"] = point.get(
                                    "handle_left") or {
                                        "Y": 0.0,
                                        "X": 0.0
                                    }
                                point["handle_left"][
                                    "X"] = interpolation_details[2]
                                point["handle_left"][
                                    "Y"] = interpolation_details[3]

                            log.debug(
                                "updating interpolation mode point: co.X = %d to %d",
                                point["co"]["X"], interpolation)
                            log.debug("use interpolation preset: %s",
                                      str(interpolation_details))

                    # Delete point (if needed)
                    if point_to_delete:
                        clip_updated = True
                        log.debug("Found point to delete at X=%s" %
                                  point_to_delete["co"]["X"])
                        clip_data[property_key]["Points"].remove(
                            point_to_delete)

                    # Create new point (if needed)
                    elif not found_point and new_value is not None:
                        clip_updated = True
                        log.debug("Created new point at X=%d",
                                  self.frame_number)
                        clip_data[property_key].setdefault(
                            'Points', []).append({
                                'co': {
                                    'X': self.frame_number,
                                    'Y': new_value
                                },
                                'interpolation': 1
                            })

            if not clip_updated:
                # If no keyframe was found, set a basic property
                if property_type == "int":
                    clip_updated = True
                    try:
                        clip_data[property_key] = int(new_value)
                    except Exception as ex:
                        log.warn(
                            'Invalid Integer value passed to property: %s' %
                            ex)

                elif property_type == "float":
                    clip_updated = True
                    try:
                        clip_data[property_key] = float(new_value)
                    except Exception as ex:
                        log.warn('Invalid Float value passed to property: %s' %
                                 ex)

                elif property_type == "bool":
                    clip_updated = True
                    try:
                        clip_data[property_key] = bool(new_value)
                    except Exception as ex:
                        log.warn(
                            'Invalid Boolean value passed to property: %s' %
                            ex)

                elif property_type == "string":
                    clip_updated = True
                    try:
                        clip_data[property_key] = str(new_value)
                    except Exception as ex:
                        log.warn(
                            'Invalid String value passed to property: %s' % ex)

                elif property_type in ["font", "caption"]:
                    clip_updated = True
                    try:
                        clip_data[property_key] = str(new_value)
                    except Exception as ex:
                        log.warn(
                            'Invalid Font/Caption value passed to property: %s'
                            % ex)

                elif property_type == "reader":
                    # Transition
                    clip_updated = True
                    try:
                        clip_object = openshot.Clip(value)
                        clip_object.Open()
                        clip_data[property_key] = json.loads(
                            clip_object.Reader().Json())
                        clip_object.Close()
                        clip_object = None
                    except Exception as ex:
                        log.warn(
                            'Invalid Reader value passed to property: %s (%s)'
                            % (value, ex))

            # Reduce # of clip properties we are saving (performance boost)
            clip_data = {property_key: clip_data.get(property_key)}
            if object_id:
                clip_data = {'objects': {object_id: clip_data}}

            # Save changes
            if clip_updated:
                # Save
                c.save()

                # Update the preview
                get_app().window.refreshFrameSignal.emit()

                log.info("Item %s: changed %s to %s at frame %s (x: %s)" %
                         (clip_id, property_key, new_value, self.frame_number,
                          closest_point_x))

            # Clear selection
            self.parent.clearSelection()
Exemplo n.º 25
0
    def mouseMoveEvent(self, event):
        # Get data model and selection
        model = self.clip_properties_model.model
        row = self.indexAt(event.pos()).row()
        column = self.indexAt(event.pos()).column()
        if model.item(row, 0):
            self.selected_label = model.item(row, 0)
            self.selected_item = model.item(row, 1)

        # Is the user dragging on the value column
        if self.selected_label and self.selected_item:
            frame_number = self.clip_properties_model.frame_number

            # Get the position of the cursor and % value
            value_column_x = self.columnViewportPosition(1)
            value_column_y = value_column_x + self.columnWidth(1)
            cursor_value = event.x() - value_column_x
            cursor_value_percent = cursor_value / self.columnWidth(1)

            try:
                property = self.selected_label.data()
            except Exception as ex:
                # If item is deleted during this drag... an exception can occur
                # Just ignore, since this is harmless
                return

            property_key = property[0]
            property_name = property[1]["name"]
            property_type = property[1]["type"]
            property_max = property[1]["max"]
            property_min = property[1]["min"]
            property_value = property[1]["value"]
            readonly = property[1]["readonly"]
            item_id, item_type = self.selected_item.data()

            # Bail if readonly
            if readonly:
                return

            # Get the original data of this item (prior to any updates, for the undo/redo system)
            if not self.original_data:
                # Ignore undo/redo history temporarily (to avoid a huge pile of undo/redo history)
                get_app().updates.ignore_history = True

                # Find this clip
                c = None
                if item_type == "clip":
                    # Get clip object
                    c = Clip.get(id=item_id)
                elif item_type == "transition":
                    # Get transition object
                    c = Transition.get(id=item_id)
                elif item_type == "effect":
                    # Get effect object
                    c = Effect.get(id=item_id)

                if c:
                    if property_key in c.data:
                        # Grab the original data for this item/property
                        self.original_data = c.data

            # Calculate percentage value
            if property_type in ["float", "int"]:
                min_max_range = float(property_max) - float(property_min)

                # Determine if range is unreasonably long (such as position, start, end, etc.... which can be huge #'s)
                if min_max_range > 1000.0:
                    # Get the current value
                    self.new_value = QLocale().system().toDouble(
                        self.selected_item.text())[0]

                    # Huge range - increment / decrement slowly
                    if self.previous_x == -1:
                        # init previous_x for the first time
                        self.previous_x = event.x()
                    # calculate # of pixels dragged
                    drag_diff = self.previous_x - event.x()
                    if drag_diff > 0:
                        # Move to the left by a small amount
                        self.new_value -= 0.50
                    elif drag_diff < 0:
                        # Move to the right by a small amount
                        self.new_value += 0.50
                    # update previous x
                    self.previous_x = event.x()
                else:
                    # Small range - use cursor % to calculate new value
                    self.new_value = property_min + (min_max_range *
                                                     cursor_value_percent)

                # Clamp value between min and max (just incase user drags too big)
                self.new_value = max(property_min, self.new_value)
                self.new_value = min(property_max, self.new_value)

                # Update value of this property
                self.clip_properties_model.value_updated(
                    self.selected_item, -1, self.new_value)

                # Repaint
                self.viewport().update()
Exemplo n.º 26
0
    def mouseMoveEvent(self, event):
        # Get data model and selection
        model = self.clip_properties_model.model
        row = self.indexAt(event.pos()).row()
        column = self.indexAt(event.pos()).column()
        if model.item(row, 0):
            self.selected_label = model.item(row, 0)
            self.selected_item = model.item(row, 1)

        # Is the user dragging on the value column
        if self.selected_label and self.selected_item:
            frame_number = self.clip_properties_model.frame_number

            # Get the position of the cursor and % value
            value_column_x = self.columnViewportPosition(1)
            value_column_y = value_column_x + self.columnWidth(1)
            cursor_value = event.x() - value_column_x
            cursor_value_percent = cursor_value / self.columnWidth(1)

            property = self.selected_label.data()
            property_key = property[0]
            property_name = property[1]["name"]
            property_type = property[1]["type"]
            property_max = property[1]["max"]
            property_min = property[1]["min"]
            property_value = property[1]["value"]
            readonly = property[1]["readonly"]
            item_id, item_type = self.selected_item.data()

            # Bail if readonly
            if readonly:
                return

            # Get the original data of this item (prior to any updates, for the undo/redo system)
            if not self.original_data:
                # Ignore undo/redo history temporarily (to avoid a huge pile of undo/redo history)
                get_app().updates.ignore_history = True

                # Find this clip
                c = None
                if item_type == "clip":
                    # Get clip object
                    c = Clip.get(id=item_id)
                elif item_type == "transition":
                    # Get transition object
                    c = Transition.get(id=item_id)
                elif item_type == "effect":
                    # Get effect object
                    c = Effect.get(id=item_id)

                if c:
                    if property_key in c.data:
                        # Grab the original data for this item/property
                        self.original_data = c.data

            # Calculate percentage value
            if property_type in ["float", "int"]:
                min_max_range = float(property_max) - float(property_min)

                # Determine if range is unreasonably long (such as position, start, end, etc.... which can be huge #'s)
                if min_max_range > 1000.0:
                    # Get the current value
                    self.new_value = QLocale().system().toDouble(self.selected_item.text())[0]

                    # Huge range - increment / decrement slowly
                    if self.previous_x == -1:
                        # init previous_x for the first time
                        self.previous_x = event.x()
                    # calculate # of pixels dragged
                    drag_diff = self.previous_x - event.x()
                    if drag_diff > 0:
                        # Move to the left by a small amount
                        self.new_value -= 0.50
                    elif drag_diff < 0:
                        # Move to the right by a small amount
                        self.new_value += 0.50
                    # update previous x
                    self.previous_x = event.x()
                else:
                    # Small range - use cursor % to calculate new value
                    self.new_value = property_min + (min_max_range * cursor_value_percent)

                # Clamp value between min and max (just incase user drags too big)
                self.new_value = max(property_min, self.new_value)
                self.new_value = min(property_max, self.new_value)

                # Update value of this property
                self.clip_properties_model.value_updated(self.selected_item, -1, self.new_value)

                # Repaint
                self.viewport().update()
Exemplo n.º 27
0
    def remove_keyframe(self, item):
        """Remove an existing keyframe (if any)"""

        # Determine what was changed
        property = self.model.item(item.row(), 0).data()
        property_name = property[1]["name"]
        property_type = property[1]["type"]
        closest_point_x = property[1]["closest_point_x"]
        property_type = property[1]["type"]
        property_key = property[0]
        clip_id, item_type = item.data()

        # Find this clip
        c = None
        clip_updated = False

        if item_type == "clip":
            # Get clip object
            c = Clip.get(id=clip_id)
        elif item_type == "transition":
            # Get transition object
            c = Transition.get(id=clip_id)
        elif item_type == "effect":
            # Get effect object
            c = Effect.get(id=clip_id)

        if c:
            # Update clip attribute
            if property_key in c.data:
                log.info(c.data)

                # Determine type of keyframe (normal or color)
                keyframe_list = []
                if property_type == "color":
                    keyframe_list = [c.data[property_key]["red"], c.data[property_key]["blue"], c.data[property_key]["green"]]
                else:
                    keyframe_list = [c.data[property_key]]

                # Loop through each keyframe (red, blue, and green)
                for keyframe in keyframe_list:

                    # Keyframe
                    # Loop through points, find a matching points on this frame
                    closest_point = None
                    point_to_delete = None
                    for point in keyframe["Points"]:
                        if point["co"]["X"] == self.frame_number:
                            # Found point, Update value
                            clip_updated = True
                            point_to_delete = point
                            break
                        if point["co"]["X"] == closest_point_x:
                            closest_point = point

                    # If no point found, use closest point x
                    if not point_to_delete:
                        point_to_delete = closest_point

                    # Delete point (if needed)
                    if point_to_delete:
                        clip_updated = True
                        log.info("Found point to delete at X=%s" % point_to_delete["co"]["X"])
                        keyframe["Points"].remove(point_to_delete)

                # Reduce # of clip properties we are saving (performance boost)
                c.data = {property_key: c.data[property_key]}

                # Save changes
                if clip_updated:
                    # Save
                    c.save()

                    # Update the preview
                    get_app().window.preview_thread.refreshFrame()

                # Clear selection
                self.parent.clearSelection()
Exemplo n.º 28
0
    def color_update(self, item, new_color, interpolation=-1):
        """Insert/Update a color keyframe for the selected row"""

        # Determine what was changed
        property = self.model.item(item.row(), 0).data()
        property_type = property[1]["type"]
        closest_point_x = property[1]["closest_point_x"]
        property_key = property[0]
        clip_id, item_type = item.data()

        if property_type == "color":
            # Find this clip
            c = None
            clip_updated = False

            if item_type == "clip":
                # Get clip object
                c = Clip.get(id=clip_id)
            elif item_type == "transition":
                # Get transition object
                c = Transition.get(id=clip_id)
            elif item_type == "effect":
                # Get effect object
                c = Effect.get(id=clip_id)

            if c:
                # Update clip attribute
                if property_key in c.data:
                    log.info(c.data)

                    # Loop through each keyframe (red, blue, and green)
                    for color, new_value in [("red", new_color.red()), ("blue", new_color.blue()),  ("green", new_color.green())]:

                        # Keyframe
                        # Loop through points, find a matching points on this frame
                        found_point = False
                        for point in c.data[property_key][color]["Points"]:
                            log.info("looping points: co.X = %s" % point["co"]["X"])
                            if interpolation == -1 and point["co"]["X"] == self.frame_number:
                                # Found point, Update value
                                found_point = True
                                clip_updated = True
                                # Update point
                                point["co"]["Y"] = new_value
                                log.info("updating point: co.X = %s to value: %s" % (point["co"]["X"], float(new_value)))
                                break

                            elif interpolation > -1 and point["co"]["X"] == closest_point_x:
                                # Only update interpolation type
                                found_point = True
                                clip_updated = True
                                point["interpolation"] = interpolation
                                log.info("updating interpolation mode point: co.X = %s to %s" % (point["co"]["X"], interpolation))
                                break

                        # Create new point (if needed)
                        if not found_point:
                            clip_updated = True
                            log.info("Created new point at X=%s" % self.frame_number)
                            c.data[property_key][color]["Points"].append({'co': {'X': self.frame_number, 'Y': new_value}, 'interpolation': 1})

                # Reduce # of clip properties we are saving (performance boost)
                c.data = {property_key: c.data[property_key]}

                # Save changes
                if clip_updated:
                    # Save
                    c.save()

                    # Update the preview
                    get_app().window.preview_thread.refreshFrame()

                # Clear selection
                self.parent.clearSelection()
Exemplo n.º 29
0
    def color_update(self,
                     item,
                     new_color,
                     interpolation=-1,
                     interpolation_details=[]):
        """Insert/Update a color keyframe for the selected row"""

        # Determine what was changed
        property = self.model.item(item.row(), 0).data()
        property_type = property[1]["type"]
        closest_point_x = property[1]["closest_point_x"]
        previous_point_x = property[1]["previous_point_x"]
        property_key = property[0]
        clip_id, item_type = item.data()

        if property_type == "color":
            # Find this clip
            c = None
            clip_updated = False

            if item_type == "clip":
                # Get clip object
                c = Clip.get(id=clip_id)
            elif item_type == "transition":
                # Get transition object
                c = Transition.get(id=clip_id)
            elif item_type == "effect":
                # Get effect object
                c = Effect.get(id=clip_id)

            if c:
                # Update clip attribute
                if property_key in c.data:
                    log.info("color update: %s" % c.data)

                    # Loop through each keyframe (red, blue, and green)
                    for color, new_value in [("red", new_color.red()),
                                             ("blue", new_color.blue()),
                                             ("green", new_color.green())]:

                        # Keyframe
                        # Loop through points, find a matching points on this frame
                        found_point = False
                        for point in c.data[property_key][color]["Points"]:
                            log.info("looping points: co.X = %s" %
                                     point["co"]["X"])
                            if interpolation == -1 and point["co"][
                                    "X"] == self.frame_number:
                                # Found point, Update value
                                found_point = True
                                clip_updated = True
                                # Update point
                                point["co"]["Y"] = new_value
                                log.info(
                                    "updating point: co.X = %s to value: %s" %
                                    (point["co"]["X"], float(new_value)))
                                break

                            elif interpolation > -1 and point["co"][
                                    "X"] == previous_point_x:
                                # Only update interpolation type (and the LEFT side of the curve)
                                found_point = True
                                clip_updated = True
                                point["interpolation"] = interpolation
                                if interpolation == 0:
                                    point["handle_right"] = point.get(
                                        "handle_right") or {
                                            "Y": 0.0,
                                            "X": 0.0
                                        }
                                    point["handle_right"][
                                        "X"] = interpolation_details[0]
                                    point["handle_right"][
                                        "Y"] = interpolation_details[1]

                                log.info(
                                    "updating interpolation mode point: co.X = %s to %s"
                                    % (point["co"]["X"], interpolation))
                                log.info("use interpolation preset: %s" %
                                         str(interpolation_details))

                            elif interpolation > -1 and point["co"][
                                    "X"] == closest_point_x:
                                # Only update interpolation type (and the RIGHT side of the curve)
                                found_point = True
                                clip_updated = True
                                point["interpolation"] = interpolation
                                if interpolation == 0:
                                    point["handle_left"] = point.get(
                                        "handle_left") or {
                                            "Y": 0.0,
                                            "X": 0.0
                                        }
                                    point["handle_left"][
                                        "X"] = interpolation_details[2]
                                    point["handle_left"][
                                        "Y"] = interpolation_details[3]

                                log.info(
                                    "updating interpolation mode point: co.X = %s to %s"
                                    % (point["co"]["X"], interpolation))
                                log.info("use interpolation preset: %s" %
                                         str(interpolation_details))

                        # Create new point (if needed)
                        if not found_point:
                            clip_updated = True
                            log.info("Created new point at X=%s" %
                                     self.frame_number)
                            c.data[property_key][color]["Points"].append({
                                'co': {
                                    'X': self.frame_number,
                                    'Y': new_value
                                },
                                'interpolation':
                                1
                            })

                # Reduce # of clip properties we are saving (performance boost)
                c.data = {property_key: c.data[property_key]}

                # Save changes
                if clip_updated:
                    # Save
                    c.save()

                    # Update the preview
                    get_app().window.refreshFrameSignal.emit()

                # Clear selection
                self.parent.clearSelection()
Exemplo n.º 30
0
    def value_updated(self, item, interpolation=-1, value=None):
        """ Table cell change event - also handles context menu to update interpolation value """

        if self.ignore_update_signal:
            return

        # Get translation method
        _ = get_app()._tr

        # Determine what was changed
        property = self.model.item(item.row(), 0).data()
        property_name = property[1]["name"]
        closest_point_x = property[1]["closest_point_x"]
        property_type = property[1]["type"]
        property_key = property[0]
        clip_id, item_type = item.data()

        # Get value (if any)
        if item.text():
            # Set and format value based on property type
            if value != None:
                # Override value
                new_value = value
            elif property_type == "string":
                # Use string value
                new_value = item.text()
            elif property_type == "bool":
                # Use boolean value
                if item.text() == _("False"):
                    new_value = False
                else:
                    new_value = True
            else:
                # Use numeric value
                new_value = QLocale().system().toFloat(item.text())[0]
        else:
            new_value = None

        log.info("%s for %s changed to %s at frame %s with interpolation: %s at closest x: %s" % (property_key, clip_id, new_value, self.frame_number, interpolation, closest_point_x))


        # Find this clip
        c = None
        clip_updated = False

        if item_type == "clip":
            # Get clip object
            c = Clip.get(id=clip_id)
        elif item_type == "transition":
            # Get transition object
            c = Transition.get(id=clip_id)
        elif item_type == "effect":
            # Get effect object
            c = Effect.get(id=clip_id)

        if c:
            # Update clip attribute
            if property_key in c.data:
                log.info(c.data)

                # Check the type of property (some are keyframe, and some are not)
                if type(c.data[property_key]) == dict:
                    # Keyframe
                    # Loop through points, find a matching points on this frame
                    found_point = False
                    point_to_delete = None
                    for point in c.data[property_key]["Points"]:
                        log.info("looping points: co.X = %s" % point["co"]["X"])
                        if interpolation == -1 and point["co"]["X"] == self.frame_number:
                            # Found point, Update value
                            found_point = True
                            clip_updated = True
                            # Update or delete point
                            if new_value != None:
                                point["co"]["Y"] = float(new_value)
                                log.info("updating point: co.X = %s to value: %s" % (point["co"]["X"], float(new_value)))
                            else:
                                point_to_delete = point
                            break

                        elif interpolation > -1 and point["co"]["X"] == closest_point_x:
                            # Only update interpolation type
                            found_point = True
                            clip_updated = True
                            point["interpolation"] = interpolation
                            log.info("updating interpolation mode point: co.X = %s to %s" % (point["co"]["X"], interpolation))
                            break

                    # Delete point (if needed)
                    if point_to_delete:
                        clip_updated = True
                        log.info("Found point to delete at X=%s" % point_to_delete["co"]["X"])
                        c.data[property_key]["Points"].remove(point_to_delete)

                    # Create new point (if needed)
                    elif not found_point and new_value != None:
                        clip_updated = True
                        log.info("Created new point at X=%s" % self.frame_number)
                        c.data[property_key]["Points"].append({'co': {'X': self.frame_number, 'Y': new_value}, 'interpolation': 1})

                elif property_type == "int":
                    # Integer
                    clip_updated = True
                    c.data[property_key] = int(new_value)

                elif property_type == "float":
                    # Float
                    clip_updated = True
                    c.data[property_key] = new_value

                elif property_type == "bool":
                    # Boolean
                    clip_updated = True
                    c.data[property_key] = bool(new_value)

                elif property_type == "string":
                    # String
                    clip_updated = True
                    c.data[property_key] = str(new_value)


            # Reduce # of clip properties we are saving (performance boost)
            c.data = {property_key: c.data[property_key]}

            # Save changes
            if clip_updated:
                # Save
                c.save()

                # Update the preview
                get_app().window.preview_thread.refreshFrame()

            # Clear selection
            self.parent.clearSelection()
Exemplo n.º 31
0
    def value_updated(self,
                      item,
                      interpolation=-1,
                      value=None,
                      interpolation_details=[]):
        """ Table cell change event - also handles context menu to update interpolation value """

        if self.ignore_update_signal:
            return

        # Get translation method
        _ = get_app()._tr

        # Determine what was changed
        property = self.model.item(item.row(), 0).data()
        property_name = property[1]["name"]
        closest_point_x = property[1]["closest_point_x"]
        previous_point_x = property[1]["previous_point_x"]
        property_type = property[1]["type"]
        property_key = property[0]
        clip_id, item_type = item.data()

        # Get value (if any)
        if item.text():
            # Set and format value based on property type
            if value != None:
                # Override value
                new_value = value
            elif property_type == "string":
                # Use string value
                new_value = item.text()
            elif property_type == "bool":
                # Use boolean value
                if item.text() == _("False"):
                    new_value = False
                else:
                    new_value = True
            elif property_type == "int":
                # Use int value
                new_value = QLocale().system().toInt(item.text())[0]
            else:
                # Use decimal value
                new_value = QLocale().system().toFloat(item.text())[0]
        else:
            new_value = None

        log.info(
            "%s for %s changed to %s at frame %s with interpolation: %s at closest x: %s"
            % (property_key, clip_id, new_value, self.frame_number,
               interpolation, closest_point_x))

        # Find this clip
        c = None
        clip_updated = False

        if item_type == "clip":
            # Get clip object
            c = Clip.get(id=clip_id)
        elif item_type == "transition":
            # Get transition object
            c = Transition.get(id=clip_id)
        elif item_type == "effect":
            # Get effect object
            c = Effect.get(id=clip_id)

        if c:
            # Update clip attribute
            if property_key in c.data:
                log.info("value updated: %s" % c.data)

                # Check the type of property (some are keyframe, and some are not)
                if type(c.data[property_key]) == dict:
                    # Keyframe
                    # Loop through points, find a matching points on this frame
                    found_point = False
                    point_to_delete = None
                    for point in c.data[property_key]["Points"]:
                        log.info("looping points: co.X = %s" %
                                 point["co"]["X"])
                        if interpolation == -1 and point["co"][
                                "X"] == self.frame_number:
                            # Found point, Update value
                            found_point = True
                            clip_updated = True
                            # Update or delete point
                            if new_value != None:
                                point["co"]["Y"] = float(new_value)
                                log.info(
                                    "updating point: co.X = %s to value: %s" %
                                    (point["co"]["X"], float(new_value)))
                            else:
                                point_to_delete = point
                            break

                        elif interpolation > -1 and point["co"][
                                "X"] == previous_point_x:
                            # Only update interpolation type (and the LEFT side of the curve)
                            found_point = True
                            clip_updated = True
                            point["interpolation"] = interpolation
                            if interpolation == 0:
                                point["handle_right"] = point.get(
                                    "handle_right") or {
                                        "Y": 0.0,
                                        "X": 0.0
                                    }
                                point["handle_right"][
                                    "X"] = interpolation_details[0]
                                point["handle_right"][
                                    "Y"] = interpolation_details[1]

                            log.info(
                                "updating interpolation mode point: co.X = %s to %s"
                                % (point["co"]["X"], interpolation))
                            log.info("use interpolation preset: %s" %
                                     str(interpolation_details))

                        elif interpolation > -1 and point["co"][
                                "X"] == closest_point_x:
                            # Only update interpolation type (and the RIGHT side of the curve)
                            found_point = True
                            clip_updated = True
                            point["interpolation"] = interpolation
                            if interpolation == 0:
                                point["handle_left"] = point.get(
                                    "handle_left") or {
                                        "Y": 0.0,
                                        "X": 0.0
                                    }
                                point["handle_left"][
                                    "X"] = interpolation_details[2]
                                point["handle_left"][
                                    "Y"] = interpolation_details[3]

                            log.info(
                                "updating interpolation mode point: co.X = %s to %s"
                                % (point["co"]["X"], interpolation))
                            log.info("use interpolation preset: %s" %
                                     str(interpolation_details))

                    # Delete point (if needed)
                    if point_to_delete:
                        clip_updated = True
                        log.info("Found point to delete at X=%s" %
                                 point_to_delete["co"]["X"])
                        c.data[property_key]["Points"].remove(point_to_delete)

                    # Create new point (if needed)
                    elif not found_point and new_value != None:
                        clip_updated = True
                        log.info("Created new point at X=%s" %
                                 self.frame_number)
                        c.data[property_key]["Points"].append({
                            'co': {
                                'X': self.frame_number,
                                'Y': new_value
                            },
                            'interpolation':
                            1
                        })

                elif property_type == "int":
                    # Integer
                    clip_updated = True
                    c.data[property_key] = int(new_value)

                elif property_type == "float":
                    # Float
                    clip_updated = True
                    c.data[property_key] = new_value

                elif property_type == "bool":
                    # Boolean
                    clip_updated = True
                    c.data[property_key] = bool(new_value)

                elif property_type == "string":
                    # String
                    clip_updated = True
                    c.data[property_key] = str(new_value)

            # Reduce # of clip properties we are saving (performance boost)
            c.data = {property_key: c.data.get(property_key)}

            # Save changes
            if clip_updated:
                # Save
                c.save()

                # Update the preview
                get_app().window.refreshFrameSignal.emit()

            # Clear selection
            self.parent.clearSelection()
    def Slice_Triggered(self, action, trans_ids, playhead_position=0):
        """Callback for slice context menus"""
        # Get FPS from project
        fps = get_app().project.get(["fps"])
        fps_num = float(fps["num"])
        fps_den = float(fps["den"])
        fps_float = fps_num / fps_den
        frame_duration = fps_den / fps_num

        # Get the nearest starting frame position to the playhead (this helps to prevent cutting
        # in-between frames, and thus less likely to repeat or skip a frame).
        playhead_position = float(
            round((playhead_position * fps_num) / fps_den) * fps_den) / fps_num

        clip_ids = self.timeline.Clips()
        # Loop through each clip (using the list of ids)
        for clip_id in clip_ids:

            # Get existing clip object
            clip = Clip.get(id=clip_id)
            if not clip:
                # Invalid clip, skip to next item
                continue

            # Determine if waveform needs to be redrawn
            has_audio_data = clip_id in self.waveform_cache

            if action == MENU_SLICE_KEEP_LEFT or action == MENU_SLICE_KEEP_BOTH:
                # Get details of original clip
                position_of_clip = float(clip.data["position"])
                start_of_clip = float(clip.data["start"])

                # Set new 'end' of clip
                clip.data["end"] = start_of_clip + (playhead_position -
                                                    position_of_clip)

            elif action == MENU_SLICE_KEEP_RIGHT:
                # Get details of original clip
                position_of_clip = float(clip.data["position"])
                start_of_clip = float(clip.data["start"])

                # Set new 'end' of clip
                clip.data["position"] = playhead_position
                clip.data["start"] = start_of_clip + (playhead_position -
                                                      position_of_clip)

                # Update thumbnail for right clip (after the clip has been created)
                self.UpdateClipThumbnail(clip.data)

            if action == MENU_SLICE_KEEP_BOTH:
                # Add the 2nd clip (the right side, since the left side has already been adjusted above)
                # Get right side clip object
                right_clip = Clip.get(id=clip_id)
                if not right_clip:
                    # Invalid clip, skip to next item
                    continue

                # Remove the ID property from the clip (so it becomes a new one)
                right_clip.id = None
                right_clip.type = 'insert'
                right_clip.data.pop('id')
                right_clip.key.pop(1)

                # Set new 'start' of right_clip (need to bump 1 frame duration more, so we don't repeat a frame)
                right_clip.data["position"] = (round(
                    float(playhead_position) * fps_float) + 1) / fps_float
                right_clip.data["start"] = (
                    round(float(clip.data["end"]) * fps_float) + 2) / fps_float

                # Save changes
                #right_clip.save()

                # Update thumbnail for right clip (after the clip has been created)
                #self.UpdateClipThumbnail(right_clip.data)

                # Save changes again (with new thumbnail)
                #self.update_clip_data(right_clip.data, only_basic_props=False, ignore_reader=True)
                '''