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