[pitivi: 4/14] application: render: Initial implementation of command line rendering



commit 26189d22846167b3de31de6ebf86629ef6d26508
Author: Robert Swain <robert swain collabora co uk>
Date:   Thu Aug 19 18:16:49 2010 +0200

    application: render: Initial implementation of command line rendering
    
    Abstract rendering code from encoding dialogue into a Renderer convenience
    class. Make the encoding dialogue use this class. Add --render option to
    command line and implement rendering of a given project file from the command
    line.

 pitivi/application.py       |   47 +++++++++++-
 pitivi/render.py            |  166 +++++++++++++++++++++++++++++++++++++++++++
 pitivi/ui/encodingdialog.py |  112 +++--------------------------
 3 files changed, 219 insertions(+), 106 deletions(-)
---
diff --git a/pitivi/application.py b/pitivi/application.py
index d9ff7c1..0cb482d 100644
--- a/pitivi/application.py
+++ b/pitivi/application.py
@@ -54,6 +54,7 @@ from pitivi.undo import UndoableActionLog, DebugActionLogObserver
 from pitivi.timeline.timeline_undo import TimelineLogObserver
 from pitivi.sourcelist_undo import SourceListLogObserver
 from pitivi.undo import UndoableAction
+from pitivi.render import Renderer
 
 # FIXME : Speedup loading time
 # Currently we load everything in one go
@@ -195,12 +196,16 @@ class Pitivi(Loggable, Signallable):
         self.current = project
         self.emit("new-project-created", project)
 
+    def _newProjectLoaded(self, project):
+        pass
+
     def _projectManagerNewProjectLoaded(self, projectManager, project):
         self.current = project
         self.action_log.clean()
         self.timelineLogObserver.startObserving(project.timeline)
         self.projectLogObserver.startObserving(project)
         self.sourcelist_log_observer.startObserving(project.sources)
+        self._newProjectLoaded(project)
         self.emit("new-project-loaded", project)
 
     def _projectManagerNewProjectFailed(self, projectManager, uri, exception):
@@ -217,14 +222,15 @@ class Pitivi(Loggable, Signallable):
 
 class InteractivePitivi(Pitivi):
     usage = _("""
-      %prog [PROJECT_FILE]
+      %prog [-r OUTPUT_FILE] [PROJECT_FILE]
       %prog -i [-a] [MEDIA_FILE]...""")
 
     description = _("""Starts the video editor, optionally loading PROJECT_FILE. If
 no project is given, %prog creates a new project.
 Alternatively, when -i is specified, arguments are treated as clips to be
 imported into the project. If -a is specified, these clips will also be added to
-the end of the project timeline.""")
+the end of the project timeline.
+When -r is specified, the given project file is rendered without opening the GUI.""")
 
     import_help = _("""Import each MEDIA_FILE into the project.""")
 
@@ -232,11 +238,20 @@ the end of the project timeline.""")
     debug_help = _("""Run pitivi in the Python Debugger""")
 
     no_ui_help = _("""Run pitivi with no gui""")
+    render_help = _("""Render the given project file to OUTPUT_FILE with no GUI.""")
 
     def __init__(self):
         Pitivi.__init__(self)
         self.mainloop = gobject.MainLoop()
 
+    def _newProjectLoaded(self, project):
+        if self.render_output:
+            # create renderer and set output file
+            self.renderer = Renderer(self.current, pipeline=None, outfile=self.output_file)
+            self.renderer.connect("eos", self._eosCb)
+            # configure the renderer and start rendering!
+            self.renderer.startRender()
+
     def run(self, argv):
         # check for dependencies
         if not self._checkDependencies():
@@ -246,10 +261,19 @@ the end of the project timeline.""")
         parser = self._createOptionParser()
         options, args = parser.parse_args(argv)
 
+        # if we aren't importing sources then n_args should be at most
+        # 1 + parameters that take individual arguments
+        n_args = 1
+
         if options.debug:
             sys.excepthook = self._excepthook
 
         # validate options
+        self.render_output = options.render_output
+        if options.render_output:
+            options.no_ui = True
+            n_args += 1
+
         if options.no_ui:
             self.gui = None
         else:
@@ -257,17 +281,26 @@ the end of the project timeline.""")
             self.gui = PitiviMainWindow(self)
             self.gui.show()
 
+        if options.import_sources and options.render_output:
+            parser.error("-r and -i are incompatible")
+            return
+
         if not options.import_sources and options.add_to_timeline:
             parser.error("-a requires -i")
             return
 
