1# -*- coding: utf-8 -*- 2# Pitivi video editor 3# Copyright (c) 2009, Alessandro Decina <alessandro.d@gmail.com> 4# Copyright (c) 2013, Alex Băluț <alexandru.balut@gmail.com> 5# 6# This program is free software; you can redistribute it and/or 7# modify it under the terms of the GNU Lesser General Public 8# License as published by the Free Software Foundation; either 9# version 2.1 of the License, or (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14# Lesser General Public License for more details. 15# 16# You should have received a copy of the GNU Lesser General Public 17# License along with this program; if not, write to the 18# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, 19# Boston, MA 02110-1301, USA. 20import collections 21import os 22import tempfile 23import time 24from unittest import mock 25 26from gi.repository import GES 27from gi.repository import Gst 28 29from pitivi import medialibrary 30from pitivi.project import Project 31from pitivi.project import ProjectManager 32from pitivi.utils.misc import path_from_uri 33from pitivi.utils.proxy import ProxyingStrategy 34from tests import common 35 36 37class ProjectManagerListener(object): 38 39 def __init__(self, manager): 40 self.manager = manager 41 self.connectToProjectManager(self.manager) 42 self._reset() 43 44 def _reset(self): 45 self.signals = [] 46 47 def connectToProjectManager(self, manager): 48 for signal in ("new-project-loading", "new-project-loaded", 49 "new-project-created", "new-project-failed", "missing-uri", 50 "closing-project", "project-closed"): 51 self.manager.connect(signal, self._recordSignal, signal) 52 53 def _recordSignal(self, *args): 54 signal = args[-1] 55 args = args[1:-1] 56 self.signals.append((signal, args)) 57 58 return True 59 60 61class TestProjectManager(common.TestCase): 62 63 def setUp(self): 64 super(TestProjectManager, self).setUp() 65 self.setupApp() 66 67 def setupApp(self, app=None): 68 if not app: 69 app = mock.MagicMock() 70 self.manager = ProjectManager(app) 71 self.listener = ProjectManagerListener(self.manager) 72 self.signals = self.listener.signals 73 74 def testLoadProjectFailedUnknownFormat(self): 75 """ 76 Check that new-project-failed is emitted when we don't have a suitable 77 formatter. 78 """ 79 uri = "file:///Untitled.meh" 80 self.manager.loadProject(uri) 81 82 # loading 83 name, args = self.signals[0] 84 self.assertEqual(uri, args[0].get_uri(), self.signals) 85 86 # failed 87 name, args = self.signals[1] 88 self.assertEqual("new-project-failed", name) 89 signalUri, unused_message = args 90 self.assertEqual(uri, signalUri, self.signals) 91 92 def testLoadProjectClosesCurrent(self): 93 """ 94 Check that new-project-failed is emitted if we can't close the current 95 project instance. 96 """ 97 state = {"tried-close": False} 98 99 def close(): 100 state["tried-close"] = True 101 return False 102 self.manager.closeRunningProject = close 103 104 uri = "file:///Untitled.xptv" 105 self.manager.current_project = mock.Mock() 106 self.manager.loadProject(uri) 107 108 self.assertEqual(0, len(self.signals)) 109 self.assertTrue(state["tried-close"], self.signals) 110 111 def testLoadProject(self): 112 self.manager.newBlankProject() 113 114 name, args = self.signals[0] 115 self.assertEqual("new-project-loading", name, self.signals) 116 117 name, args = self.signals[1] 118 self.assertEqual("new-project-created", name, self.signals) 119 120 name, args = self.signals[2] 121 self.assertEqual("new-project-loaded", name, self.signals) 122 123 def testMissingUriForwarded(self): 124 self.setupApp(app=common.create_pitivi_mock()) 125 mainloop = common.create_main_loop() 126 127 def missingUriCb(self, project, error, clip_asset, result): 128 result[0] = True 129 mainloop.quit() 130 131 result = [False] 132 self.manager.connect("missing-uri", missingUriCb, result) 133 134 with common.cloned_sample(): 135 asset_uri = common.get_sample_uri("missing.png") 136 with common.created_project_file(asset_uri) as uri: 137 self.assertTrue(self.manager.loadProject(uri)) 138 mainloop.run() 139 self.assertTrue(result[0], "missing-uri has not been emitted") 140 141 def testLoaded(self): 142 mainloop = common.create_main_loop() 143 144 def new_project_loaded_cb(project_manager, project): 145 mainloop.quit() 146 147 self.manager.connect("new-project-loaded", new_project_loaded_cb) 148 149 with common.cloned_sample("flat_colour1_640x480.png"): 150 asset_uri = common.get_sample_uri("flat_colour1_640x480.png") 151 with common.created_project_file(asset_uri=asset_uri) as uri: 152 self.assertTrue(self.manager.loadProject(uri)) 153 mainloop.run() 154 155 project = self.manager.current_project 156 self.assertFalse(project.at_least_one_asset_missing) 157 self.assertTrue(project.loaded) 158 self.assertFalse(project.hasUnsavedModifications()) 159 160 def testCloseRunningProjectNoProject(self): 161 self.assertTrue(self.manager.closeRunningProject()) 162 self.assertFalse(self.signals) 163 164 def testCloseRunningProjectRefuseFromSignal(self): 165 def closing(manager, project): 166 return False 167 168 self.manager.current_project = mock.Mock() 169 self.manager.current_project.uri = "file:///ciao" 170 self.manager.connect("closing-project", closing) 171 172 self.assertFalse(self.manager.closeRunningProject()) 173 self.assertEqual(1, len(self.signals)) 174 name, args = self.signals[0] 175 self.assertEqual("closing-project", name) 176 project = args[0] 177 self.assertTrue(project is self.manager.current_project) 178 179 def testCloseRunningProject(self): 180 current = mock.Mock() 181 current.uri = None 182 self.manager.current_project = current 183 self.assertTrue(self.manager.closeRunningProject()) 184 self.assertEqual(2, len(self.signals)) 185 186 name, args = self.signals[0] 187 self.assertEqual("closing-project", name) 188 project = args[0] 189 self.assertTrue(project is current) 190 191 name, args = self.signals[1] 192 self.assertEqual("project-closed", name) 193 project = args[0] 194 self.assertTrue(project is current) 195 196 self.assertTrue(self.manager.current_project is None) 197 198 def testNewBlankProjectCantCloseCurrent(self): 199 def closing(manager, project): 200 return False 201 202 self.manager.current_project = mock.Mock() 203 self.manager.current_project.uri = "file:///ciao" 204 self.manager.connect("closing-project", closing) 205 self.assertFalse(self.manager.newBlankProject()) 206 self.assertEqual(1, len(self.signals)) 207 signal, args = self.signals[0] 208 self.assertEqual("closing-project", signal) 209 210 def testNewBlankProject(self): 211 self.assertTrue(self.manager.newBlankProject()) 212 self.assertEqual(3, len(self.signals)) 213 214 name, args = self.signals[0] 215 self.assertEqual("new-project-loading", name) 216 project = args[0] 217 self.assertTrue(project.get_uri() is None) 218 219 name, args = self.signals[1] 220 self.assertEqual("new-project-created", name) 221 project = args[0] 222 self.assertEqual(project.get_uri(), project.uri) 223 224 name, args = self.signals[2] 225 self.assertEqual("new-project-loaded", name) 226 project = args[0] 227 self.assertTrue(project is self.manager.current_project) 228 229 def testSaveProject(self): 230 self.assertTrue(self.manager.newBlankProject()) 231 232 unused, path = tempfile.mkstemp(suffix=".xges") 233 unused, path2 = tempfile.mkstemp(suffix=".xges") 234 try: 235 uri = "file://" + os.path.abspath(path) 236 uri2 = "file://" + os.path.abspath(path2) 237 238 # Save the project. 239 self.assertTrue(self.manager.saveProject(uri=uri, backup=False)) 240 self.assertTrue(os.path.isfile(path)) 241 242 # Wait a bit. 243 time.sleep(0.1) 244 245 # Save the project at a new location. 246 self.assertTrue(self.manager.saveProject(uri2, backup=False)) 247 self.assertTrue(os.path.isfile(path2)) 248 249 # Make sure the old path and the new path have different mtimes. 250 mtime = os.path.getmtime(path) 251 mtime2 = os.path.getmtime(path2) 252 self.assertLess(mtime, mtime2) 253 254 # Wait a bit more. 255 time.sleep(0.1) 256 257 # Save project again under the new path (by omitting uri arg) 258 self.assertTrue(self.manager.saveProject(backup=False)) 259 260 # regression test for bug 594396 261 # make sure we didn't save to the old URI 262 self.assertEqual(mtime, os.path.getmtime(path)) 263 # make sure we did save to the new URI 264 self.assertLess(mtime2, os.path.getmtime(path2)) 265 finally: 266 os.remove(path) 267 os.remove(path2) 268 269 def testMakeBackupUri(self): 270 uri = "file:///tmp/x.xges" 271 self.assertEqual(uri + "~", self.manager._makeBackupURI(uri)) 272 273 def testBackupProject(self): 274 self.manager.newBlankProject() 275 276 # Assign an uri to the project where it's saved by default. 277 unused, xges_path = tempfile.mkstemp(suffix=".xges") 278 uri = "file://" + os.path.abspath(xges_path) 279 self.manager.current_project.uri = uri 280 # This is where the automatic backup file is saved. 281 backup_uri = self.manager._makeBackupURI(uri) 282 283 # Save the backup 284 self.assertTrue(self.manager.saveProject( 285 self.manager.current_project, backup=True)) 286 self.assertTrue(os.path.isfile(path_from_uri(backup_uri))) 287 288 self.manager.closeRunningProject() 289 self.assertFalse(os.path.isfile(path_from_uri(backup_uri)), 290 "Backup file not deleted when project closed") 291 292 293class TestProjectLoading(common.TestCase): 294 295 def test_loaded_callback(self): 296 mainloop = common.create_main_loop() 297 298 def loaded(project, timeline): 299 # If not called, the timeout of the mainloop will fail the test. 300 mainloop.quit() 301 302 # Create a blank project and save it. 303 project = common.create_project() 304 project.connect("loaded", loaded) 305 mainloop.run() 306 # The blank project loading succeeded emitting signal "loaded". 307 308 self.assertIsNotNone(project.ges_timeline) 309 self.assertEqual(len(project.ges_timeline.get_layers()), 1) 310 311 # Load the blank project and make sure "loaded" is triggered. 312 unused, xges_path = tempfile.mkstemp() 313 uri = "file://%s" % xges_path 314 try: 315 # Save so we can close it without complaints. 316 project.save(project.ges_timeline, uri, None, overwrite=True) 317 318 project2 = common.create_project() 319 project2.connect("loaded", loaded) 320 mainloop.run() 321 # The blank project loading succeeded emitting signal "loaded". 322 323 self.assertIsNotNone(project2.ges_timeline) 324 self.assertEqual(len(project2.ges_timeline.get_layers()), 1) 325 finally: 326 os.remove(xges_path) 327 328 def test_asset_added_signal(self): 329 app = common.create_pitivi() 330 self.assertTrue(app.project_manager.newBlankProject()) 331 332 project = app.project_manager.current_project 333 proxy_manager = app.proxy_manager 334 335 mainloop = common.create_main_loop() 336 337 def asset_added_cb(project, asset, assets): 338 assets.append(asset) 339 340 assets = [] 341 project.connect("asset-added", asset_added_cb, assets) 342 343 def proxy_ready_cb(unused_proxy_manager, asset, proxy): 344 mainloop.quit() 345 346 proxy_manager.connect("proxy-ready", proxy_ready_cb) 347 348 uris = [common.get_sample_uri("tears_of_steel.webm")] 349 project.addUris(uris) 350 351 mainloop.run() 352 353 self.assertEqual(len(assets), 1, assets) 354 355 def create_project_file_from_xges(self, app, xges): 356 unused, xges_path = tempfile.mkstemp(suffix=".xges") 357 proj_uri = Gst.filename_to_uri(os.path.abspath(xges_path)) 358 app.project_manager.saveProject(uri=proj_uri) 359 360 with open(xges_path, "w") as f: 361 f.write(xges) 362 363 return proj_uri 364 365 def load_project_with_missing_proxy(self): 366 """Loads a project with missing proxies.""" 367 uris = [common.get_sample_uri("1sec_simpsons_trailer.mp4")] 368 proxy_uri = uris[0] + ".232417.proxy.mkv" 369 PROJECT_STR = """<ges version='0.3'> 370 <project properties='properties;' metadatas='metadatas, name=(string)"New\ Project", author=(string)Unknown, render-scale=(double)100;'> 371 <encoding-profiles> 372 </encoding-profiles> 373 <ressources> 374 <asset id='%(uri)s' extractable-type-name='GESUriClip' properties='properties, supported-formats=(int)6, duration=(guint64)1228000000;' metadatas='metadatas, audio-codec=(string)"MPEG-4\ AAC\ audio", maximum-bitrate=(uint)130625, bitrate=(uint)130625, datetime=(datetime)2007-02-19T05:03:04Z, encoder=(string)Lavf54.6.100, container-format=(string)"ISO\ MP4/M4A", video-codec=(string)"H.264\ /\ AVC", file-size=(guint64)232417;' proxy-id='file:///home/thiblahute/devel/pitivi/flatpak/pitivi/tests/samples/1sec_simpsons_trailer.mp4.232417.proxy.mkv' /> 375 <asset id='%(proxy_uri)s' extractable-type-name='GESUriClip' properties='properties, supported-formats=(int)6, duration=(guint64)1228020833;' metadatas='metadatas, container-format=(string)Matroska, audio-codec=(string)Opus, language-code=(string)en, encoder=(string)Lavf54.6.100, bitrate=(uint)64000, video-codec=(string)"Motion\ JPEG", file-size=(guint64)4695434;' /> 376 </ressources> 377 <timeline properties='properties, auto-transition=(boolean)true, snapping-distance=(guint64)0;' metadatas='metadatas, duration=(guint64)0;'> 378 <track caps='video/x-raw(ANY)' track-type='4' track-id='0' properties='properties, async-handling=(boolean)false, message-forward=(boolean)true, caps=(string)"video/x-raw\(ANY\)", restriction-caps=(string)"video/x-raw\,\ width\=\(int\)720\,\ height\=\(int\)576\,\ framerate\=\(fraction\)25/1", mixing=(boolean)true;' metadatas='metadatas;'/> 379 <track caps='audio/x-raw(ANY)' track-type='2' track-id='1' properties='properties, async-handling=(boolean)false, message-forward=(boolean)true, caps=(string)"audio/x-raw\(ANY\)", restriction-caps=(string)"audio/x-raw\,\ format\=\(string\)S32LE\,\ channels\=\(int\)2\,\ rate\=\(int\)44100\,\ layout\=\(string\)interleaved", mixing=(boolean)true;' metadatas='metadatas;'/> 380 <layer priority='0' properties='properties, auto-transition=(boolean)true;' metadatas='metadatas, volume=(float)1;'> 381 <clip id='0' asset-id='%(proxy_uri)s' type-name='GESUriClip' layer-priority='0' track-types='6' start='0' duration='1228000000' inpoint='0' rate='0' properties='properties, name=(string)uriclip0, mute=(boolean)false, is-image=(boolean)false;' > 382 <source track-id='1' children-properties='properties, GstVolume::mute=(boolean)false, GstVolume::volume=(double)1;'> 383 <binding type='direct' source_type='interpolation' property='volume' mode='1' track_id='1' values =' 0:0.10000000000000001 1228000000:0.10000000000000001 '/> 384 </source> 385 <source track-id='0' children-properties='properties, GstFramePositioner::alpha=(double)1, GstDeinterlace::fields=(int)0, GstFramePositioner::height=(int)720, GstDeinterlace::mode=(int)0, GstFramePositioner::posx=(int)0, GstFramePositioner::posy=(int)0, GstDeinterlace::tff=(int)0, GstFramePositioner::width=(int)1280;'> 386 <binding type='direct' source_type='interpolation' property='alpha' mode='1' track_id='0' values =' 0:1 1228000000:1 '/> 387 </source> 388 </clip> 389 </layer> 390 <groups> 391 </groups> 392 </timeline> 393</project> 394</ges>""" % {"uri": uris[0], "proxy_uri": proxy_uri} 395 app = common.create_pitivi(proxyingStrategy=ProxyingStrategy.ALL) 396 proxy_manager = app.proxy_manager 397 project_manager = app.project_manager 398 medialib = medialibrary.MediaLibraryWidget(app) 399 400 mainloop = common.create_main_loop() 401 402 proj_uri = self.create_project_file_from_xges(app, PROJECT_STR) 403 404 def closing_project_cb(*args, **kwargs): 405 # Do not ask whether to save project on closing. 406 return True 407 408 def proxy_ready_cb(proxy_manager, asset, proxy): 409 self.assertEqual(proxy.props.id, proxy_uri) 410 mainloop.quit() 411 412 project_manager.connect("closing-project", closing_project_cb) 413 proxy_manager.connect_after("proxy-ready", proxy_ready_cb) 414 415 app.project_manager.loadProject(proj_uri) 416 return mainloop, app, medialib, proxy_uri 417 418 def test_load_project_with_missing_proxy(self): 419 """Checks loading a project with missing proxies.""" 420 with common.cloned_sample("1sec_simpsons_trailer.mp4"): 421 mainloop, app, medialib, proxy_uri = self.load_project_with_missing_proxy() 422 mainloop.run() 423 424 self.assertEqual(len(medialib.storemodel), 1) 425 self.assertEqual(medialib.storemodel[0][medialibrary.COL_ASSET].props.id, 426 proxy_uri) 427 self.assertEqual(medialib.storemodel[0][medialibrary.COL_THUMB_DECORATOR].state, 428 medialibrary.AssetThumbnail.PROXIED) 429 430 def test_load_project_with_missing_proxy_progress_tracking(self): 431 """Checks progress tracking of loading project with missing proxies.""" 432 from gi.repository import GstTranscoder 433 434 with common.cloned_sample("1sec_simpsons_trailer.mp4"): 435 # Disable proxy generation by not making it start ever. 436 # This way we are sure it will not finish before we test 437 # the state while it is being rebuilt. 438 with mock.patch.object(GstTranscoder.Transcoder, "run_async"): 439 mainloop, app, medialib, proxy_uri = self.load_project_with_missing_proxy() 440 uri = common.get_sample_uri("1sec_simpsons_trailer.mp4") 441 442 app.project_manager.connect("new-project-loaded", lambda x, y: mainloop.quit()) 443 mainloop.run() 444 445 self.assertEqual(len(medialib.storemodel), 1) 446 self.assertEqual(medialib.storemodel[0][medialibrary.COL_ASSET].props.id, 447 uri) 448 self.assertEqual(medialib.storemodel[0][medialibrary.COL_THUMB_DECORATOR].state, 449 medialibrary.AssetThumbnail.IN_PROGRESS) 450 451 def test_load_project_with_missing_proxy_stop_generating_and_proxy(self): 452 """Checks cancelling creation of a missing proxies and forcing it again.""" 453 from gi.repository import GstTranscoder 454 455 with common.cloned_sample("1sec_simpsons_trailer.mp4"): 456 # Disable proxy generation by not making it start ever. 457 # This way we are sure it will not finish before we test 458 # stop generating the proxy and restart it. 459 with mock.patch.object(GstTranscoder.Transcoder, "run_async"): 460 mainloop, app, medialib, proxy_uri = self.load_project_with_missing_proxy() 461 462 app.project_manager.connect("new-project-loaded", lambda x, y: mainloop.quit()) 463 mainloop.run() 464 asset = medialib.storemodel[0][medialibrary.COL_ASSET] 465 app.project_manager.current_project.disable_proxies_for_assets([asset]) 466 467 row, = medialib.storemodel 468 asset = row[medialibrary.COL_ASSET] 469 self.assertEqual(medialib._progressbar.get_fraction(), 1.0) 470 uri = common.get_sample_uri("1sec_simpsons_trailer.mp4") 471 self.assertEqual(asset.props.id, uri) 472 self.assertEqual(asset.ready, True) 473 self.assertEqual(asset.creation_progress, 100) 474 self.assertEqual(row[medialibrary.COL_THUMB_DECORATOR].state, 475 medialibrary.AssetThumbnail.NO_PROXY) 476 477 app.project_manager.current_project.use_proxies_for_assets([asset]) 478 mainloop.run() 479 480 row, = medialib.storemodel 481 asset = row[medialibrary.COL_ASSET] 482 self.assertEqual(medialib._progressbar.is_visible(), False) 483 self.assertEqual(asset.props.id, proxy_uri) 484 self.assertEqual(asset.ready, True) 485 self.assertEqual(asset.creation_progress, 100) 486 self.assertEqual(row[medialibrary.COL_THUMB_DECORATOR].state, 487 medialibrary.AssetThumbnail.PROXIED) 488 489 def test_loading_project_with_moved_asset(self): 490 """Loads a project with moved asset.""" 491 app = common.create_pitivi(proxyingStrategy=ProxyingStrategy.NOTHING) 492 493 proj_uri = self.create_project_file_from_xges(app, """<ges version='0.3'> 494 <project properties='properties;' metadatas='metadatas;'> 495 <ressources> 496 <asset id='file://this/is/a/moved/asset.mp4' extractable-type-name='GESUriClip' 497 properties='properties, supported-formats=(int)6, duration=(guint64)1228000000;' metadatas='metadatas' /> 498 </ressources> 499 </project> 500 </ges>""") 501 project_manager = app.project_manager 502 medialib = medialibrary.MediaLibraryWidget(app) 503 504 mainloop = common.create_main_loop() 505 506 def new_project_loaded_cb(*args, **kwargs): 507 mainloop.quit() 508 509 def missing_uri_cb(project_manager, project, unused_error, asset): 510 return common.get_sample_uri("1sec_simpsons_trailer.mp4") 511 512 project_manager.connect("missing-uri", missing_uri_cb) 513 project_manager.connect("new-project-loaded", new_project_loaded_cb) 514 515 project_manager.loadProject(proj_uri) 516 with common.cloned_sample("1sec_simpsons_trailer.mp4"): 517 mainloop.run() 518 self.assertEqual(medialib._progressbar.get_fraction(), 1.0) 519 520 def test_loading_project_with_moved_assets_and_deleted_proxy(self): 521 """Loads a project with moved asset as deleted proxy file.""" 522 523 mainloop = common.create_main_loop() 524 525 def proxy_ready_cb(unused_proxy_manager, asset, proxy): 526 mainloop.quit() 527 528 app = common.create_pitivi(proxyingStrategy=ProxyingStrategy.ALL) 529 app.proxy_manager.connect("proxy-ready", proxy_ready_cb) 530 531 proj_uri = self.create_project_file_from_xges(app, """<ges version='0.3'> 532 <project properties='properties;' metadatas='metadatas, name=(string)"New\ Project", author=(string)Unknown, render-scale=(double)100, format-version=(string)0.3;'> 533 <ressources> 534 <asset id='file:///nop/1sec_simpsons_trailer.mp4' extractable-type-name='GESUriClip' properties='properties, supported-formats=(int)6, duration=(guint64)1228000000;' metadatas='metadatas, audio-codec=(string)"MPEG-4\ AAC\ audio", maximum-bitrate=(uint)130625, bitrate=(uint)130625, datetime=(datetime)2007-02-19T05:03:04Z, encoder=(string)Lavf54.6.100, container-format=(string)"ISO\ MP4/M4A", video-codec=(string)"H.264\ /\ AVC", file-size=(guint64)232417;' proxy-id='file:///nop/1sec_simpsons_trailer.mp4.232417.proxy.mkv' /> 535 <asset id='file:///nop/tears_of_steel.webm' extractable-type-name='GESUriClip' properties='properties, supported-formats=(int)6, duration=(guint64)2003000000;' metadatas='metadatas, container-format=(string)Matroska, language-code=(string)und, application-name=(string)Lavc56.60.100, encoder=(string)"Xiph.Org\ libVorbis\ I\ 20150105\ \(\342\233\204\342\233\204\342\233\204\342\233\204\)", encoder-version=(uint)0, audio-codec=(string)Vorbis, nominal-bitrate=(uint)80000, bitrate=(uint)80000, video-codec=(string)"VP8\ video", file-size=(guint64)223340;' /> 536 <asset id='file:///nop/1sec_simpsons_trailer.mp4.232417.proxy.mkv' extractable-type-name='GESUriClip' properties='properties, supported-formats=(int)6, duration=(guint64)1228020833;' metadatas='metadatas, container-format=(string)Matroska, audio-codec=(string)Opus, language-code=(string)en, encoder=(string)Lavf54.6.100, bitrate=(uint)64000, video-codec=(string)"Motion\ JPEG", file-size=(guint64)4694708;' /> 537 <asset id='file:///nop/tears_of_steel.webm.223340.proxy.mkv' extractable-type-name='GESUriClip' properties='properties, supported-formats=(int)6, duration=(guint64)2003000000;' metadatas='metadatas, container-format=(string)Matroska, language-code=(string)und, application-name=(string)Lavc56.60.100, encoder=(string)"Xiph.Org\ libVorbis\ I\ 20150105\ \(\342\233\204\342\233\204\342\233\204\342\233\204\)", encoder-version=(uint)0, audio-codec=(string)Vorbis, nominal-bitrate=(uint)80000, bitrate=(uint)80000, video-codec=(string)"VP8\ video", file-size=(guint64)223340;' /> 538 </ressources> 539</project> 540</ges>""") 541 project_manager = app.project_manager 542 medialib = medialibrary.MediaLibraryWidget(app) 543 544 # Remove proxy 545 with common.cloned_sample("1sec_simpsons_trailer.mp4", "tears_of_steel.webm"): 546 def new_project_loaded_cb(*args, **kwargs): 547 mainloop.quit() 548 549 missing_uris = [] 550 551 def missing_uri_cb(project_manager, project, unused_error, asset): 552 missing_uris.append(asset.props.id) 553 return common.get_sample_uri(os.path.basename(asset.props.id)) 554 555 project_manager.connect("missing-uri", missing_uri_cb) 556 project_manager.connect("new-project-loaded", new_project_loaded_cb) 557 558 project_manager.loadProject(proj_uri) 559 mainloop.run() 560 self.assertEqual(len(missing_uris), 1, 561 "missing_uri_cb should be called only once, got %s." % missing_uris) 562 self.assertEqual(medialib._progressbar.get_fraction(), 1.0) 563 mainloop.run() 564 self.assertEqual(len(medialib.storemodel), 2, 565 "We should have one asset displayed in the MediaLibrary.") 566 self.assertEqual(medialib.storemodel[0][medialibrary.COL_THUMB_DECORATOR].state, 567 medialibrary.AssetThumbnail.PROXIED) 568 self.assertEqual(medialib.storemodel[1][medialibrary.COL_THUMB_DECORATOR].state, 569 medialibrary.AssetThumbnail.IN_PROGRESS) 570 571 572class TestProjectSettings(common.TestCase): 573 574 def testAudio(self): 575 project = common.create_project() 576 project.audiochannels = 2 577 self.assertEqual(2, project.audiochannels) 578 project.audiorate = 44100 579 self.assertEqual(44100, project.audiorate) 580 581 def testVideo(self): 582 project = common.create_project() 583 project.videowidth = 1920 584 self.assertEqual(1920, project.videowidth) 585 project.videoheight = 1080 586 self.assertEqual(1080, project.videoheight) 587 project.videorate = Gst.Fraction(50, 7) 588 self.assertEqual(Gst.Fraction(50, 7), project.videorate) 589 590 def testSetAudioProp(self): 591 timeline = common.create_timeline_container() 592 project = timeline.app.project_manager.current_project 593 project.addUris([common.get_sample_uri("mp3_sample.mp3")]) 594 595 audio_track = [t for t in project.ges_timeline.tracks if isinstance(t, GES.AudioTrack)][0] 596 mainloop = common.create_main_loop() 597 598 def progress_cb(project, progress, estimated_time): 599 if progress == 100: 600 mainloop.quit() 601 602 project.connect_after("asset-loading-progress", progress_cb) 603 mainloop.run() 604 605 expected = Gst.Caps("audio/x-raw,channels=(int)2,rate=(int)44100") 606 ccaps = audio_track.props.restriction_caps 607 self.assertTrue(ccaps.is_equal_fixed(expected), "%s != %s" % (ccaps, expected)) 608 609 project.audiochannels = 6 610 611 expected = Gst.Caps("audio/x-raw,channels=(int)6,rate=(int)44100") 612 ccaps = audio_track.props.restriction_caps 613 self.assertTrue(ccaps.is_equal_fixed(expected), "%s != %s" % (ccaps, expected)) 614 615 def testInitialization(self): 616 mainloop = common.create_main_loop() 617 uris = collections.deque([ 618 common.get_sample_uri("flat_colour1_640x480.png"), 619 common.get_sample_uri("tears_of_steel.webm"), 620 common.get_sample_uri("1sec_simpsons_trailer.mp4")]) 621 622 def loaded_cb(project, timeline): 623 project.addUris([uris.popleft()]) 624 625 def progress_cb(project, progress, estimated_time): 626 if progress == 100: 627 if uris: 628 project.addUris([uris.popleft()]) 629 else: 630 mainloop.quit() 631 632 # Create a blank project and add some assets. 633 project = common.create_project() 634 self.assertTrue(project._has_default_video_settings) 635 self.assertTrue(project._has_default_audio_settings) 636 637 project.connect_after("loaded", loaded_cb) 638 project.connect_after("asset-loading-progress", progress_cb) 639 640 mainloop.run() 641 642 assets = project.list_assets(GES.UriClip) 643 self.assertEqual(3, len(assets), assets) 644 645 self.assertFalse(project._has_default_video_settings) 646 self.assertFalse(project._has_default_audio_settings) 647 648 # The audio settings should match tears_of_steel.webm 649 self.assertEqual(1, project.audiochannels) 650 self.assertEqual(44100, project.audiorate) 651 652 # The video settings should match tears_of_steel.webm 653 self.assertEqual(960, project.videowidth) 654 self.assertEqual(400, project.videoheight) 655 self.assertEqual(Gst.Fraction(24, 1), project.videorate) 656 657 def testLoad(self): 658 project = Project(uri="fake.xges", app=common.create_pitivi_mock()) 659 self.assertFalse(project._has_default_video_settings) 660 self.assertFalse(project._has_default_audio_settings) 661 662 663class TestExportSettings(common.TestCase): 664 665 def test_master_attributes(self): 666 self._check_master_attribute("muxer", dependant_attr="containersettings") 667 self._check_master_attribute("vencoder", dependant_attr="vcodecsettings") 668 self._check_master_attribute("aencoder", dependant_attr="acodecsettings") 669 670 def _check_master_attribute(self, attr, dependant_attr): 671 """Test changing the specified attr has effect on its dependent attr.""" 672 project = common.create_project() 673 674 attr_value1 = "%s_value1" % attr 675 attr_value2 = "%s_value2" % attr 676 677 setattr(project, attr, attr_value1) 678 setattr(project, dependant_attr, {}) 679 getattr(project, dependant_attr)["key1"] = "v1" 680 681 setattr(project, attr, attr_value2) 682 setattr(project, dependant_attr, {}) 683 getattr(project, dependant_attr)["key2"] = "v2" 684 685 setattr(project, attr, attr_value1) 686 self.assertTrue("key1" in getattr(project, dependant_attr)) 687 self.assertFalse("key2" in getattr(project, dependant_attr)) 688 self.assertEqual("v1", getattr(project, dependant_attr)["key1"]) 689 setattr(project, dependant_attr, {}) 690 691 setattr(project, attr, attr_value2) 692 self.assertFalse("key1" in getattr(project, dependant_attr)) 693 self.assertTrue("key2" in getattr(project, dependant_attr)) 694 self.assertEqual("v2", getattr(project, dependant_attr)["key2"]) 695 setattr(project, dependant_attr, {}) 696 697 setattr(project, attr, attr_value1) 698 self.assertFalse("key1" in getattr(project, dependant_attr)) 699 self.assertFalse("key2" in getattr(project, dependant_attr)) 700 701 setattr(project, attr, attr_value2) 702 self.assertFalse("key1" in getattr(project, dependant_attr)) 703 self.assertFalse("key2" in getattr(project, dependant_attr)) 704 705 def test_set_rendering(self): 706 """Checks the set_rendering method.""" 707 mainloop = common.create_main_loop() 708 709 def loaded_cb(project, timeline): 710 project.addUris([common.get_sample_uri("tears_of_steel.webm")]) 711 712 def progress_cb(project, progress, estimated_time): 713 if progress == 100: 714 mainloop.quit() 715 716 # Create a blank project and add some assets. 717 project = common.create_project() 718 719 project.connect_after("loaded", loaded_cb) 720 project.connect_after("asset-loading-progress", progress_cb) 721 722 mainloop.run() 723 724 # The video settings should match tears_of_steel.webm 725 self.assertEqual(project.videowidth, 960) 726 self.assertEqual(project.videoheight, 400) 727 728 project.render_scale = 3 729 # Pretend we're rendering. 730 project.set_rendering(True) 731 self.assertEqual(project.videowidth, 28) 732 self.assertEqual(project.videoheight, 12) 733 734 # Pretend we're not rendering anymore. 735 project.set_rendering(False) 736 self.assertEqual(project.videowidth, 960) 737 self.assertEqual(project.videoheight, 400) 738