Fix freeze keyframes for trimmed clips and rename "Speed->Reset" to "Speed->Reset Speed"

- Use absolute trim end when adding freeze endpoint keyframes
- Add regression coverage for freezing a right-side split clip
- Rename Speed > Reset to Speed > Reset Speed
- Update clip docs to match the new menu label
This commit is contained in:
Jonathan Thomas
2026-05-09 22:09:31 -05:00
parent e711d6c249
commit 1b966ea707
3 changed files with 43 additions and 5 deletions
+2 -2
View File
@@ -247,7 +247,7 @@ Speed
The :guilabel:`Speed` menu manipulates clip playback speed, allowing for reverse playback, time-lapse,
slow-motion, freezes, and looping effects. See :ref:`clip_time_ref` key-frame.
- :guilabel:`Reset` — restores the clip to normal 1× speed.
- :guilabel:`Reset Speed` — restores the clip to normal 1× speed.
- :guilabel:`Reverse` — plays the clip backwards at normal speed.
- :guilabel:`Speed Up` → Forward / Backward → 2×, 4×, 8×, 16× — speeds up playback in either direction.
- :guilabel:`Slow Down` → Forward / Backward → 1/2×, 1/4×, 1/8×, 1/16× — slows down playback in either direction.
@@ -302,7 +302,7 @@ Example: “Forward then Back and stop” = :guilabel:`Ping-Pong → Forward →
**Reset**
- :guilabel:`Speed → Reset` completely removes any Time curve (including Repeat) and restores the clip to its
- :guilabel:`Speed → Reset Speed` completely removes any Time curve (including Repeat) and restores the clip to its
original playback, **without deleting your original non-Time keyframes**.
Timing Tool
+37
View File
@@ -2104,6 +2104,43 @@ class TimelineHelperTests(unittest.TestCase):
self.assertEqual(middle_frame, 777)
self.assertEqual(end_frame, 64)
def test_freeze_uses_absolute_trim_end_for_right_side_split_clip(self):
helper = self.make_time_helper()
clip = types.SimpleNamespace(
id="C1",
data={
"id": "C1",
"position": 10.0,
"start": 10.0,
"end": 52.0,
"duration": 42.0,
"time": {"Points": [{"co": {"X": 1, "Y": 1}, "interpolation": openshot.LINEAR}]},
"volume": {"Points": [{"co": {"X": 1, "Y": 1.0}, "interpolation": openshot.LINEAR}]},
"ui": {},
},
)
app = types.SimpleNamespace(
project=types.SimpleNamespace(get=lambda key: {"num": 30, "den": 1} if key == "fps" else None),
updates=types.SimpleNamespace(apply_last_action_to_history=lambda _data: None),
)
with patch.object(self.timeline_module.Clip, "get", return_value=clip), \
patch.object(self.timeline_module, "get_app", return_value=app):
self.timeline_module.TimelineView.Time_Triggered(
helper,
self.timeline_module.MenuTime.FREEZE,
["C1"],
8,
10.0,
)
self.assertEqual(clip.data["end"], 60.0)
self.assertEqual(clip.data["duration"], 50.0)
self.assertIn(
{"co": {"X": 1801.0, "Y": 1561.0}, "interpolation": openshot.LINEAR},
clip.data["time"]["Points"],
)
def test_volume_triggered_refreshes_waveforms_for_visible_waveform_clips(self):
helper = self.make_time_helper()
clip = types.SimpleNamespace(
+4 -3
View File
@@ -1925,7 +1925,7 @@ class TimelineView(updates.UpdateInterface, ViewClass):
# Speed Menu
Time_Menu = StyledContextMenu(title=_("Speed"), parent=self)
Time_None = Time_Menu.addAction(_("Reset"))
Time_None = Time_Menu.addAction(_("Reset Speed"))
Time_None.triggered.connect(partial(self.Time_Triggered, MenuTime.NONE, clip_ids, '1X'))
Time_Menu.addSeparator()
@@ -4274,6 +4274,7 @@ class TimelineView(updates.UpdateInterface, ViewClass):
freeze_seconds = float(speed)
original_duration = clip.data["duration"]
original_end = float(clip.data["end"])
log.info('Updating timing for clip ID {}, original duration: {}'.format(clip.id, original_duration))
log.debug(clip.data)
@@ -4287,9 +4288,9 @@ class TimelineView(updates.UpdateInterface, ViewClass):
start_animation_frames_value = start_animation_frames
end_animation_seconds = start_animation_seconds + freeze_seconds
end_animation_frames = round(end_animation_seconds * fps_float) + 1
end_of_clip_seconds = float(clip.data["duration"])
end_of_clip_seconds = float(clip.data["end"])
end_of_clip_frames = round((end_of_clip_seconds) * fps_float) + 1
end_of_clip_frames_value = round((original_duration) * fps_float) + 1
end_of_clip_frames_value = round((original_end) * fps_float) + 1
# Determine volume start and end
start_volume_value = 1.0