-        if not options.import_sources and len(args) > 1:
+        if not options.import_sources and ((options.render_output and len(args) != 2)
+                    or len(args) > n_args):
             parser.error("invalid arguments")
             return
 
         if not options.import_sources and args:
+            index = 0
+            if options.render_output:
+                self.output_file = "file://%s" % os.path.abspath(args[index])
+                index += 1
             # load a project file
-            project = "file://%s" % os.path.abspath(args[0])
+            project = "file://%s" % os.path.abspath(args[index])
             self.projectManager.loadProject(project)
         else:
             # load the passed filenames, optionally adding them to the timeline
@@ -283,6 +316,10 @@ the end of the project timeline.""")
         # run the mainloop
         self.mainloop.run()
 
+    def _eosCb(self, unused_obj):
+        if self.gui is None:
+            self.shutdown()
+
     def shutdown(self):
         if Pitivi.shutdown(self):
             if self.gui:
@@ -302,6 +339,8 @@ the end of the project timeline.""")
                 action="store_true", default=False)
         parser.add_option("-n", "--no-ui", help=self.no_ui_help,
                 action="store_true", default=False)
+        parser.add_option("-r", "--render", help=self.render_help,
+                dest="render_output", action="store_true", default=False)
 
         return parser
 
diff --git a/pitivi/render.py b/pitivi/render.py
new file mode 100644
index 0000000..468eb5f
--- /dev/null
+++ b/pitivi/render.py
@@ -0,0 +1,166 @@
+# PiTiVi , Non-linear video editor
+#
+#       render.py
+#
+# Copyright (c) 2005, Edward Hervey <bilboed bilboed com>
+# Copyright (c) 2010, Robert Swain <rob opendot cl>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+"""
+Rendering helpers
+"""
+
+import time
+import gst
+
+from pitivi.signalinterface import Signallable
+from pitivi.log.loggable import Loggable
+from pitivi.action import render_action_for_uri, ViewAction
+from pitivi.factories.base import SourceFactory
+from pitivi.factories.timeline import TimelineSourceFactory
+from pitivi.settings import export_settings_to_render_settings
+from pitivi.stream import VideoStream, AudioStream
+from pitivi.utils import beautify_length
+
+class Renderer(Loggable, Signallable):
+    """ Rendering helper methods """
+
+    __signals__ = {
+        "eos" : None
+        }
+
+    def __init__(self, project, pipeline=None, outfile=None):
+        Loggable.__init__(self)
+        # grab the Pipeline and settings
+        self.project = project
+        if pipeline != None:
+            self.pipeline = pipeline
+        else:
+            self.pipeline = self.project.pipeline
+        self.outfile = outfile
+        self.detectStreamTypes()
+
+        self.rendering = False
+        self.renderaction = None
+        self.settings = project.getSettings()
+
+    def detectStreamTypes(self):
+        self.have_video = False
+        self.have_audio = False
+
+        # we can only render TimelineSourceFactory
+        if len(self.pipeline.factories) == 0:
+            timeline_source = self.project.factory
+        else:
+            sources = [factory for factory in self.pipeline.factories.keys()
+                    if isinstance(factory, SourceFactory)]
+            timeline_source = sources[0]
+        assert isinstance(timeline_source, TimelineSourceFactory)
+
+        for track in timeline_source.timeline.tracks:
+            if isinstance(track.stream, AudioStream) and track.duration > 0:
+                self.have_audio = True
+            elif isinstance(track.stream, VideoStream) and \
+                    track.duration > 0:
+                self.have_video = True
+
+    def _eosCb(self, unused_pipeline):
+        self.debug("eos !")
+        self.rendering = False
+        self.updateUIOnEOS()
+        self.removeRecordAction()
+        self.emit("eos")
+
+    def updateUIOnEOS(self):
+        pass
+
+    def _positionCb(self, unused_pipeline, position):
+        self.debug("%r %r", unused_pipeline, position)
+        fraction = None
+        text = None
+        timediff = time.time() - self.timestarted
+        length = self.project.timeline.duration
+        fraction = float(min(position, length)) / float(length)
+        if timediff > 5.0 and position:
+            # only display ETA after 5s in order to have enough averaging and
+            # if the position is non-null
+            totaltime = (timediff * float(length) / float(position)) - timediff
+            text = beautify_length(int(totaltime * gst.SECOND))
+        self.updatePosition(fraction, text)
+
+    def updatePosition(self, fraction, text):
+        pass
+
+    def _changeSourceSettings(self, settings):
+        videocaps = settings.getVideoCaps()
+        for source in self.project.sources.getSources():
+            source.setFilterCaps(videocaps)
+
+    def addRecordAction(self):
+        self.debug("renderaction %r", self.renderaction)
+        if self.renderaction == None:
+            self.pipeline.connect('position', self._positionCb)
+            self.pipeline.connect('eos', self._eosCb)
+            self.debug("Setting pipeline to STOP")
+            self.pipeline.stop()
+            settings = export_settings_to_render_settings(self.settings,
+                    self.have_video, self.have_audio)
+            self.debug("Creating RenderAction")
+            if len(self.pipeline.factories) == 0:
+                sources = [self.project.factory]
+            else:
+                sources = [factory for factory in self.pipeline.factories
+                        if isinstance(factory, SourceFactory)]
+            self.renderaction = render_action_for_uri(self.outfile,
+                    settings, *sources)
+            self.debug("setting action on pipeline")
+            self.pipeline.addAction(self.renderaction)
+            self.debug("Activating render action")
+            self.renderaction.activate()
+            self.debug("Setting all active ViewAction to sync=False")
+            for ac in self.pipeline.actions:
+                if isinstance(ac, ViewAction) and ac.isActive():
+                    ac.setSync(False)
+            self.debug("Updating all sources to render settings")
+            self._changeSourceSettings(self.settings)
+            self.debug("setting pipeline to PAUSE")
+            self.pipeline.pause()
+            self.debug("done")
+
+    def removeRecordAction(self):
+        self.debug("renderaction %r", self.renderaction)
+        if self.renderaction:
+            self.pipeline.stop()
+            self.renderaction.deactivate()
+            self.pipeline.removeAction(self.renderaction)
+            self.debug("putting all active ViewActions back to sync=True")
+            for ac in self.pipeline.actions:
+                if isinstance(ac, ViewAction) and ac.isActive():
+                    ac.setSync(True)
+            self._changeSourceSettings(self.project.getSettings())
+            self.pipeline.pause()
+            self.pipeline.disconnect_by_function(self._positionCb)
+            self.pipeline.disconnect_by_function(self._eosCb)
+            self.renderaction = None
+
+    def startRender(self):
+        self.debug("Rendering")
+        if self.outfile and not self.rendering:
+            self.addRecordAction()
+            self.pipeline.play()
+            self.timestarted = time.time()
+            self.rendering = True
diff --git a/pitivi/ui/encodingdialog.py b/pitivi/ui/encodingdialog.py
index be7339e..1356507 100644
--- a/pitivi/ui/encodingdialog.py
+++ b/pitivi/ui/encodingdialog.py
@@ -24,7 +24,6 @@ Encoding dialog
 """
 
 import os
