[pitivi: 4/14] application: render: Initial implementation of command line rendering
- From: Edward Hervey <edwardrv src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi: 4/14] application: render: Initial implementation of command line rendering
- Date: Mon, 20 Sep 2010 09:43:59 +0000 (UTC)
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]