27
Hierarchy of the whole thing:
32
ProjectCreatorGuiPitivi
33
ProjectLoaderGuiPitivi
34
StartupWizardGuiPitivi
29
from optparse import OptionParser
34
from pitivi.pitivigstutils import patch_gst_python
39
from gi.repository import GES
40
from gi.repository import Gio
41
from gi.repository import GLib
42
from gi.repository import Gtk
37
44
from gettext import gettext as _
39
import pitivi.instance as instance
41
from pitivi.check import initial_checks
45
from optparse import OptionParser
42
47
from pitivi.effects import EffectsHandler
43
from pitivi.configure import APPNAME
48
from pitivi.configure import VERSION, RELEASES_URL
44
49
from pitivi.settings import GlobalSettings
45
from pitivi.threads import ThreadMaster
46
from pitivi.signalinterface import Signallable
47
from pitivi.log.loggable import Loggable
48
from pitivi.log import log
49
from pitivi.ui.mainwindow import PitiviMainWindow
50
from pitivi.projectmanager import ProjectManager, ProjectLogObserver
51
from pitivi.undo import UndoableActionLog, DebugActionLogObserver
52
from pitivi.timeline.timeline_undo import TimelineLogObserver
53
from pitivi.sourcelist_undo import SourceListLogObserver
54
from pitivi.ui.viewer import PitiviViewer
55
from pitivi.actioner import Renderer, Previewer
56
from pitivi.ui.startupwizard import StartUpWizard
50
from pitivi.utils.threads import ThreadMaster
51
from pitivi.mainwindow import PitiviMainWindow
52
from pitivi.project import ProjectManager, ProjectLogObserver
53
from pitivi.undo.undo import UndoableActionLog, DebugActionLogObserver
54
from pitivi.dialogs.startupwizard import StartUpWizard
58
# FIXME : Speedup loading time
59
# Currently we load everything in one go
60
# It would be better if a minimalistic UI could start up ASAP, without loading
61
# anything gst-related or that could slow down startup.
62
# AND THEN load up the required parts.
63
# This will result in a much better end-user experience
56
from pitivi.utils.misc import quote_uri
57
from pitivi.utils.signal import Signallable
58
from pitivi.utils.system import getSystem
59
from pitivi.utils.loggable import Loggable
60
import pitivi.utils.loggable as log
61
#FIXME GES port disabled it
62
#from pitivi.undo.timeline import TimelineLogObserver
66
65
class Pitivi(Loggable, Signallable):
94
89
"new-project-created": ["project"],
95
90
"new-project-loaded": ["project"],
96
91
"new-project-failed": ["uri", "exception"],
97
"closing-project": ["project"],
98
92
"project-closed": ["project"],
99
93
"missing-uri": ["formatter", "uri"],
94
"version-info-received": ["versions"],
102
97
def __init__(self):
104
initialize pitivi with the command line arguments
106
98
Loggable.__init__(self)
108
# init logging as early as possible so we can log startup code
109
enable_color = os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '0')
110
log.init('PITIVI_DEBUG', enable_color)
100
# Init logging as early as possible so we can log startup code
101
enable_color = not os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '1')
102
# Let's show a human-readable pitivi debug output by default, and only
103
# show a crazy unreadable mess when surrounded by gst debug statements.
104
enable_crack_output = "GST_DEBUG" in os.environ
105
log.init('PITIVI_DEBUG', enable_color, enable_crack_output)
112
107
self.info('starting up')
114
# store ourself in the instance global
116
raise RuntimeWarning(
117
_("There is already a %s instance, please inform the developers by filing a bug at http://bugzilla.gnome.org/enter_bug.cgi?product=pitivi")
119
instance.PiTiVi = self
124
109
self.settings = GlobalSettings()
125
110
self.threads = ThreadMaster()
126
#self.screencast = False
128
111
self.effects = EffectsHandler()
112
self.system = getSystem()
130
self.projectManager = ProjectManager(self.effects)
114
self.current_project = None
115
self.projectManager = ProjectManager(self)
131
116
self._connectToProjectManager(self.projectManager)
133
118
self.action_log = UndoableActionLog()
134
119
self.debug_action_log_observer = DebugActionLogObserver()
135
120
self.debug_action_log_observer.startObserving(self.action_log)
136
self.timelineLogObserver = TimelineLogObserver(self.action_log)
121
# TODO reimplement the observing after GES port
122
#self.timelineLogObserver = TimelineLogObserver(self.action_log)
137
123
self.projectLogObserver = ProjectLogObserver(self.action_log)
138
self.sourcelist_log_observer = SourceListLogObserver(self.action_log)
125
self._version_information = {}
140
128
def shutdown(self):
144
@return: C{True} if PiTiVi was successfully closed, else C{False}.
132
@return: C{True} if Pitivi was successfully closed, else C{False}.
147
135
self.debug("shutting down")
148
136
# we refuse to close if we're running a user interface and the user
149
137
# doesn't want us to close the current project.
150
if self.current and not self.projectManager.closeRunningProject():
138
if self.current_project and not self.projectManager.closeRunningProject():
151
139
self.warning("Not closing since running project doesn't want to close")
153
141
self.threads.stopAllThreads()
154
142
self.settings.storeSettings()
156
instance.PiTiVi = None
143
self.current_project = None
157
144
self.emit("shutdown")
160
147
def _connectToProjectManager(self, projectManager):
161
projectManager.connect("new-project-loading",
162
self._projectManagerNewProjectLoading)
163
projectManager.connect("new-project-created",
164
self._projectManagerNewProjectCreated)
165
projectManager.connect("new-project-loaded",
166
self._projectManagerNewProjectLoaded)
167
projectManager.connect("new-project-failed",
168
self._projectManagerNewProjectFailed)
169
projectManager.connect("closing-project",
170
self._projectManagerClosingProject)
171
projectManager.connect("project-closed",
172
self._projectManagerProjectClosed)
149
pm.connect("new-project-loading", self._projectManagerNewProjectLoading)
150
pm.connect("new-project-created", self._projectManagerNewProjectCreated)
151
pm.connect("new-project-loaded", self._projectManagerNewProjectLoaded)
152
pm.connect("new-project-failed", self._projectManagerNewProjectFailed)
153
pm.connect("project-closed", self._projectManagerProjectClosed)
174
def _projectManagerNewProjectLoading(self, projectManager, uri):
155
def _projectManagerNewProjectLoading(self, unused_project_manager, uri):
175
156
self.emit("new-project-loading", uri)
177
def _projectManagerNewProjectCreated(self, projectManager, project):
178
self.current = project
158
def _projectManagerNewProjectCreated(self, unused_project_manager, project):
159
self.current_project = project
179
160
self.emit("new-project-created", project)
181
def _newProjectLoaded(self, project):
162
def _newProjectLoaded(self, unused_project):
184
def _projectManagerNewProjectLoaded(self, projectManager, project):
185
self.current = project
165
def _projectManagerNewProjectLoaded(self, unused_project_manager, project, unused_fully_loaded):
166
self.current_project = project
186
167
self.action_log.clean()
187
self.timelineLogObserver.startObserving(project.timeline)
168
#self.timelineLogObserver.startObserving(project.timeline)
188
169
self.projectLogObserver.startObserving(project)
189
self.sourcelist_log_observer.startObserving(project.sources)
190
170
self._newProjectLoaded(project)
191
171
self.emit("new-project-loaded", project)
193
def _projectManagerNewProjectFailed(self, projectManager, uri, exception):
173
def _projectManagerNewProjectFailed(self, unused_project_manager, uri, exception):
194
174
self.emit("new-project-failed", uri, exception)
196
def _projectManagerClosingProject(self, projectManager, project):
197
return self.emit("closing-project", project)
199
def _projectManagerProjectClosed(self, projectManager, project):
200
self.timelineLogObserver.stopObserving(project.timeline)
176
def _projectManagerProjectClosed(self, unused_project_manager, project):
177
#self.timelineLogObserver.stopObserving(project.timeline)
201
178
self.projectLogObserver.stopObserving(project)
179
self.current_project = None
203
180
self.emit("project-closed", project)
182
def _checkVersion(self):
183
# Check online for release versions information
184
giofile = Gio.File.new_for_uri(RELEASES_URL)
185
self.info("Requesting version information")
186
giofile.load_contents_async(None, self._versionInfoReceivedCb, None)
188
def _versionInfoReceivedCb(self, giofile, result, user_data):
190
raw = giofile.load_contents_finish(result)[1]
191
raw = raw.split("\n")
192
# Split line at '=' if the line is not empty or a comment line
193
data = [element.split("=") for element in raw
194
if element and not element.startswith("#")]
196
# search newest version and status
197
status = "UNSUPPORTED"
198
current_version = None
199
for version, version_status in data:
200
if VERSION == version:
201
status = version_status
202
if version_status.upper() == "CURRENT":
203
# This is the latest.
204
current_version = version
206
self.info("Latest software version is %s", current_version)
207
# Python is magical... comparing version *strings* always works,
208
# even with different major.minor.nano version number schemes!
209
if VERSION > current_version:
211
self.info("Running version %s, which is newer than the latest known version. Considering it as the latest current version.", VERSION)
212
elif status is "UNSUPPORTED":
213
self.warning("Using an outdated version of Pitivi (%s)", VERSION)
215
self._version_information["current"] = current_version
216
self._version_information["status"] = status
217
self.emit("version-info-received", self._version_information)
219
self.warning("Version info could not be read: %s", e)
223
Whether the app's version is the latest as far as we know.
225
status = self._version_information.get("status")
226
return status is None or status.upper() == "CURRENT"
230
Get the latest version of the app or None.
232
return self._version_information.get("current")
206
235
class InteractivePitivi(Pitivi):
208
Base class to launch interactive PiTiVi
237
Base class to launch interactive Pitivi
211
240
def __init__(self, debug=False):
212
241
Pitivi.__init__(self)
213
self.mainloop = gobject.MainLoop()
242
self.mainloop = GLib.MainLoop()
217
# Check the dependencies.
218
missing_deps = initial_checks()
220
message, detail = missing_deps
221
self._showStartupError(message, detail)
225
245
sys.excepthook = self._excepthook
227
def _showStartupError(self, message, detail):
228
self.error("%s %s" % (message, detail))
230
def _excepthook(self, exc_type, value, tback):
247
def _excepthook(self, unused_exc_type, unused_value, tback):
233
250
traceback.print_tb(tback)
234
251
pdb.post_mortem(tback)
236
def _setActioner(self, actioner):
237
self.actioner = actioner
239
self.actioner.connect("eos", self._eosCb)
240
# On error, all we need to do is shutdown which
241
# is the same as we do for EOS
242
self.actioner.connect("error", self._eosCb)
243
# Configure the actioner and start acting!
244
self.actioner.startAction()
246
def _eosCb(self, unused_obj):
247
raise NotImplementedError()
249
def _loadProject(self, project_filename):
250
project = "file://%s" % os.path.abspath(project_filename)
251
self.projectManager.loadProject(project)
254
254
"""Runs the main loop."""
255
255
self.mainloop.run()
295
class FullGuiPitivi(GuiPitivi):
297
Creates an instance of PiTiVi with the UI
299
This is called when we start the UI with a project passed as a parameter,
300
but not when we start with the welcome dialog.
303
def _createGui(self, **kargs):
304
return PitiviMainWindow(self, **kargs)
307
class ProjectCreatorGuiPitivi(FullGuiPitivi):
309
Creates an instance of PiTiVi with the UI and loading a list
294
class ProjectCreatorGuiPitivi(GuiPitivi):
296
Creates an instance of Pitivi with the UI and loading a list
310
297
of clips, adding them to the timeline or not
313
300
def __init__(self, media_filenames, add_to_timeline=False, debug=False):
314
FullGuiPitivi.__init__(self, debug)
301
GuiPitivi.__init__(self, debug)
315
302
# load the passed filenames, optionally adding them to the timeline
316
303
# (useful during development)
317
self.projectManager.newBlankProject()
318
uris = ["file://" + urllib.quote(os.path.abspath(media_filename))
304
self.projectManager.newBlankProject(emission=False)
305
uris = [quote_uri(os.path.abspath(media_filename))
319
306
for media_filename in media_filenames]
320
self.current.sources.connect("source-added",
321
self._sourceAddedCb, uris, add_to_timeline)
322
self.current.sources.connect("discovery-error",
323
self._discoveryErrorCb, uris)
324
self.current.sources.addUris(uris)
307
lib = self.current_project.medialibrary
308
lib.connect("source-added", self._sourceAddedCb, uris, add_to_timeline)
309
lib.connect("discovery-error", self._discoveryErrorCb, uris)
326
def _sourceAddedCb(self, sourcelist, factory,
327
startup_uris, add_to_timeline):
328
if self._maybePopStartupUri(startup_uris, factory.uri) \
312
def _sourceAddedCb(self, unused_media_library, info, startup_uris, add_to_timeline):
313
if self._maybePopStartupUri(startup_uris, info.get_uri()) \
329
314
and add_to_timeline:
330
315
self.action_log.begin("add clip")
331
self.current.timeline.addSourceFactory(factory)
316
src = GES.UriClip(uri=info.get_uri())
317
src.set_property("priority", 1)
318
self.current_project.timeline.get_layers()[0].add_clip(src)
332
319
self.action_log.commit()
334
def _discoveryErrorCb(self, sourcelist, uri, error, debug, startup_uris):
321
def _discoveryErrorCb(self, unused_media_library, uri, unused_error, unused_debug, startup_uris):
335
322
self._maybePopStartupUri(startup_uris, uri)
337
324
def _maybePopStartupUri(self, startup_uris, uri):
346
333
if not startup_uris:
347
self.current.sources.disconnect_by_function(self._sourceAddedCb)
348
self.current.sources.disconnect_by_function(self._discoveryErrorCb)
334
self.current_project.medialibrary.disconnect_by_function(self._sourceAddedCb)
335
self.current_project.medialibrary.disconnect_by_function(self._discoveryErrorCb)
353
class ProjectLoaderGuiPitivi(FullGuiPitivi):
340
class ProjectLoaderGuiPitivi(GuiPitivi):
355
342
Creates an instance of the UI and loads @project_filename
358
345
def __init__(self, project_filename, debug=False):
359
FullGuiPitivi.__init__(self, debug)
360
self._loadProject(project_filename)
363
class StartupWizardGuiPitivi(FullGuiPitivi):
346
GuiPitivi.__init__(self, debug)
347
if not os.path.exists(project_filename):
348
self.error("Project file does not exist: %s" % project_filename)
350
self.projectManager.loadProject(quote_uri(os.path.abspath(project_filename)))
353
class StartupWizardGuiPitivi(GuiPitivi):
365
Creates an instance of the PiTiVi UI with the welcome dialog
355
Creates an instance of the Pitivi UI with the welcome dialog
367
357
This is not called when a project is passed as a parameter.
370
360
def __init__(self, debug=False):
371
FullGuiPitivi.__init__(self, debug)
361
GuiPitivi.__init__(self, debug)
362
self.projectManager.newBlankProject(emission=False)
373
364
def _createGui(self):
365
return PitiviMainWindow(self)
368
GuiPitivi._showGui(self)
374
369
self.wizard = StartUpWizard(self)
375
# Prevent the main window to go fullscreen because at least
376
# the Metacity window manager will refuse to bring
377
# the startup wizard window in front of the main window.
378
return FullGuiPitivi._createGui(self, allow_full_screen=False)
381
FullGuiPitivi._showGui(self)
382
370
self.wizard.show()
385
class PreviewGuiPitivi(GuiPitivi):
387
Creates an instance of PiTiVi which plays the @project_filename
391
def __init__(self, project_filename, debug=False):
392
GuiPitivi.__init__(self, debug)
393
self._loadProject(project_filename)
395
def _createGui(self):
396
self.viewer = PitiviViewer(self)
397
window = gtk.Window()
398
window.connect("delete-event", self._deleteCb)
399
window.add(self.viewer)
402
def _deleteCb(self, unused_widget, unused_data):
405
def _eosCb(self, unused_obj):
408
def _newProjectLoaded(self, project):
409
# create previewer and set ui
410
previewer = Previewer(project, ui=self.viewer)
411
self._setActioner(previewer)
412
# hack to make the gtk.HScale seek slider UI behave properly
413
self.viewer._durationChangedCb(None, project.timeline.duration)
416
class RenderingNoGuiPitivi(InteractivePitivi):
418
Creates an instance of PiTiVi with no UI which aims
419
at rendering the @project_filename project in @output_filename file
422
def __init__(self, project_filename, output_filename, debug=False):
423
InteractivePitivi.__init__(self, debug)
424
self.outfile = "file://%s" % os.path.abspath(output_filename)
425
print _("Loading project...")
426
self._loadProject(project_filename)
428
def _eosCb(self, unused_obj):
431
def _newProjectLoaded(self, project):
432
# create renderer and set output file
433
renderer = Renderer(project, outfile=self.outfile)
434
print _("Project loaded.")
435
print _("Rendering...")
436
self._setActioner(renderer)
439
if Pitivi.shutdown(self):
445
373
def _parse_options(argv):
446
374
parser = OptionParser(
448
376
%prog [PROJECT_FILE] # Start the video editor.
449
%prog -i [-a] [MEDIA_FILE1 ...] # Start the editor and create a project.
450
%prog PROJECT_FILE -r OUTPUT_FILE # Render a project.
451
%prog PROJECT_FILE -p # Preview a project."""))
377
%prog -i [-a] [MEDIA_FILE1 ...] # Start the editor and create a project."""))
453
379
parser.add_option("-i", "--import", dest="import_sources",
454
380
action="store_true", default=False,