From ada2ec8d1bb954037220b0783d17f2564d73ef67 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 11 Sep 2020 02:20:47 -0500 Subject: [PATCH 1/2] Always remove existing keyframe points for a colliding X coordinate. For example, if there is already a Point with coordinate X=1, remove that, and then add the new preset Point. Fixes bug where first preset keyframe was uneditable. --- src/windows/views/timeline_webview.py | 91 +++++++++++++++------------ 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/src/windows/views/timeline_webview.py b/src/windows/views/timeline_webview.py index 530850f614..3b5f17d0f7 100644 --- a/src/windows/views/timeline_webview.py +++ b/src/windows/views/timeline_webview.py @@ -1284,10 +1284,10 @@ def Animate_Triggered(self, action, clip_ids, position="Entire Clip"): end = openshot.Point(end_animation, end_scale, openshot.BEZIER) end_object = json.loads(end.Json()) clip.data["gravity"] = openshot.GRAVITY_CENTER - clip.data["scale_x"]["Points"].append(start_object) - clip.data["scale_x"]["Points"].append(end_object) - clip.data["scale_y"]["Points"].append(start_object) - clip.data["scale_y"]["Points"].append(end_object) + self.AddPoint(clip.data["scale_x"], start_object) + self.AddPoint(clip.data["scale_x"], end_object) + self.AddPoint(clip.data["scale_y"], start_object) + self.AddPoint(clip.data["scale_y"], end_object) if action in [ MENU_ANIMATE_CENTER_TOP, @@ -1352,10 +1352,10 @@ def Animate_Triggered(self, action, clip_ids, position="Entire Clip"): end_y = openshot.Point(end_animation, animate_end_y, openshot.BEZIER) end_y_object = json.loads(end_y.Json()) clip.data["gravity"] = openshot.GRAVITY_CENTER - clip.data["location_x"]["Points"].append(start_x_object) - clip.data["location_x"]["Points"].append(end_x_object) - clip.data["location_y"]["Points"].append(start_y_object) - clip.data["location_y"]["Points"].append(end_y_object) + self.AddPoint(clip.data["location_x"], start_x_object) + self.AddPoint(clip.data["location_x"], end_x_object) + self.AddPoint(clip.data["location_y"], start_y_object) + self.AddPoint(clip.data["location_y"], end_y_object) if action == MENU_ANIMATE_RANDOM: # Location animation @@ -1374,10 +1374,10 @@ def Animate_Triggered(self, action, clip_ids, position="Entire Clip"): end = openshot.Point(end_animation, end_scale, openshot.BEZIER) end_object = json.loads(end.Json()) clip.data["gravity"] = openshot.GRAVITY_CENTER - clip.data["scale_x"]["Points"].append(start_object) - clip.data["scale_x"]["Points"].append(end_object) - clip.data["scale_y"]["Points"].append(start_object) - clip.data["scale_y"]["Points"].append(end_object) + self.AddPoint(clip.data["scale_x"], start_object) + self.AddPoint(clip.data["scale_x"], end_object) + self.AddPoint(clip.data["scale_y"], start_object) + self.AddPoint(clip.data["scale_y"], end_object) # Add keyframes start_x = openshot.Point(start_animation, animate_start_x, openshot.BEZIER) @@ -1389,14 +1389,25 @@ def Animate_Triggered(self, action, clip_ids, position="Entire Clip"): end_y = openshot.Point(end_animation, animate_end_y, openshot.BEZIER) end_y_object = json.loads(end_y.Json()) clip.data["gravity"] = openshot.GRAVITY_CENTER - clip.data["location_x"]["Points"].append(start_x_object) - clip.data["location_x"]["Points"].append(end_x_object) - clip.data["location_y"]["Points"].append(start_y_object) - clip.data["location_y"]["Points"].append(end_y_object) + self.AddPoint(clip.data["location_x"], start_x_object) + self.AddPoint(clip.data["location_x"], end_x_object) + self.AddPoint(clip.data["location_y"], start_y_object) + self.AddPoint(clip.data["location_y"], end_y_object) # Save changes self.update_clip_data(clip.data, only_basic_props=False, ignore_reader=True) + def AddPoint(self, keyframe, new_point): + """Add a Point to a Keyframe dict. Always remove existing points, + if any collisions are found""" + # Get all points that don't match new point coordinate + cleaned_points = [point for point in keyframe["Points"] if not point.get("co", {}).get("X") == new_point.get("co", {}).get("X")] + cleaned_points.append(new_point) + + # Replace points with new list + keyframe["Points"] = cleaned_points + + def Copy_Triggered(self, action, clip_ids, tran_ids): """Callback for copy context menus""" log.debug(action) @@ -1812,8 +1823,8 @@ def Fade_Triggered(self, action, clip_ids, position="Entire Clip"): start_object = json.loads(start.Json()) end = openshot.Point(end_animation, 1.0, openshot.BEZIER) end_object = json.loads(end.Json()) - clip.data['alpha']["Points"].append(start_object) - clip.data['alpha']["Points"].append(end_object) + self.AddPoint(clip.data['alpha'], start_object) + self.AddPoint(clip.data['alpha'], end_object) if action in [MENU_FADE_OUT_FAST, MENU_FADE_OUT_SLOW]: # Add keyframes @@ -1821,8 +1832,8 @@ def Fade_Triggered(self, action, clip_ids, position="Entire Clip"): start_object = json.loads(start.Json()) end = openshot.Point(end_animation, 0.0, openshot.BEZIER) end_object = json.loads(end.Json()) - clip.data['alpha']["Points"].append(start_object) - clip.data['alpha']["Points"].append(end_object) + self.AddPoint(clip.data['alpha'], start_object) + self.AddPoint(clip.data['alpha'], end_object) # Save changes self.update_clip_data(clip.data, only_basic_props=False, ignore_reader=True) @@ -2078,8 +2089,8 @@ def callback(self, clip_id, callback_data): start_object = json.loads(start.Json()) end = openshot.Point(end_animation, 1.0, openshot.BEZIER) end_object = json.loads(end.Json()) - clip.data['volume']["Points"].append(start_object) - clip.data['volume']["Points"].append(end_object) + self.AddPoint(clip.data['volume'], start_object) + self.AddPoint(clip.data['volume'], end_object) if action in [ MENU_VOLUME_FADE_OUT_FAST, @@ -2090,8 +2101,8 @@ def callback(self, clip_id, callback_data): start_object = json.loads(start.Json()) end = openshot.Point(end_animation, 0.0, openshot.BEZIER) end_object = json.loads(end.Json()) - clip.data['volume']["Points"].append(start_object) - clip.data['volume']["Points"].append(end_object) + self.AddPoint(clip.data['volume'], start_object) + self.AddPoint(clip.data['volume'], end_object) if action in [ MENU_VOLUME_LEVEL_100, @@ -2109,7 +2120,7 @@ def callback(self, clip_id, callback_data): # Add keyframes p = openshot.Point(start_animation, float(action) / 100.0, openshot.BEZIER) p_object = json.loads(p.Json()) - clip.data['volume']["Points"].append(p_object) + self.AddPoint(clip.data['volume'], p_object) # Save changes self.update_clip_data(clip.data, only_basic_props=False, ignore_reader=True) @@ -2252,51 +2263,51 @@ def Time_Triggered(self, action, clip_ids, speed="1X", playhead_position=0.0): # Create Time Freeze keyframe points p = openshot.Point(start_animation_frames, start_animation_frames_value, openshot.LINEAR) p_object = json.loads(p.Json()) - clip.data['time']["Points"].append(p_object) + self.AddPoint(clip.data['time'], p_object) p1 = openshot.Point(end_animation_frames, start_animation_frames_value, openshot.LINEAR) p1_object = json.loads(p1.Json()) - clip.data['time']["Points"].append(p1_object) + self.AddPoint(clip.data['time'], p1_object) p2 = openshot.Point(end_of_clip_frames, end_of_clip_frames_value, openshot.LINEAR) p2_object = json.loads(p2.Json()) - clip.data['time']["Points"].append(p2_object) + self.AddPoint(clip.data['time'], p2_object) # Create Volume mute keyframe points (so the freeze is silent) p = openshot.Point(start_animation_frames - 1, start_volume_value, openshot.LINEAR) p_object = json.loads(p.Json()) - clip.data['volume']["Points"].append(p_object) + self.AddPoint(clip.data['volume'], p_object) p = openshot.Point(start_animation_frames, 0.0, openshot.LINEAR) p_object = json.loads(p.Json()) - clip.data['volume']["Points"].append(p_object) + self.AddPoint(clip.data['volume'], p_object) p2 = openshot.Point(end_animation_frames - 1, 0.0, openshot.LINEAR) p2_object = json.loads(p2.Json()) - clip.data['volume']["Points"].append(p2_object) + self.AddPoint(clip.data['volume'], p2_object) p3 = openshot.Point(end_animation_frames, start_volume_value, openshot.LINEAR) p3_object = json.loads(p3.Json()) - clip.data['volume']["Points"].append(p3_object) + self.AddPoint(clip.data['volume'], p3_object) # Create zoom keyframe points if action == MENU_TIME_FREEZE_ZOOM: p = openshot.Point(start_animation_frames, 1.0, openshot.BEZIER) p_object = json.loads(p.Json()) - clip.data['scale_x']["Points"].append(p_object) + self.AddPoint(clip.data['scale_x'], p_object) p = openshot.Point(start_animation_frames, 1.0, openshot.BEZIER) p_object = json.loads(p.Json()) - clip.data['scale_y']["Points"].append(p_object) + self.AddPoint(clip.data['scale_y'], p_object) diff_halfed = (end_animation_frames - start_animation_frames) / 2.0 p1 = openshot.Point(start_animation_frames + diff_halfed, 1.05, openshot.BEZIER) p1_object = json.loads(p1.Json()) - clip.data['scale_x']["Points"].append(p1_object) + self.AddPoint(clip.data['scale_x'], p1_object) p1 = openshot.Point(start_animation_frames + diff_halfed, 1.05, openshot.BEZIER) p1_object = json.loads(p1.Json()) - clip.data['scale_y']["Points"].append(p1_object) + self.AddPoint(clip.data['scale_y'], p1_object) p1 = openshot.Point(end_animation_frames, 1.0, openshot.BEZIER) p1_object = json.loads(p1.Json()) - clip.data['scale_x']["Points"].append(p1_object) + self.AddPoint(clip.data['scale_x'], p1_object) p1 = openshot.Point(end_animation_frames, 1.0, openshot.BEZIER) p1_object = json.loads(p1.Json()) - clip.data['scale_y']["Points"].append(p1_object) + self.AddPoint(clip.data['scale_y'], p1_object) else: @@ -2332,7 +2343,7 @@ def Time_Triggered(self, action, clip_ids, speed="1X", playhead_position=0.0): end = openshot.Point( start_animation + (duration_animation / speed_factor), end_animation, openshot.LINEAR) end_object = json.loads(end.Json()) - clip.data['time']["Points"].append(end_object) + self.AddPoint(clip.data['time'], end_object) # Adjust end & duration clip.data["end"] = (start_animation + (duration_animation / speed_factor)) / fps_float @@ -2348,7 +2359,7 @@ def Time_Triggered(self, action, clip_ids, speed="1X", playhead_position=0.0): end = openshot.Point( start_animation + (duration_animation / speed_factor), start_animation, openshot.LINEAR) end_object = json.loads(end.Json()) - clip.data['time']["Points"].append(end_object) + self.AddPoint(clip.data['time'], end_object) # Adjust end & duration clip.data["end"] = (start_animation + (duration_animation / speed_factor)) / fps_float From cd3246ef4af92a54c6273c1bdc8419c6ef657fff Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 11 Sep 2020 02:56:35 -0500 Subject: [PATCH 2/2] Adapting https://github.com/OpenShot/openshot-qt/pull/3317 PR to enable CTRL to allow for adding to the current selection (for clips and transitions) --- src/timeline/js/controllers.js | 75 ++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/src/timeline/js/controllers.js b/src/timeline/js/controllers.js index 4b7b1bad95..90477491b5 100644 --- a/src/timeline/js/controllers.js +++ b/src/timeline/js/controllers.js @@ -517,10 +517,12 @@ App.controller("TimelineCtrl", function ($scope) { // Trim clip_id var id = clip_id.replace("clip_", ""); - // Clear transitions also (if needed) - if (id !== "" && clear_selections) { - $scope.selectTransition("", clear_selections); - $scope.selectEffect("", clear_selections); + // Is CTRL pressed? + var is_ctrl = event && event.ctrlKey; + + // Clear transitions selection if needed + if (id !== "" && clear_selections && !is_ctrl) { + $scope.selectTransition("", true); } // Call slice method and exit (don't actually select the clip) if (id !== "" && $scope.enable_razor) { @@ -531,18 +533,28 @@ App.controller("TimelineCtrl", function ($scope) { // Don't actually select clip return; } - // Is CTRL pressed? - var is_ctrl = false; - if (event && event.ctrlKey) { - is_ctrl = true; - } - // Unselect all clips + // Update selection for clips for (var clip_index = 0; clip_index < $scope.project.clips.length; clip_index++) { if ($scope.project.clips[clip_index].id === id) { - $scope.project.clips[clip_index].selected = true; - if ($scope.Qt) { - timeline.addSelection(id, "clip", clear_selections); + // Invert selection if CTRL is pressed and not forced add and already selected + if (is_ctrl && clear_selections && ($scope.project.clips[clip_index].selected === true)) { + $scope.project.clips[clip_index].selected = false; + if ($scope.Qt) { + timeline.removeSelection($scope.project.clips[clip_index].id, "clip"); + } + } + else { + $scope.project.clips[clip_index].selected = true; + if ($scope.Qt) { + // Do not clear selection if CTRL is pressed + if (is_ctrl) { + timeline.addSelection(id, "clip", false); + } + else { + timeline.addSelection(id, "clip", clear_selections); + } + } } } else if (clear_selections && !is_ctrl) { @@ -559,10 +571,12 @@ App.controller("TimelineCtrl", function ($scope) { // Trim tran_id var id = tran_id.replace("transition_", ""); - // Clear clips also (if needed) - if (id !== "" && clear_selections) { + // Is CTRL pressed? + var is_ctrl = event && event.ctrlKey; + + // Clear clips selection if needed + if (id !== "" && clear_selections && !is_ctrl) { $scope.selectClip("", true); - $scope.selectEffect("", true); } // Call slice method and exit (don't actually select the transition) if (id !== "" && $scope.enable_razor) { @@ -574,18 +588,27 @@ App.controller("TimelineCtrl", function ($scope) { return; } - // Is CTRL pressed? - var is_ctrl = false; - if (event && event.ctrlKey) { - is_ctrl = true; - } - - // Unselect all transitions + // Update selection for transitions for (var tran_index = 0; tran_index < $scope.project.effects.length; tran_index++) { if ($scope.project.effects[tran_index].id === id) { - $scope.project.effects[tran_index].selected = true; - if ($scope.Qt) { - timeline.addSelection(id, "transition", clear_selections); + // Invert selection if CTRL is pressed and not forced add and already selected + if (is_ctrl && clear_selections && ($scope.project.effects[tran_index].selected === true)) { + $scope.project.effects[tran_index].selected = false; + if ($scope.Qt) { + timeline.removeSelection($scope.project.effects[tran_index].id, "transition"); + } + } + else { + $scope.project.effects[tran_index].selected = true; + if ($scope.Qt) { + // Do not clear selection if CTRL is pressed + if (is_ctrl) { + timeline.addSelection(id, "transition", false); + } + else { + timeline.addSelection(id, "transition", clear_selections); + } + } } } else if (clear_selections && !is_ctrl) {