-import time
 import gtk
 import gst
 from urlparse import urlparse
@@ -41,9 +40,9 @@ from pitivi.factories.base import SourceFactory
 from pitivi.factories.timeline import TimelineSourceFactory
 from pitivi.settings import export_settings_to_render_settings
 from pitivi.stream import VideoStream, AudioStream
-from pitivi.utils import beautify_length
+from pitivi.render import Renderer
 
-class EncodingDialog(GladeWindow, Loggable):
+class EncodingDialog(GladeWindow, Renderer):
     """ Encoding dialog box """
     glade_file = "encodingdialog.glade"
 
@@ -64,18 +63,8 @@ class EncodingDialog(GladeWindow, Loggable):
         self.ainfo = self.widgets["audioinfolabel"]
         self.window.set_icon_from_file(configure.get_pixmap_dir() + "/pitivi-render-16.png")
 
-        # grab the Pipeline and settings
-        self.project = project
-        if pipeline != None:
-            self.pipeline = pipeline
-        else:
-            self.pipeline = self.project.pipeline
-        self.detectStreamTypes()
+        Renderer.__init__(self, project, pipeline)
 
-        self.outfile = None
-        self.rendering = False
-        self.renderaction = None
-        self.settings = project.getSettings()
         self.timestarted = 0
         self._displaySettings()
 
@@ -123,31 +112,14 @@ class EncodingDialog(GladeWindow, Loggable):
             self.app.settings.lastExportFolder = dialog.get_current_folder()
         dialog.destroy()
 
