1
# PiTiVi , Non-linear video editor
5
# Copyright (c) 2005-2009 Edward Hervey <bilboed@bilboed.com>
6
# Copyright (c) 2008-2009 Alessandro Decina <alessandro.d@gmail.com>
8
# This program is free software; you can redistribute it and/or
9
# modify it under the terms of the GNU Lesser General Public
10
# License as published by the Free Software Foundation; either
11
# version 2.1 of the License, or (at your option) any later version.
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
# Lesser General Public License for more details.
18
# You should have received a copy of the GNU Lesser General Public
19
# License along with this program; if not, write to the
20
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21
# Boston, MA 02111-1307, USA.
27
gobject.threads_init()
29
from optparse import OptionParser
34
from pitivi.pitivigstutils import patch_gst_python
37
from gettext import gettext as _
39
import pitivi.instance as instance
41
from pitivi.check import initial_checks
42
from pitivi.device import get_probe
43
from pitivi.effects import EffectsHandler
44
from pitivi.configure import APPNAME
45
from pitivi.settings import GlobalSettings
46
from pitivi.threads import ThreadMaster
47
from pitivi.pluginmanager import PluginManager
48
from pitivi.signalinterface import Signallable
49
from pitivi.log.loggable import Loggable
50
from pitivi.log import log
51
from pitivi.ui.mainwindow import PitiviMainWindow
52
from pitivi.projectmanager import ProjectManager, ProjectLogObserver
53
from pitivi.undo import UndoableActionLog, DebugActionLogObserver
54
from pitivi.timeline.timeline_undo import TimelineLogObserver
55
from pitivi.sourcelist_undo import SourceListLogObserver
56
from pitivi.undo import UndoableAction
57
from pitivi.ui.viewer import PitiviViewer
58
from pitivi.actioner import Renderer, Previewer
60
# FIXME : Speedup loading time
61
# Currently we load everything in one go
62
# It would be better if a minimalistic UI could start up ASAP, without loading
63
# anything gst-related or that could slow down startup.
64
# AND THEN load up the required parts.
65
# This will result in a much better end-user experience
67
# FIXME : maybe we should have subclasses for UI and CLI
69
class Pitivi(Loggable, Signallable):
71
Pitivi's main application class.
74
- C{new-project} : A new C{Project} is loaded and ready to use.
76
- C{new-project-loading} : Pitivi is attempting to load a new project.
77
- C{new-project-loaded} : A new L{Project} has been loaded, and the UI should refresh it's view.
78
- C{new-project-failed} : A new L{Project} failed to load.
79
- C{closing-project} : pitivi would like to close a project. handlers should return false
80
if they do not want this project to close. by default, assumes
81
true. This signal should only be used by classes that might want to abort
82
the closing of a project.
83
- C{project-closed} : The project is closed, it will be freed when the callback returns.
84
Classes should connect to this instance when they want to know that
85
data related to that project is no longer going to be used.
86
- C{shutdown} : Used internally, do not use this signal.`
88
@ivar settings: Application-wide settings.
89
@type settings: L{GlobalSettings}.
90
@ivar projects: List of used projects
91
@type projects: List of L{Project}.
92
@ivar current: Currently used project.
93
@type current: L{Project}.
97
"new-project" : ["project"],
99
"new-project-loading" : ["uri"],
100
"new-project-created" : ["project"],
101
"new-project-loaded" : ["project"],
102
"new-project-failed" : ["uri", "exception"],
103
"closing-project" : ["project"],
104
"project-closed" : ["project"],
105
"missing-uri" : ["formatter", "uri"],
111
initialize pitivi with the command line arguments
113
Loggable.__init__(self)
115
# init logging as early as possible so we can log startup code
116
enable_color = os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '0')
117
log.init('PITIVI_DEBUG', enable_color)
119
self.info('starting up')
121
# store ourself in the instance global
123
raise RuntimeWarning(
124
_("There is already a %s instance, please inform the developers by filing a bug at http://bugzilla.gnome.org/enter_bug.cgi?product=pitivi")
126
instance.PiTiVi = self
132
self.settings = GlobalSettings()
133
self.threads = ThreadMaster()
134
#self.screencast = False
136
self.plugin_manager = PluginManager(
137
self.settings.get_local_plugin_path(),
138
self.settings.get_plugin_settings_path())
139
self.effects = EffectsHandler()
140
self.deviceprobe = get_probe()
142
self.projectManager = ProjectManager(self.effects)
143
self._connectToProjectManager(self.projectManager)
145
self.action_log = UndoableActionLog()
146
self.debug_action_log_observer = DebugActionLogObserver()
147
self.debug_action_log_observer.startObserving(self.action_log)
148
self.timelineLogObserver = TimelineLogObserver(self.action_log)
149
self.projectLogObserver = ProjectLogObserver(self.action_log)
150
self.sourcelist_log_observer = SourceListLogObserver(self.action_log)
158
@return: C{True} if PiTiVi was successfully closed, else C{False}.
161
self.debug("shutting down")
162
# we refuse to close if we're running a user interface and the user
163
# doesn't want us to close the current project.
164
if self.projectManager.current and not self.projectManager.closeRunningProject():
165
self.warning("Not closing since running project doesn't want to close")
167
self.threads.stopAllThreads()
168
self.settings.storeSettings()
170
self.deviceprobe.release()
171
self.deviceprobe = None
173
instance.PiTiVi = None
174
self.emit("shutdown")
179
def _connectToProjectManager(self, projectManager):
180
projectManager.connect("new-project-loading",
181
self._projectManagerNewProjectLoading)
182
projectManager.connect("new-project-created",
183
self._projectManagerNewProjectCreated)
184
projectManager.connect("new-project-loaded",
185
self._projectManagerNewProjectLoaded)
186
projectManager.connect("new-project-failed",
187
self._projectManagerNewProjectFailed)
188
projectManager.connect("closing-project",
189
self._projectManagerClosingProject)
190
projectManager.connect("project-closed",
191
self._projectManagerProjectClosed)
193
def _projectManagerNewProjectLoading(self, projectManager, uri):
194
self.emit("new-project-loading", uri)
196
def _projectManagerNewProjectCreated(self, projectManager, project):
197
self.current = project
198
self.emit("new-project-created", project)
200
def _newProjectLoaded(self, project):
203
def _projectManagerNewProjectLoaded(self, projectManager, project):
204
self.current = project
205
self.action_log.clean()
206
self.timelineLogObserver.startObserving(project.timeline)
207
self.projectLogObserver.startObserving(project)
208
self.sourcelist_log_observer.startObserving(project.sources)
209
self._newProjectLoaded(project)
210
self.emit("new-project-loaded", project)
212
def _projectManagerNewProjectFailed(self, projectManager, uri, exception):
213
self.emit("new-project-failed", uri, exception)
215
def _projectManagerClosingProject(self, projectManager, project):
216
return self.emit("closing-project", project)
218
def _projectManagerProjectClosed(self, projectManager, project):
219
self.timelineLogObserver.stopObserving(project.timeline)
220
self.projectLogObserver.stopObserving(project)
222
self.emit("project-closed", project)
224
class InteractivePitivi(Pitivi):
226
%prog [-r OUTPUT_FILE] [PROJECT_FILE]
227
%prog -p [PROJECT_FILE]
228
%prog -i [-a] [MEDIA_FILE]...""")
230
description = _("""Starts the video editor, optionally loading PROJECT_FILE. If
231
no project is given, %prog creates a new project.
232
Alternatively, when -i is specified, arguments are treated as clips to be
233
imported into the project. If -a is specified, these clips will also be added to
234
the end of the project timeline.
235
When -r is specified, the given project file is rendered without opening the GUI.""")
237
import_help = _("""Import each MEDIA_FILE into the project.""")
239
add_help = _("""Add each MEDIA_FILE to timeline after importing.""")
240
debug_help = _("""Run pitivi in the Python Debugger""")
242
no_ui_help = _("""Run pitivi with no gui""")
243
render_help = _("""Render the given project file to OUTPUT_FILE with no GUI.""")
244
preview_help = _("""Preview the given project file without the full UI.""")
247
Pitivi.__init__(self)
248
self.mainloop = gobject.MainLoop()
251
def _newProjectLoaded(self, project):
252
if self.render_output:
253
# create renderer and set output file
254
self.actioner = Renderer(self.current, pipeline=None, outfile=self.output_file)
256
# create previewer and set ui
257
self.actioner = Previewer(self.current, pipeline=None, ui=self.gui)
258
# hack to make the gtk.HScale seek slider UI behave properly
259
self.gui._durationChangedCb(None, project.timeline.duration)
261
self.actioner.connect("eos", self._eosCb)
262
# on error, all we need to do is shutdown which is the same as we do for EOS
263
self.actioner.connect("error", self._eosCb)
264
# configure the actioner and start acting!
265
self.actioner.startAction()
268
# check for dependencies
269
if not self._checkDependencies():
272
# parse cmdline options
273
parser = self._createOptionParser()
274
options, args = parser.parse_args(argv)
276
# if we aren't importing sources then n_args should be at most
277
# 1 + parameters that take individual arguments
281
sys.excepthook = self._excepthook
284
self.render_output = options.render_output
285
self.preview = options.preview
286
if options.render_output:
290
if options.render_output and options.preview:
291
parser.error("-p and -r cannot be used simultaneously")
294
if options.import_sources and (options.render_output or options.preview):
295
parser.error("-r or -p and -i are incompatible")
298
if not options.import_sources and options.add_to_timeline:
299
parser.error("-a requires -i")
302
if not options.import_sources and ((options.render_output and len(args) != 2)
303
or len(args) > n_args):
304
parser.error("invalid arguments")
309
elif options.preview:
310
# init ui for previewing
311
self.gui = PitiviViewer()
312
self.window = gtk.Window()
313
self.window.connect("delete-event", self._deleteCb)
314
self.window.add(self.gui)
315
self.window.show_all()
318
self.gui = PitiviMainWindow(self)
321
if not options.import_sources and args:
323
if options.render_output:
324
self.output_file = "file://%s" % os.path.abspath(args[index])
326
# load a project file
327
project = "file://%s" % os.path.abspath(args[index])
328
self.projectManager.loadProject(project)
330
# load the passed filenames, optionally adding them to the timeline
331
# (useful during development)
332
self.projectManager.newBlankProject()
333
uris = ["file://" + urllib.quote(os.path.abspath(path)) for path in args]
334
self.current.sources.connect("source-added",
335
self._sourceAddedCb, uris, options.add_to_timeline)
336
self.current.sources.connect("discovery-error",
337
self._discoveryErrorCb, uris)
338
self.current.sources.addUris(uris)
343
def _deleteCb(self, unused_widget, unused_data):
346
def _eosCb(self, unused_obj):
349
elif self.window is not None:
353
if Pitivi.shutdown(self):
361
def _createOptionParser(self):
362
parser = OptionParser(self.usage, description=self.description)
363
parser.add_option("-i", "--import", help=self.import_help,
364
dest="import_sources", action="store_true", default=False)
365
parser.add_option("-a", "--add-to-timeline", help=self.add_help,
366
action="store_true", default=False)
367
parser.add_option("-d", "--debug", help=self.debug_help,
368
action="store_true", default=False)
369
parser.add_option("-n", "--no-ui", help=self.no_ui_help,
370
action="store_true", default=False)
371
parser.add_option("-r", "--render", help=self.render_help,
372
dest="render_output", action="store_true", default=False)
373
parser.add_option("-p", "--preview", help=self.preview_help,
374
action="store_true", default=False)
379
def _checkDependencies(self):
380
missing_deps = initial_checks()
382
message, detail = missing_deps
383
dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
384
buttons=gtk.BUTTONS_OK)
385
dialog.set_icon_name("pitivi")
386
dialog.set_markup("<b>"+message+"</b>")
387
dialog.format_secondary_text(detail)
394
def _sourceAddedCb(self, sourcelist, factory,
395
startup_uris, add_to_timeline):
396
if self._maybePopStartupUri(startup_uris, factory.uri) \
398
self.action_log.begin("add clip")
399
self.current.timeline.addSourceFactory(factory)
400
self.action_log.commit()
402
def _discoveryErrorCb(self, sourcelist, uri, error, debug, startup_uris):
403
self._maybePopStartupUri(startup_uris, uri)
405
def _maybePopStartupUri(self, startup_uris, uri):
407
startup_uris.remove(uri)
409
# uri is not a startup uri. This can happen if the user starts
410
# importing sources while sources specified at startup are still
411
# being processed. In practice this will never happen.
415
self.current.sources.disconnect_by_function(self._sourceAddedCb)
416
self.current.sources.disconnect_by_function(self._discoveryErrorCb)
420
def _excepthook(self, exc_type, value, tback):
423
traceback.print_tb(tback)
424
pdb.post_mortem(tback)
427
ptv = InteractivePitivi()
428
ptv.run(sys.argv[1:])