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
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)
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()
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())
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())
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()
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)
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)
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)
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")
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())
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)
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())
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()
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()
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)
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()
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())
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()
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()
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()
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()
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()
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()
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()
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) '''