-    def _positionCb(self, unused_pipeline, position):
-        self.debug("%r %r", unused_pipeline, position)
-        timediff = time.time() - self.timestarted
-        length = self.project.timeline.duration
-        self.progressbar.set_fraction(float(min(position, length)) / float(length))
-        if timediff > 5.0 and position:
-            # only display ETA after 5s in order to have enough averaging and
-            # if the position is non-null
-            totaltime = (timediff * float(length) / float(position)) - timediff
-            length = beautify_length(int(totaltime * gst.SECOND))
-            if length:
-                self.progressbar.set_text(_("About %s left") % length)
-
-    def _changeSourceSettings(self, settings):
-        videocaps = settings.getVideoCaps()
-        for source in self.project.sources.getSources():
-            source.setFilterCaps(videocaps)
+    def updatePosition(self, fraction, text):
+        self.progressbar.set_fraction(fraction)
+        if text is not None:
+            self.progressbar.set_text(_("About %s left") % text)
 
     def _recordButtonClickedCb(self, unused_button):
-        self.debug("Rendering")
-        if self.outfile and not self.rendering:
-            self.addRecordAction()
-            self.pipeline.play()
-            self.timestarted = time.time()
-            self.rendering = True
+        self.startRender()
+        if self.rendering:
             self.cancelbutton.set_label("gtk-cancel")
             self.progressbar.set_text(_("Rendering"))
             self.recordbutton.set_sensitive(False)
@@ -163,16 +135,13 @@ class EncodingDialog(GladeWindow, Loggable):
             self._displaySettings()
         dialog.destroy()
 
-    def _eosCb(self, unused_pipeline):
-        self.debug("EOS !")
-        self.rendering = False
+    def updateUIOnEOS(self):
         self.progressbar.set_text(_("Rendering Complete"))
         self.progressbar.set_fraction(1.0)
         self.recordbutton.set_sensitive(False)
         self.filebutton.set_sensitive(True)
         self.settingsbutton.set_sensitive(True)
         self.cancelbutton.set_label("gtk-close")
-        self.removeRecordAction()
 
     def _cancelButtonClickedCb(self, unused_button):
         self.debug("Cancelling !")
@@ -181,64 +150,3 @@ class EncodingDialog(GladeWindow, Loggable):
     def _deleteEventCb(self, window, event):
         self.debug("delete event")
         self._shutDown()
-
-    def detectStreamTypes(self):
-        self.have_video = False
-        self.have_audio = False
-
-        # we can only render TimelineSourceFactory
-        sources = [factory for factory in self.pipeline.factories.keys()
-                if isinstance(factory, SourceFactory)]
-        timeline_source = sources[0]
-        assert isinstance(timeline_source, TimelineSourceFactory)
-
-        for track in timeline_source.timeline.tracks:
-            if isinstance(track.stream, AudioStream) and track.duration > 0:
-                self.have_audio = True
-            elif isinstance(track.stream, VideoStream) and \
-                    track.duration > 0:
-                self.have_video = True
-
-    def addRecordAction(self):
-        self.debug("renderaction %r", self.renderaction)
-        if self.renderaction == None:
-            self.pipeline.connect('position', self._positionCb)
-            self.pipeline.connect('eos', self._eosCb)
-            self.debug("Setting pipeline to STOP")
-            self.pipeline.stop()
-            settings = export_settings_to_render_settings(self.settings,
-                    self.have_video, self.have_audio)
-            self.debug("Creating RenderAction")
-            sources = [factory for factory in self.pipeline.factories
-                    if isinstance(factory, SourceFactory)]
-            self.renderaction = render_action_for_uri(self.outfile,
-                    settings, *sources)
-            self.debug("setting action on pipeline")
-            self.pipeline.addAction(self.renderaction)
-            self.debug("Activating render action")
-            self.renderaction.activate()
-            self.debug("Setting all active ViewAction to sync=False")
-            for ac in self.pipeline.actions:
-                if isinstance(ac, ViewAction) and ac.isActive():
-                    ac.setSync(False)
-            self.debug("Updating all sources to render settings")
-            self._changeSourceSettings(self.settings)
-            self.debug("setting pipeline to PAUSE")
-            self.pipeline.pause()
-            self.debug("done")
-
-    def removeRecordAction(self):
-        self.debug("renderaction %r", self.renderaction)
-        if self.renderaction:
-            self.pipeline.stop()
-            self.renderaction.deactivate()
-            self.pipeline.removeAction(self.renderaction)
-            self.debug("putting all active ViewActions back to sync=True")
-            for ac in self.pipeline.actions:
-                if isinstance(ac, ViewAction) and ac.isActive():
-                    ac.setSync(True)
-            self._changeSourceSettings(self.project.getSettings())
-            self.pipeline.pause()
-            self.pipeline.disconnect_by_function(self._positionCb)
-            self.pipeline.disconnect_by_function(self._eosCb)
-            self.renderaction = None



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]