~ubuntu-branches/ubuntu/lucid/pitivi/lucid

« back to all changes in this revision

Viewing changes to pitivi/application.py

  • Committer: Bazaar Package Importer
  • Author(s): Sebastian Dröge
  • Date: 2009-05-27 14:22:49 UTC
  • mfrom: (1.2.1 upstream) (3.1.13 experimental)
  • Revision ID: james.westby@ubuntu.com-20090527142249-tj0qnkc37320ylml
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# PiTiVi , Non-linear video editor
 
2
#
 
3
#       pitivi/pitivi.py
 
4
#
 
5
# Copyright (c) 2005, Edward Hervey <bilboed@bilboed.com>
 
6
#
 
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.
 
11
#
 
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.
 
16
#
 
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.
 
21
 
 
22
"""
 
23
Main application
 
24
"""
 
25
import os
 
26
import gobject
 
27
gobject.threads_init()
 
28
 
 
29
from pitivi.pitivigstutils import patch_gst_python
 
30
patch_gst_python()
 
31
 
 
32
from gettext import gettext as _
 
33
 
 
34
import pitivi.instance as instance
 
35
 
 
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
 
49
 
 
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
 
56
 
 
57
# FIXME : maybe we should have subclasses for UI and CLI
 
58
 
 
59
class Pitivi(Loggable, Signallable):
 
60
    """
 
61
    Pitivi's main application class.
 
62
 
 
63
    Signals:
 
64
     - C{new-project} : A new C{Project} is loaded and ready to use.
 
65
 
 
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.`
 
77
 
 
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}.
 
84
    """
 
85
 
 
86
    __signals__ = {
 
87
        "new-project" : ["project"],
 
88
 
 
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"],
 
95
        "shutdown" : None
 
96
        }
 
97
 
 
98
    def __init__(self):
 
99
        """
 
100
        initialize pitivi with the command line arguments
 
101
        """
 
102
        Loggable.__init__(self)
 
103
 
 
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)
 
107
 
 
108
        self.info('starting up')
 
109
 
 
110
        # store ourself in the instance global
 
111
        if instance.PiTiVi:
 
112
            raise RuntimeWarning(
 
113
                _("There is already a %s instance, please inform developers by filing a bug at http://bugzilla.gnome.org/")
 
114
                % APPNAME)
 
115
        instance.PiTiVi = self
 
116
 
 
117
        self.projects = []
 
118
        self.current = None
 
119
 
 
120
        # get settings
 
121
        self.settings = GlobalSettings()
 
122
        self.threads = ThreadMaster()
 
123
        #self.screencast = False
 
124
 
 
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()
 
131
 
 
132
    #{ Project-related methods
 
133
 
 
134
    def addProject(self, project=None, uri=None):
 
135
        """ Add the given L{Project} to the list of projects controlled
 
136
        by the application.
 
137
 
 
138
        If no project is given, then the application will attempt to load
 
139
        the project contained at the given C{URI}.
 
140
 
 
141
        The 'C{new-project}' signal will be emitted if the project is properly
 
142
        added.
 
143
 
 
144
        @arg project: The project to add.
 
145
        @type project: L{Project}
 
146
        @arg uri: The location of the project to load.
 
147
        @type uri: C{URI}
 
148
        """
 
149
        if project == None and uri == None:
 
150
            raise Exception("No project or URI given")
 
151
        if uri != None:
 
152
            if project != None:
 
153
                raise Exception("Only provide either a project OR a URI")
 
154
            project = load_project(uri)
 
155
 
 
156
        if project in self.projects:
 
157
            raise Exception("Project already controlled")
 
158
        self.projects.append(project)
 
159
        self.emit("new-project", project)
 
160
 
 
161
    ## old implementations
 
162
 
 
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."),
 
168
                uri)
 
169
            return
 
170
        if filepath:
 
171
            if not os.path.exists(filepath):
 
172
                self.emit("new-project-failed",
 
173
                          _("File does not exist"), filepath)
 
174
                return
 
175
            uri = "file://" + filepath
 
176
        # is the given filepath a valid pitivi project
 
177
        formatter = get_formatter_for_uri(uri)
 
178
        if not formatter:
 
179
            self.emit("new-project-failed", _("Not a valid project file."),
 
180
                uri)
 
181
            return
 
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)
 
188
            try:
 
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")
 
195
                self.current = None
 
196
                self.emit("new-project-failed",
 
197
                    _("There was an error loading the file."), uri)
 
198
            finally:
 
199
                formatter.disconnect_by_function(self._missingURICb)
 
200
 
 
201
    def _missingURICb(self, formatter, uri):
 
202
        self.emit("missing-uri", formatter, uri)
 
