1# -*- coding: utf-8 -*- 2# Pitivi video editor 3# Copyright (c) 2016, Jakub Brindza <jakub.brindza@gmail.com> 4# 5# This program is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2.1 of the License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General Public 16# License along with this program; if not, write to the 17# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, 18# Boston, MA 02110-1301, USA. 19"""Tests for the timeline.elements module.""" 20# pylint: disable=protected-access,no-self-use,too-many-locals 21from unittest import mock 22 23from gi.overrides import GObject 24from gi.repository import Gdk 25from gi.repository import GES 26from matplotlib.backend_bases import MouseEvent 27 28from pitivi.timeline.elements import GES_TYPE_UI_TYPE 29from pitivi.undo.undo import UndoableActionLog 30from tests.test_timeline_timeline import BaseTestTimeline 31from tests import common 32 33 34class TestKeyframeCurve(BaseTestTimeline): 35 """Tests for the KeyframeCurve class.""" 36 37 def test_keyframe_toggle(self): 38 """Checks keyframes toggling at the playhead position.""" 39 timeline_container = common.create_timeline_container() 40 timeline_container.app.action_log = UndoableActionLog() 41 timeline = timeline_container.timeline 42 ges_layer = timeline.ges_timeline.append_layer() 43 ges_clip1 = self.add_clip(ges_layer, 0) 44 ges_clip2 = self.add_clip(ges_layer, 10) 45 ges_clip3 = self.add_clip(ges_layer, 20, inpoint=100) 46 # For variety, add TitleClip to the list of clips. 47 ges_clip4 = common.create_test_clip(GES.TitleClip) 48 ges_clip4.props.start = 30 49 ges_clip4.props.duration = 4.5 50 ges_layer.add_clip(ges_clip4) 51 52 self.check_keyframe_toggle(ges_clip1, timeline_container) 53 self.check_keyframe_toggle(ges_clip2, timeline_container) 54 self.check_keyframe_toggle(ges_clip3, timeline_container) 55 self.check_keyframe_toggle(ges_clip4, timeline_container) 56 57 self.check_keyframe_ui_toggle(ges_clip1, timeline_container) 58 self.check_keyframe_ui_toggle(ges_clip2, timeline_container) 59 self.check_keyframe_ui_toggle(ges_clip3, timeline_container) 60 self.check_keyframe_ui_toggle(ges_clip4, timeline_container) 61 62 def check_keyframe_toggle(self, ges_clip, timeline_container): 63 """Checks keyframes toggling on the specified clip.""" 64 timeline = timeline_container.timeline 65 pipeline = timeline._project.pipeline 66 67 start = ges_clip.props.start 68 inpoint = ges_clip.props.in_point 69 duration = ges_clip.props.duration 70 offsets = (1, int(duration / 2), int(duration) - 1) 71 timeline.selection.select([ges_clip]) 72 73 ges_video_source = None 74 for child in ges_clip.get_children(recursive=False): 75 if isinstance(child, GES.VideoSource): 76 ges_video_source = child 77 binding = ges_video_source.get_control_binding("alpha") 78 control_source = binding.props.control_source 79 80 values = [item.timestamp for item in control_source.get_all()] 81 self.assertEqual(values, [inpoint, inpoint + duration]) 82 83 # Add keyframes. 84 for offset in offsets: 85 position = start + offset 86 pipeline.getPosition = mock.Mock(return_value=position) 87 timeline_container._keyframe_cb(None, None) 88 values = [item.timestamp for item in control_source.get_all()] 89 self.assertIn(inpoint + offset, values) 90 91 # Remove keyframes. 92 for offset in offsets: 93 position = start + offset 94 pipeline.getPosition = mock.Mock(return_value=position) 95 timeline_container._keyframe_cb(None, None) 96 values = [item.timestamp for item in control_source.get_all()] 97 self.assertNotIn(inpoint + offset, values, offset) 98 99 # Make sure the keyframes at the start and end of the clip 100 # cannot be toggled. 101 for offset in [0, duration]: 102 position = start + offset 103 pipeline.getPosition = mock.Mock(return_value=position) 104 values = [item.timestamp for item in control_source.get_all()] 105 self.assertIn(inpoint + offset, values) 106 timeline_container._keyframe_cb(None, None) 107 values = [item.timestamp for item in control_source.get_all()] 108 self.assertIn(inpoint + offset, values) 109 110 # Test out of clip range. 111 for offset in [-1, duration + 1]: 112 position = min(max(0, start + offset), 113 timeline.ges_timeline.props.duration) 114 pipeline.getPosition = mock.Mock(return_value=position) 115 timeline_container._keyframe_cb(None, None) 116 values = [item.timestamp for item in control_source.get_all()] 117 self.assertEqual(values, [inpoint, inpoint + duration]) 118 119 # pylint: disable=too-many-statements 120 def check_keyframe_ui_toggle(self, ges_clip, timeline_container): 121 """Checks keyframes toggling by click events.""" 122 timeline = timeline_container.timeline 123 124 inpoint = ges_clip.props.in_point 125 duration = ges_clip.props.duration 126 offsets = (1, int(duration / 2), int(duration) - 1) 127 timeline.selection.select([ges_clip]) 128 129 ges_video_source = ges_clip.find_track_element(None, GES.VideoSource) 130 binding = ges_video_source.get_control_binding("alpha") 131 control_source = binding.props.control_source 132 keyframe_curve = ges_video_source.ui.keyframe_curve 133 134 values = [item.timestamp for item in control_source.get_all()] 135 self.assertEqual(values, [inpoint, inpoint + duration]) 136 137 # Add keyframes. 138 for offset in offsets: 139 xdata, ydata = inpoint + offset, 1 140 x, y = keyframe_curve._ax.transData.transform((xdata, ydata)) 141 142 event = MouseEvent( 143 name="button_press_event", 144 canvas=keyframe_curve, 145 x=x, 146 y=y, 147 button=1 148 ) 149 event.guiEvent = Gdk.Event.new(Gdk.EventType.BUTTON_PRESS) 150 keyframe_curve._mpl_button_press_event_cb(event) 151 event.name = "button_release_event" 152 event.guiEvent = Gdk.Event.new(Gdk.EventType.BUTTON_RELEASE) 153 keyframe_curve._mpl_button_release_event_cb(event) 154 155 values = [item.timestamp for item in control_source.get_all()] 156 self.assertIn(inpoint + offset, values) 157 158 # Remove keyframes. 159 for offset in offsets: 160 xdata, ydata = inpoint + offset, 1 161 x, y = keyframe_curve._ax.transData.transform((xdata, ydata)) 162 163 event = MouseEvent( 164 name="button_press_event", 165 canvas=keyframe_curve, 166 x=x, 167 y=y, 168 button=1 169 ) 170 event.guiEvent = Gdk.Event.new(Gdk.EventType.BUTTON_PRESS) 171 keyframe_curve._mpl_button_press_event_cb(event) 172 event.name = "button_release_event" 173 event.guiEvent = Gdk.Event.new(Gdk.EventType.BUTTON_RELEASE) 174 keyframe_curve._mpl_button_release_event_cb(event) 175 event.name = "button_press_event" 176 event.guiEvent = Gdk.Event.new(Gdk.EventType.BUTTON_PRESS) 177 keyframe_curve._mpl_button_press_event_cb(event) 178 event.guiEvent = Gdk.Event.new(Gdk.EventType._2BUTTON_PRESS) 179 keyframe_curve._mpl_button_press_event_cb(event) 180 event.name = "button_release_event" 181 event.guiEvent = Gdk.Event.new(Gdk.EventType.BUTTON_RELEASE) 182 keyframe_curve._mpl_button_release_event_cb(event) 183 184 values = [item.timestamp for item in control_source.get_all()] 185 self.assertNotIn(inpoint + offset, values) 186 187 def test_no_clip_selected(self): 188 """Checks nothing happens when no clip is selected.""" 189 timeline_container = common.create_timeline_container() 190 # Make sure this does not raise any exception 191 timeline_container._keyframe_cb(None, None) 192 193 194class TestVideoSource(BaseTestTimeline): 195 """Tests for the VideoSource class.""" 196 197 def test_video_source_scaling(self): 198 """Checks the size of the scaled clips.""" 199 timeline_container = common.create_timeline_container() 200 timeline = timeline_container.timeline 201 project = timeline.app.project_manager.current_project 202 203 clip = self.addClipsSimple(timeline, 1)[0] 204 205 video_source = clip.find_track_element(None, GES.VideoUriSource) 206 sinfo = video_source.get_asset().get_stream_info() 207 208 width = video_source.get_child_property("width")[1] 209 height = video_source.get_child_property("height")[1] 210 self.assertEqual(sinfo.get_width(), 960) 211 self.assertEqual(sinfo.get_height(), 400) 212 self.assertEqual(project.videowidth, sinfo.get_width()) 213 self.assertEqual(project.videoheight, sinfo.get_height()) 214 self.assertEqual(project.videowidth, width) 215 self.assertEqual(project.videoheight, height) 216 217 project.videowidth = sinfo.get_width() * 2 218 project.videoheight = sinfo.get_height() * 2 219 width = video_source.get_child_property("width")[1] 220 height = video_source.get_child_property("height")[1] 221 self.assertEqual(project.videowidth, width) 222 self.assertEqual(project.videoheight, height) 223 224 project.videowidth = 150 225 project.videoheight = 200 226 width = video_source.get_child_property("width")[1] 227 height = video_source.get_child_property("height")[1] 228 229 expected_width = project.videowidth 230 expected_height = int(sinfo.get_height() * (project.videowidth / sinfo.get_width())) 231 self.assertEqual(width, expected_width) 232 self.assertEqual(height, expected_height) 233 234 video_source.set_child_property("posx", 50) 235 width = video_source.get_child_property("width")[1] 236 height = video_source.get_child_property("height")[1] 237 self.assertEqual(width, expected_width) 238 self.assertEqual(height, expected_height) 239 240 project.videowidth = 1920 241 project.videoheight = 1080 242 self.assertEqual(width, expected_width) 243 self.assertEqual(height, expected_height) 244 245 expected_default_position = { 246 "width": 1920, 247 "height": 800, 248 "posx": 0, 249 "posy": 140} 250 self.assertEqual(video_source.ui.default_position, 251 expected_default_position) 252 253 def test_rotation(self): 254 """Checks the size of the clips flipped 90 degrees.""" 255 timeline_container = common.create_timeline_container() 256 timeline = timeline_container.timeline 257 258 clip = self.addClipsSimple(timeline, 1)[0] 259 260 video_source = clip.find_track_element(None, GES.VideoUriSource) 261 sinfo = video_source.get_asset().get_stream_info() 262 263 width = video_source.get_child_property("width")[1] 264 height = video_source.get_child_property("height")[1] 265 self.assertEqual(sinfo.get_width(), 960) 266 self.assertEqual(sinfo.get_height(), 400) 267 self.assertEqual(width, 960) 268 self.assertEqual(height, 400) 269 270 videoflip = GES.Effect.new("videoflip") 271 videoflip.set_child_property("method", 1) # clockwise 272 273 clip.add(videoflip) 274 # The video is flipped 90 degrees 275 width = video_source.get_child_property("width")[1] 276 height = video_source.get_child_property("height")[1] 277 self.assertEqual(width, 167) 278 self.assertEqual(height, 400) 279 280 videoflip.props.active = False 281 width = video_source.get_child_property("width")[1] 282 height = video_source.get_child_property("height")[1] 283 self.assertEqual(width, 960) 284 self.assertEqual(height, 400) 285 286 videoflip.props.active = True 287 width = video_source.get_child_property("width")[1] 288 height = video_source.get_child_property("height")[1] 289 self.assertEqual(width, 167) 290 self.assertEqual(height, 400) 291 292 clip.remove(videoflip) 293 width = video_source.get_child_property("width")[1] 294 height = video_source.get_child_property("height")[1] 295 self.assertEqual(width, 960) 296 self.assertEqual(height, 400) 297 298 299class TestClip(common.TestCase): 300 """Tests for the Clip class.""" 301 302 def test_clip_subclasses(self): 303 """Checks the constructors of the Clip class.""" 304 for gtype, widget_class in GES_TYPE_UI_TYPE.items(): 305 ges_object = GObject.new(gtype) 306 widget = widget_class(mock.Mock(), ges_object) 307 self.assertEqual(ges_object.ui, widget, widget_class) 308