1
# PiTiVi , Non-linear video editor
5
# Copyright (c) 2005, Edward Hervey <bilboed@bilboed.com>
7
# This program is free software; you can redistribute it and/or
8
# modify it under the terms of the GNU Lesser General Public
9
# License as published by the Free Software Foundation; either
10
# version 2.1 of the License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
# Lesser General Public License for more details.
17
# You should have received a copy of the GNU Lesser General Public
18
# License along with this program; if not, write to the
19
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20
# Boston, MA 02111-1307, USA.
27
gobject.threads_init()
29
from pitivi.pitivigstutils import patch_gst_python
32
from gettext import gettext as _
34
import pitivi.instance as instance
36
from pitivi.check import initial_checks
37
from pitivi.device import get_probe
38
from pitivi.effects import Magician
39
from pitivi.configure import APPNAME
40
from pitivi.settings import GlobalSettings
41
from pitivi.threads import ThreadMaster
42
from pitivi.pluginmanager import PluginManager
43
from pitivi.signalinterface import Signallable
44
from pitivi.log.loggable import Loggable
45
from pitivi.log import log
46
from pitivi.project import Project
47
from pitivi.formatters.format import get_formatter_for_uri
48
from pitivi.formatters.base import FormatterError
50
# FIXME : Speedup loading time
51
# Currently we load everything in one go
52
# It would be better if a minimalistic UI could start up ASAP, without loading
53
# anything gst-related or that could slow down startup.
54
# AND THEN load up the required parts.
55
# This will result in a much better end-user experience
57
# FIXME : maybe we should have subclasses for UI and CLI
59
class Pitivi(Loggable, Signallable):
61
Pitivi's main application class.
64
- C{new-project} : A new C{Project} is loaded and ready to use.
66
- C{new-project-loading} : Pitivi is attempting to load a new project.
67
- C{new-project-loaded} : A new L{Project} has been loaded, and the UI should refresh it's view.
68
- C{new-project-failed} : A new L{Project} failed to load.
69
- C{closing-project} : pitivi would like to close a project. handlers should return false
70
if they do not want this project to close. by default, assumes
71
true. This signal should only be used by classes that might want to abort
72
the closing of a project.
73
- C{project-closed} : The project is closed, it will be freed when the callback returns.
74
Classes should connect to this instance when they want to know that
75
data related to that project is no longer going to be used.
76
- C{shutdown} : Used internally, do not use this signal.`
78
@ivar settings: Application-wide settings.
79
@type settings: L{GlobalSettings}.
80
@ivar projects: List of used projects
81
@type projects: List of L{Project}.
82
@ivar current: Currently used project.
83
@type current: L{Project}.
87
"new-project" : ["project"],
89
"new-project-loading" : ["project"],
90
"new-project-loaded" : ["project"],
91
"closing-project" : ["project"],
92
"project-closed" : ["project"],
93
"new-project-failed" : ["reason", "uri"],
94
"missing-uri" : ["formatter", "uri"],
100
initialize pitivi with the command line arguments
102
Loggable.__init__(self)
104
# init logging as early as possible so we can log startup code
105
enable_color = os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '0')
106
log.init('PITIVI_DEBUG', enable_color)
108
self.info('starting up')
110
# store ourself in the instance global
112
raise RuntimeWarning(
113
_("There is already a %s instance, please inform developers by filing a bug at http://bugzilla.gnome.org/")
115
instance.PiTiVi = self
121
self.settings = GlobalSettings()
122
self.threads = ThreadMaster()
123
#self.screencast = False
125
self.plugin_manager = PluginManager(
126
self.settings.get_local_plugin_path(),
127
self.settings.get_plugin_settings_path())
128
self.effects = Magician()
129
self.deviceprobe = get_probe()
130
self.newBlankProject()
132
#{ Project-related methods
134
def addProject(self, project=None, uri=None):
135
""" Add the given L{Project} to the list of projects controlled
138
If no project is given, then the application will attempt to load
139
the project contained at the given C{URI}.
141
The 'C{new-project}' signal will be emitted if the project is properly
144
@arg project: The project to add.
145
@type project: L{Project}
146
@arg uri: The location of the project to load.
149
if project == None and uri == None:
150
raise Exception("No project or URI given")
153
raise Exception("Only provide either a project OR a URI")
154
project = load_project(uri)
156
if project in self.projects:
157
raise Exception("Project already controlled")
158
self.projects.append(project)
159
self.emit("new-project", project)
161
## old implementations
163
def loadProject(self, uri=None, filepath=None):
164
""" Load the given file through it's uri or filepath """
165
self.info("uri:%s, filepath:%s", uri, filepath)
166
if not uri and not filepath:
167
self.emit("new-project-failed", _("No location given."),
171
if not os.path.exists(filepath):
172
self.emit("new-project-failed",
173
_("File does not exist"), filepath)
175
uri = "file://" + filepath
176
# is the given filepath a valid pitivi project
177
formatter = get_formatter_for_uri(uri)
179
self.emit("new-project-failed", _("Not a valid project file."),
182
# if current project, try to close it
183
if self._closeRunningProject():
184
project = formatter.newProject()
185
formatter.connect("missing-uri", self._missingURICb)
186
self.emit("new-project-loading", project)
187
self.info("Got a new project %r, calling loadProject", project)
189
formatter.loadProject(uri, project)
190
self.current = project
191
self.emit("new-project-loaded", self.current)
192
except FormatterError, e:
193
self.handleException(e)
194
self.warning("error loading the project")
196
self.emit("new-project-failed",
197
_("There was an error loading the file."), uri)
199
formatter.disconnect_by_function(self._missingURICb)
201
def _missingURICb(self, formatter, uri):
202
self.emit("missing-uri", formatter, uri)
204
def _closeRunningProject(self):
205
""" close the current project """
206
self.info("closing running project")
208
if self.current.hasUnsavedModifications():
209
if not self.current.save():
211
if self.emit("closing-project", self.current) == False:
213
self.emit("project-closed", self.current)
214
self.current.release()
218
def newBlankProject(self):
219
""" start up a new blank project """
220
# if there's a running project we must close it
221
if self._closeRunningProject():
222
project = Project(_("New Project"))
223
self.emit("new-project-loading", project)
224
self.current = project
226
from pitivi.stream import AudioStream, VideoStream
228
from pitivi.timeline.track import Track
230
# FIXME: this should not be hard-coded
231
# add default tracks for a new project
232
video = VideoStream(gst.Caps('video/x-raw-rgb; video/x-raw-yuv'))
234
project.timeline.addTrack(track)
235
audio = AudioStream(gst.Caps('audio/x-raw-int; audio/x-raw-float'))
237
project.timeline.addTrack(track)
239
self.emit("new-project-loaded", self.current)
247
@return: C{True} if PiTiVi was successfully closed, else C{False}.
250
self.debug("shutting down")
251
# we refuse to close if we're running a user interface and the user
252
# doesn't want us to close the current project.
253
if not self._closeRunningProject():
254
self.warning("Not closing since running project doesn't want to close")
256
self.threads.stopAllThreads()
257
self.settings.storeSettings()
258
self.deviceprobe.release()
259
self.deviceprobe = None
261
instance.PiTiVi = None
262
self.emit("shutdown")
268
class InteractivePitivi(Pitivi):
269
""" Class for PiTiVi instances that provide user interaction """
271
def __init__(self, sources=[], import_sources=False,
272
add_to_timeline=False, mainloop=None, *args, **kwargs):
273
from pitivi.ui.mainwindow import PitiviMainWindow
274
Pitivi.__init__(self, *args, **kwargs)
275
self._mainloop = None
276
self.mainloop = mainloop
278
self._gui = PitiviMainWindow(self)
281
if not import_sources and sources:
283
self.loadProject(filepath=project)
285
uris = ["file://" + os.path.abspath(path) for path in sources]
288
self._duration = self.current.timeline.duration
289
self.current.sources.connect("file_added", self._addSourceCb)
290
self.current.sources.connect("discovery-error", self._discoveryErrorCb)
291
self.current.sources.addUris(uris)
293
def _addSourceCb(self, unused_sourcelist, factory):
294
if factory.name in self._uris:
295
self._uris.remove(factory.name)
297
self.current.sources.disconnect_by_function(self._addSourceCb)
299
t = self.current.timeline.addSourceFactory(factory)
300
t.start = self._duration
301
self._duration += t.duration
303
def _discoveryErrorCb(self, sourcelist, uri, error, debug):
304
if uri in self._uris:
305
self._uris.remove(uri)
307
self.current.sources.disconnect_by_function(self._discoveryErrorCb)
311
def _get_mainloop(self):
312
return self._mainloop
314
def _set_mainloop(self, mainloop):
315
if self._mainloop != None:
316
raise Exception("Mainloop already set !")
318
mainloop = gobject.MainLoop()
319
self._mainloop = mainloop
320
mainloop = property(_get_mainloop, _set_mainloop,
321
doc="The MainLoop running the program")
325
"""The user interface"""
328
# PiTiVi method overrides
330
if Pitivi.shutdown(self):
342
%prog -i [-a] [MEDIA_FILE]...""")
344
description = _("""Starts the video editor, optionally loading PROJECT_FILE. If
345
no project is given, %prog creates a new project.
346
Alternatively, when -i is specified, arguments are treated as clips to be
347
imported into the project. If -a is specified, these clips will also be added to
348
the end of the project timeline.""")
350
import_help = _("""Import each MEDIA_FILE into the project.""")
352
add_help = _("""Add each MEDIA_FILE to timeline after importing.""")
355
""" Start PiTiVi ! """
356
from optparse import OptionParser
358
parser = OptionParser(usage, description=description)
359
parser.add_option("-i", "--import", help=import_help,
360
dest="import_sources", action="store_true", default=False)
361
parser.add_option("-a", "--add-to-timeline", help=add_help,
362
action="store_true", default=False)
363
options, args = parser.parse_args(argv)
364
ptv = InteractivePitivi(sources=args[1:],
365
import_sources=options.import_sources,
366
add_to_timeline=options.add_to_timeline)