203
 
 
204
    def _closeRunningProject(self):
 
205
        """ close the current project """
 
206
        self.info("closing running project")
 
207
        if self.current:
 
208
            if self.current.hasUnsavedModifications():
 
209
                if not self.current.save():
 
210
                    return False
 
211
            if self.emit("closing-project", self.current) == False:
 
212
                return False
 
213
            self.emit("project-closed", self.current)
 
214
            self.current.release()
 
215
            self.current = None
 
216
        return True
 
217
 
 
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
 
225
 
 
226
            from pitivi.stream import AudioStream, VideoStream
 
227
            import gst
 
228
            from pitivi.timeline.track import Track
 
229
 
 
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'))
 
233
            track = Track(video)
 
234
            project.timeline.addTrack(track)
 
235
            audio = AudioStream(gst.Caps('audio/x-raw-int; audio/x-raw-float'))
 
236
            track = Track(audio)
 
237
            project.timeline.addTrack(track)
 
238
 
 
239
            self.emit("new-project-loaded", self.current)
 
240
 
 
241
    #{ Shutdown methods
 
242
 
 
243
    def shutdown(self):
 
244
        """
 
245
        Close PiTiVi.
 
246
 
 
247
        @return: C{True} if PiTiVi was successfully closed, else C{False}.
 
248
        @rtype: C{bool}
 
249
        """
 
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")
 
255
            return False
 
256
        self.threads.stopAllThreads()
 
257
        self.settings.storeSettings()
 
258
        self.deviceprobe.release()
 
259
        self.deviceprobe = None
 
260
        self.current = None
 
261
        instance.PiTiVi = None
 
262
        self.emit("shutdown")
 
263
        return True
 
264
 
 
265
    #}
 
266
 
 
267
 
 
268
class InteractivePitivi(Pitivi):
 
269
    """ Class for PiTiVi instances that provide user interaction """
 
270
 
 
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
 
277
 
 
278
        self._gui = PitiviMainWindow(self)
 
279
        self._gui.show()
 
280
 
 
281
        if not import_sources and sources:
 
282
            project = sources[0]
 
283
            self.loadProject(filepath=project)
 
284
        else:
 
285
            uris = ["file://" + os.path.abspath(path) for path in sources]
 
286
            if add_to_timeline:
 
287
                self._uris = uris
 
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)
 
292
 
 
293
    def _addSourceCb(self, unused_sourcelist, factory):
 
294
        if factory.name in self._uris:
 
295
            self._uris.remove(factory.name)
 
296
            if not self._uris:
 
297
                self.current.sources.disconnect_by_function(self._addSourceCb)
 
298
 
 
299
            t = self.current.timeline.addSourceFactory(factory)
 
300
            t.start = self._duration
 
301
            self._duration += t.duration
 
302
 
 
303
    def _discoveryErrorCb(self, sourcelist, uri, error, debug):
 
304
        if uri in self._uris:
 
305
            self._uris.remove(uri)
 
306
            if not self._uris:
 
307
                self.current.sources.disconnect_by_function(self._discoveryErrorCb)
 
308
 
 
309
    # properties
 
310
 
 
311
    def _get_mainloop(self):
 
312
        return self._mainloop
 
313
 
 
314
    def _set_mainloop(self, mainloop):
 
315
        if self._mainloop != None:
 
316
            raise Exception("Mainloop already set !")
 
317
        if mainloop == None:
 
318
            mainloop = gobject.MainLoop()
 
319
        self._mainloop = mainloop
 
320
    mainloop = property(_get_mainloop, _set_mainloop,
 
321
                        doc="The MainLoop running the program")
 
322
 
 
323
    @property
 
324
    def gui(self):
 
325
        """The user interface"""
 
326
        return self._gui
 
327
 
 
328
    # PiTiVi method overrides
 
329
    def shutdown(self):
 
330
        if Pitivi.shutdown(self):
 
331
            if self.mainloop:
 
332
                self.mainloop.quit()
 
333
            return True
 
334
        return False
 
335
 
 
336
    def run(self):
 
337
        if self.mainloop:
 
338
            self.mainloop.run()
 
339
 
 
340
usage = _("""
 
341
  %prog [PROJECT_FILE]
 
342
  %prog -i [-a] [MEDIA_FILE]...""")
 
343
 
 
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.""")
 
349
 
 
350
import_help = _("""Import each MEDIA_FILE into the project.""")
 
351
 
 
352
add_help = _("""Add each MEDIA_FILE to timeline after importing.""")
 
353
 
 
354
def main(argv):
 
355
    """ Start PiTiVi ! """
 
356
    from optparse import OptionParser
 
357
    initial_checks()
 
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)
 
367
    ptv.run()