[pitivi/ges: 179/287] encode: Move everything related to encoding into encode.py
- From: Jean-FranÃois Fortin Tam <jfft src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi/ges: 179/287] encode: Move everything related to encoding into encode.py
- Date: Thu, 15 Mar 2012 16:40:53 +0000 (UTC)
commit dfd3e0767e085b704768426c6604c46feef8cfd9
Author: Thibault Saunier <thibault saunier collabora com>
Date: Mon Jan 9 20:39:42 2012 -0300
encode: Move everything related to encoding into encode.py
pitivi/encode.py | 882 ++++++++++++++++++++++++++++++++++++++++++
pitivi/ui/Makefile.am | 1 -
pitivi/ui/encodingdialog.py | 901 -------------------------------------------
pitivi/ui/mainwindow.py | 2 +-
po/POTFILES.in | 3 +-
5 files changed, 884 insertions(+), 905 deletions(-)
---
diff --git a/pitivi/encode.py b/pitivi/encode.py
index a008817..8415dd3 100644
--- a/pitivi/encode.py
+++ b/pitivi/encode.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
# PiTiVi , Non-linear video editor
#
# pitivi/encode.py
@@ -23,11 +24,29 @@
Encoding-related utilities and classes
"""
+import os
+import gtk
+import gst
+import ges
+import time
import gst
import pitivi.utils.loggable as log
+from gettext import gettext as _
+
+from pitivi import configure
+from pitivi.utils.playback import togglePlayback, Seeker
+from pitivi.utils.signal import Signallable
+
+from pitivi.utils.loggable import Loggable
+from pitivi.utils.widgets import GstElementSettingsDialog
+from pitivi.utils.ripple_update_group import RippleUpdateGroup
+from pitivi.utils.ui import model, frame_rates, audio_rates, audio_depths, \
+ audio_channels, get_combo_value, set_combo_value, beautify_ETA
+
+#---------------- Private utils ---------------------------------------#
def get_compatible_sink_pad(factoryname, caps):
"""
Returns the pad name of a (request) pad from factoryname which is
@@ -232,3 +251,866 @@ def available_combinations():
containers.append(muxer)
return containers, audio, video
+
+
+def beautify_factoryname(factory):
+ """Returns a nice name for the specified gst.ElementFactory instance."""
+ # only replace lowercase versions of "format", "video", "audio"
+ # otherwise they might be part of a trademark name
+ words_to_remove = ["Muxer", "muxer", "Encoder", "encoder",
+ "format", "video", "audio", "instead"]
+ name = factory.get_longname()
+ for word in words_to_remove:
+ name = name.replace(word, "")
+ return " ".join(word for word in name.split())
+
+
+def extension_for_muxer(muxer):
+ """Returns the file extension appropriate for the specified muxer."""
+ exts = {
+ "asfmux": "asf",
+ "avimux": "avi",
+ "ffmux_3g2": "3g2",
+ "ffmux_avm2": "avm2",
+ "ffmux_dvd": "vob",
+ "ffmux_flv": "flv",
+ "ffmux_ipod": "mp4",
+ "ffmux_mpeg": "mpeg",
+ "ffmux_mpegts": "mpeg",
+ "ffmux_psp": "mp4",
+ "ffmux_rm": "rm",
+ "ffmux_svcd": "mpeg",
+ "ffmux_swf": "swf",
+ "ffmux_vcd": "mpeg",
+ "ffmux_vob": "vob",
+ "flvmux": "flv",
+ "gppmux": "3gp",
+ "matroskamux": "mkv",
+ "mj2mux": "mj2",
+ "mp4mux": "mp4",
+ "mpegpsmux": "mpeg",
+ "mpegtsmux": "mpeg",
+ "mvemux": "mve",
+ "mxfmux": "mxf",
+ "oggmux": "ogv",
+ "qtmux": "mov",
+ "webmmux": "webm"}
+ return exts.get(muxer)
+
+
+def factorylist(factories):
+ """Create a gtk.ListStore() of sorted, beautified factory names.
+
+ @param factories: The factories available for creating the list.
+ @type factories: A sequence of gst.ElementFactory instances.
+ """
+ columns = (str, object)
+ data = [(beautify_factoryname(factory), factory)
+ for factory in factories
+ if factory.get_rank() > 0]
+ data.sort(key=lambda x: x[0])
+ return model(columns, data)
+
+
+#--------------------------------- Public classes -----------------------------#
+class EncodingProgressDialog(Signallable):
+ __signals__ = {
+ "pause": [],
+ "cancel": [],
+ }
+
+ def __init__(self, app, parent):
+ self.app = app
+ self.system = app.app.system
+ self.builder = gtk.Builder()
+ self.builder.add_from_file(os.path.join(configure.get_ui_dir(),
+ "encodingprogress.ui"))
+ self.builder.connect_signals(self)
+
+ self.window = self.builder.get_object("render-progress")
+ self.table1 = self.builder.get_object("table1")
+ self.progressbar = self.builder.get_object("progressbar")
+ self.play_pause_button = self.builder.get_object("play_pause_button")
+ # Parent the dialog with mainwindow, since encodingdialog is hidden.
+ # It allows this dialog to properly minimize together with mainwindow
+ self.window.set_transient_for(self.app)
+
+ # UI widgets
+ self.window.set_icon_from_file(configure.get_pixmap_dir() + "/pitivi-render-16.png")
+
+ # FIXME: re-enable these widgets when bugs #650710 and 637079 are fixed
+ self.play_pause_button.hide()
+ self.table1.hide()
+
+ def updatePosition(self, fraction, estimated):
+ self.progressbar.set_fraction(fraction)
+ self.window.set_title(_("%d%% Rendered") % int(100 * fraction))
+ if estimated:
+ self.progressbar.set_text(_("About %s left") % estimated)
+
+ def setState(self, state):
+ if state == gst.STATE_PLAYING:
+ self.play_pause_button.props.label = gtk.STOCK_MEDIA_PAUSE
+ self.system.inhibitSleep(EncodingDialog.INHIBIT_REASON)
+ else:
+ self.play_pause_button.props.label = 'pitivi-render'
+ self.system.uninhibitSleep(EncodingDialog.INHIBIT_REASON)
+
+ def _cancelButtonClickedCb(self, unused_button):
+ self.emit("cancel")
+
+ def _pauseButtonClickedCb(self, unused_button):
+ self.emit("pause")
+
+
+class EncodingDialog(Loggable):
+ """Render dialog box.
+
+ @ivar preferred_aencoder: The last audio encoder selected by the user.
+ @type preferred_aencoder: str
+ @ivar preferred_vencoder: The last video encoder selected by the user.
+ @type preferred_vencoder: str
+ @ivar settings: The settings used for rendering.
+ @type settings: MultimediaSettings
+ """
+ INHIBIT_REASON = _("Currently rendering media")
+
+ def __init__(self, app, project, pipeline=None):
+
+ from pitivi.ui.preset import RenderPresetManager
+
+ Loggable.__init__(self)
+
+ self.app = app
+ self.project = project
+ self.system = app.app.system
+ self._timeline = self.app.timeline
+ self._seeker = Seeker(80)
+ if pipeline != None:
+ self._pipeline = pipeline
+ else:
+ self._pipeline = self.project.pipeline
+
+ self.outfile = None
+ self.settings = project.getSettings()
+ self.timestarted = 0
+
+ # Various gstreamer signal connection ID's
+ # {object: sigId}
+ self._gstSigId = {}
+
+ self.builder = gtk.Builder()
+ self.builder.add_from_file(os.path.join(configure.get_ui_dir(),
+ "encodingdialog.ui"))
+ self._setProperties()
+ self.builder.connect_signals(self)
+
+ # UI widgets
+ icon = os.path.join(configure.get_pixmap_dir(), "pitivi-render-16.png")
+ self.window.set_icon_from_file(icon)
+
+ # FIXME: re-enable this widget when bug #637078 is implemented
+ self.selected_only_button.destroy()
+
+ # The Render dialog and the Project Settings dialog have some
+ # common settings, for example the audio sample rate.
+ # When these common settings are changed in the Render dialog,
+ # we don't want them to be saved, so we create a copy of the project's
+ # settings to be used by the Render dialog for rendering.
+ render_settings = project.getSettings().copy()
+ # Note: render_settings will end up as self.settings.
+
+ # Directory and Filename
+ self.filebutton.set_current_folder(self.app.settings.lastExportFolder)
+ if not self.project.name:
+ self.updateFilename(_("Untitled"))
+ else:
+ self.updateFilename(self.project.name)
+
+ # We store these so that when the user tries various container formats,
+ # (AKA muxers) we select these a/v encoders, if they are compatible with
+ # the current container format.
+ self.preferred_vencoder = self.settings.vencoder
+ self.preferred_aencoder = self.settings.aencoder
+
+ self._initializeComboboxModels()
+ self._displaySettings()
+ self._displayRenderSettings()
+
+ self.window.connect("delete-event", self._deleteEventCb)
+ self.settings.connect("settings-changed", self._settingsChanged)
+
+ # Monitor changes
+
+ self.wg = RippleUpdateGroup()
+ self.wg.addVertex(self.frame_rate_combo, signal="changed")
+ self.wg.addVertex(self.save_render_preset_button,
+ update_func=self._updateRenderSaveButton)
+ self.wg.addVertex(self.channels_combo, signal="changed")
+ self.wg.addVertex(self.sample_rate_combo, signal="changed")
+ self.wg.addVertex(self.sample_depth_combo, signal="changed")
+ self.wg.addVertex(self.muxercombobox, signal="changed")
+ self.wg.addVertex(self.audio_encoder_combo, signal="changed")
+ self.wg.addVertex(self.video_encoder_combo, signal="changed")
+ self.render_presets = RenderPresetManager()
+ self.render_presets.loadAll()
+
+ self._fillPresetsTreeview(
+ self.render_preset_treeview, self.render_presets,
+ self._updateRenderPresetButtons)
+
+ self.wg.addEdge(self.frame_rate_combo,
+ self.save_render_preset_button)
+ self.wg.addEdge(self.audio_encoder_combo,
+ self.save_render_preset_button)
+ self.wg.addEdge(self.video_encoder_combo,
+ self.save_render_preset_button)
+ self.wg.addEdge(self.muxercombobox,
+ self.save_render_preset_button)
+ self.wg.addEdge(self.channels_combo,
+ self.save_render_preset_button)
+ self.wg.addEdge(self.sample_rate_combo,
+ self.save_render_preset_button)
+ self.wg.addEdge(self.sample_depth_combo,
+ self.save_render_preset_button)
+
+ self._infobarForPresetManager = {
+ self.render_presets: self.render_preset_infobar}
+
+ # Bind widgets to RenderPresetsManager
+ self.bindCombo(self.render_presets, "channels",
+ self.channels_combo)
+ self.bindCombo(self.render_presets, "sample-rate",
+ self.sample_rate_combo)
+ self.bindCombo(self.render_presets, "depth",
+ self.sample_depth_combo)
+ self.bindCombo(self.render_presets, "acodec",
+ self.audio_encoder_combo)
+ self.bindCombo(self.render_presets, "vcodec",
+ self.video_encoder_combo)
+ self.bindCombo(self.render_presets, "container",
+ self.muxercombobox)
+ self.bindCombo(self.render_presets, "frame-rate",
+ self.frame_rate_combo)
+ self.bindHeight(self.render_presets)
+ self.bindWidth(self.render_presets)
+
+ self.createNoPreset(self.render_presets)
+
+ def createNoPreset(self, mgr):
+ mgr.prependPreset(_("No preset"), {
+ "depth": int(get_combo_value(self.sample_depth_combo)),
+ "channels": int(get_combo_value(self.channels_combo)),
+ "sample-rate": int(get_combo_value(self.sample_rate_combo)),
+ "acodec": get_combo_value(self.audio_encoder_combo).get_name(),
+ "vcodec": get_combo_value(self.video_encoder_combo).get_name(),
+ "container": get_combo_value(self.muxercombobox).get_name(),
+ "frame-rate": gst.Fraction(int(get_combo_value(self.frame_rate_combo).num),
+ int(get_combo_value(self.frame_rate_combo).denom)),
+ "height": self.getDimension("height"),
+ "width": self.getDimension("width")})
+
+ def bindCombo(self, mgr, name, widget):
+ if name == "container":
+ mgr.bindWidget(name,
+ lambda x: self.muxer_setter(widget, x),
+ lambda: get_combo_value(widget).get_name())
+
+ elif name == "acodec":
+ mgr.bindWidget(name,
+ lambda x: self.acodec_setter(widget, x),
+ lambda: get_combo_value(widget).get_name())
+
+ elif name == "vcodec":
+ mgr.bindWidget(name,
+ lambda x: self.vcodec_setter(widget, x),
+ lambda: get_combo_value(widget).get_name())
+
+ elif name == "depth":
+ mgr.bindWidget(name,
+ lambda x: self.sample_depth_setter(widget, x),
+ lambda: get_combo_value(widget))
+
+ elif name == "sample-rate":
+ mgr.bindWidget(name,
+ lambda x: self.sample_rate_setter(widget, x),
+ lambda: get_combo_value(widget))
+
+ elif name == "channels":
+ mgr.bindWidget(name,
+ lambda x: self.channels_setter(widget, x),
+ lambda: get_combo_value(widget))
+
+ elif name == "frame-rate":
+ mgr.bindWidget(name,
+ lambda x: self.framerate_setter(widget, x),
+ lambda: get_combo_value(widget))
+
+ def muxer_setter(self, widget, value):
+ set_combo_value(widget, gst.element_factory_find(value))
+ self.settings.setEncoders(muxer=value)
+
+ # Update the extension of the filename.
+ basename = os.path.splitext(self.fileentry.get_text())[0]
+ self.updateFilename(basename)
+
+ # Update muxer-dependent widgets.
+ self.muxer_combo_changing = True
+ try:
+ self.updateAvailableEncoders()
+ finally:
+ self.muxer_combo_changing = False
+
+ def acodec_setter(self, widget, value):
+ set_combo_value(widget, gst.element_factory_find(value))
+ self.settings.setEncoders(aencoder=value)
+ if not self.muxer_combo_changing:
+ # The user directly changed the audio encoder combo.
+ self.preferred_aencoder = value
+
+ def vcodec_setter(self, widget, value):
+ set_combo_value(widget, gst.element_factory_find(value))
+ self.settings.setEncoders(vencoder=value)
+ if not self.muxer_combo_changing:
+ # The user directly changed the video encoder combo.
+ self.preferred_vencoder = value
+
+ def sample_depth_setter(self, widget, value):
+ set_combo_value(widget, value)
+ self.settings.setAudioProperties(depth=value)
+
+ def sample_rate_setter(self, widget, value):
+ set_combo_value(widget, value)
+ self.settings.setAudioProperties(rate=value)
+
+ def channels_setter(self, widget, value):
+ set_combo_value(widget, value)
+ self.settings.setAudioProperties(nbchanns=value)
+
+ def framerate_setter(self, widget, value):
+ set_combo_value(widget, value)
+ self.settings.setVideoProperties(framerate=value)
+
+ def bindHeight(self, mgr):
+ mgr.bindWidget("height",
+ lambda x: self.settings.setVideoProperties(height=x),
+ lambda: 0)
+
+ def bindWidth(self, mgr):
+ mgr.bindWidget("width",
+ lambda x: self.settings.setVideoProperties(width=x),
+ lambda: 0)
+
+ def getDimension(self, dimension):
+ value = self.settings.getVideoWidthAndHeight()
+ if dimension == "height":
+ return value[1]
+ elif dimension == "width":
+ return value[0]
+
+ def _fillPresetsTreeview(self, treeview, mgr, update_buttons_func):
+ """Set up the specified treeview to display the specified presets.
+
+ @param treeview: The treeview for displaying the presets.
+ @type treeview: TreeView
+ @param mgr: The preset manager.
+ @type mgr: PresetManager
+ @param update_buttons_func: A function which updates the buttons for
+ removing and saving a preset, enabling or disabling them accordingly.
+ @type update_buttons_func: function
+ """
+ renderer = gtk.CellRendererText()
+ renderer.props.editable = True
+ column = gtk.TreeViewColumn("Preset", renderer, text=0)
+ treeview.append_column(column)
+ treeview.props.headers_visible = False
+ model = mgr.getModel()
+ treeview.set_model(model)
+ model.connect("row-inserted", self._newPresetCb,
+ column, renderer, treeview)
+ renderer.connect("edited", self._presetNameEditedCb, mgr)
+ renderer.connect("editing-started", self._presetNameEditingStartedCb,
+ mgr)
+ treeview.get_selection().connect("changed", self._presetChangedCb,
+ mgr, update_buttons_func)
+ treeview.connect("focus-out-event", self._treeviewDefocusedCb, mgr)
+
+ def _newPresetCb(self, model, path, iter_, column, renderer, treeview):
+ """Handle the addition of a preset to the model of the preset manager.
+ """
+ treeview.set_cursor_on_cell(path, column, renderer, start_editing=True)
+ treeview.grab_focus()
+
+ def _presetNameEditedCb(self, renderer, path, new_text, mgr):
+ """Handle the renaming of a preset."""
+ from pitivi.ui.preset import DuplicatePresetNameException
+
+ try:
+ mgr.renamePreset(path, new_text)
+ self._updateRenderPresetButtons()
+ except DuplicatePresetNameException:
+ error_markup = _('"%s" already exists.') % new_text
+ self._showPresetManagerError(mgr, error_markup)
+
+ def _presetNameEditingStartedCb(self, renderer, editable, path, mgr):
+ """Handle the start of a preset renaming."""
+ self._hidePresetManagerError(mgr)
+
+ def _treeviewDefocusedCb(self, widget, event, mgr):
+ """Handle the treeview loosing the focus."""
+ self._hidePresetManagerError(mgr)
+
+ def _showPresetManagerError(self, mgr, error_markup):
+ """Show the specified error on the infobar associated with the manager.
+
+ @param mgr: The preset manager for which to show the error.
+ @type mgr: PresetManager
+ """
+ infobar = self._infobarForPresetManager[mgr]
+ # The infobar must contain exactly one object in the content area:
+ # a label for displaying the error.
+ label = infobar.get_content_area().children()[0]
+ label.set_markup(error_markup)
+ infobar.show()
+
+ def _hidePresetManagerError(self, mgr):
+ """Hide the error infobar associated with the manager.
+
+ @param mgr: The preset manager for which to hide the error infobar.
+ @type mgr: PresetManager
+ """
+ infobar = self._infobarForPresetManager[mgr]
+ infobar.hide()
+
+ def _updateRenderSaveButton(self, unused_in, button):
+ button.set_sensitive(self.render_presets.isSaveButtonSensitive())
+
+ @staticmethod
+ def _getUniquePresetName(mgr):
+ """Get a unique name for a new preset for the specified PresetManager.
+ """
+ existing_preset_names = list(mgr.getPresetNames())
+ preset_name = _("New preset")
+ i = 1
+ while preset_name in existing_preset_names:
+ preset_name = _("New preset %d") % i
+ i += 1
+ return preset_name
+
+ def _addRenderPresetButtonClickedCb(self, button):
+ preset_name = self._getUniquePresetName(self.render_presets)
+ self.render_presets.addPreset(preset_name, {
+ "depth": int(get_combo_value(self.sample_depth_combo)),
+ "channels": int(get_combo_value(self.channels_combo)),
+ "sample-rate": int(get_combo_value(self.sample_rate_combo)),
+ "acodec": get_combo_value(self.audio_encoder_combo).get_name(),
+ "vcodec": get_combo_value(self.video_encoder_combo).get_name(),
+ "container": get_combo_value(self.muxercombobox).get_name(),
+ "frame-rate": gst.Fraction(int(get_combo_value(self.frame_rate_combo).num),
+ int(get_combo_value(self.frame_rate_combo).denom)),
+ "height": 0,
+ "width": 0})
+
+ self.render_presets.restorePreset(preset_name)
+ self._updateRenderPresetButtons()
+
+ def _saveRenderPresetButtonClickedCb(self, button):
+ self.render_presets.savePreset()
+ self.save_render_preset_button.set_sensitive(False)
+ self.remove_render_preset_button.set_sensitive(True)
+
+ def _updateRenderPresetButtons(self):
+ can_save = self.render_presets.isSaveButtonSensitive()
+ self.save_render_preset_button.set_sensitive(can_save)
+ can_remove = self.render_presets.isRemoveButtonSensitive()
+ self.remove_render_preset_button.set_sensitive(can_remove)
+
+ def _removeRenderPresetButtonClickedCb(self, button):
+ selection = self.render_preset_treeview.get_selection()
+ model, iter_ = selection.get_selected()
+ if iter_:
+ self.render_presets.removePreset(model[iter_][0])
+
+ def _presetChangedCb(self, selection, mgr, update_preset_buttons_func):
+ """Handle the selection of a preset."""
+ model, iter_ = selection.get_selected()
+ if iter_:
+ self.selected_preset = model[iter_][0]
+ else:
+ self.selected_preset = None
+
+ mgr.restorePreset(self.selected_preset)
+ self._displaySettings()
+ update_preset_buttons_func()
+ self._hidePresetManagerError(mgr)
+
+ def _setProperties(self):
+ self.window = self.builder.get_object("render-dialog")
+ self.selected_only_button = self.builder.get_object(
+ "selected_only_button")
+ self.frame_rate_combo = self.builder.get_object("frame_rate_combo")
+ self.scale_spinbutton = self.builder.get_object("scale_spinbutton")
+ self.channels_combo = self.builder.get_object("channels_combo")
+ self.sample_rate_combo = self.builder.get_object(
+ "sample_rate_combo")
+ self.sample_depth_combo = self.builder.get_object(
+ "sample_depth_combo")
+ self.muxercombobox = self.builder.get_object("muxercombobox")
+ self.audio_encoder_combo = self.builder.get_object(
+ "audio_encoder_combo")
+ self.video_encoder_combo = self.builder.get_object(
+ "video_encoder_combo")
+ self.filebutton = self.builder.get_object("filebutton")
+ self.fileentry = self.builder.get_object("fileentry")
+ self.resolution_label = self.builder.get_object("resolution_label")
+ self.render_preset_treeview = self.builder.get_object(
+ "render_preset_treeview")
+ self.save_render_preset_button = self.builder.get_object(
+ "save_render_preset_button")
+ self.remove_render_preset_button = self.builder.get_object(
+ "remove_render_preset_button")
+ self.render_preset_infobar = self.builder.get_object(
+ "render-preset-infobar")
+
+ def _settingsChanged(self, settings):
+ self.updateResolution()
+
+ def _initializeComboboxModels(self):
+ # Avoid loop import
+ from pitivi.settings import MultimediaSettings
+ self.frame_rate_combo.set_model(frame_rates)
+ self.channels_combo.set_model(audio_channels)
+ self.sample_rate_combo.set_model(audio_rates)
+ self.sample_depth_combo.set_model(audio_depths)
+ self.muxercombobox.set_model(factorylist(MultimediaSettings.muxers))
+
+ def _displaySettings(self):
+ """Display the settings that also change in the ProjectSettingsDialog.
+ """
+ # Video settings
+ set_combo_value(self.frame_rate_combo, self.settings.videorate)
+ # Audio settings
+ set_combo_value(self.channels_combo, self.settings.audiochannels)
+ set_combo_value(self.sample_rate_combo, self.settings.audiorate)
+ set_combo_value(self.sample_depth_combo, self.settings.audiodepth)
+
+ def _displayRenderSettings(self):
+ """Display the settings which can be changed only in the EncodingDialog.
+ """
+ # Video settings
+ # note: this will trigger an update of the video resolution label
+ self.scale_spinbutton.set_value(self.settings.render_scale)
+ # Muxer settings
+ # note: this will trigger an update of the codec comboboxes
+ set_combo_value(self.muxercombobox,
+ gst.element_factory_find(self.settings.muxer))
+
+ def _checkForExistingFile(self, *args):
+ """
+ Display a warning icon and tooltip if the file path already exists.
+ """
+ path = self.filebutton.get_current_folder()
+ if not path:
+ # This happens when the window is initialized.
+ return
+ warning_icon = gtk.STOCK_DIALOG_WARNING
+ filename = self.fileentry.get_text()
+ if not filename:
+ tooltip_text = _("A file name is required.")
+ elif filename and os.path.exists(os.path.join(path, filename)):
+ tooltip_text = _("This file already exists.\n"
+ "If you don't want to overwrite it, choose a "
+ "different file name or folder.")
+ else:
+ warning_icon = None
+ tooltip_text = None
+ self.fileentry.set_icon_from_stock(1, warning_icon)
+ self.fileentry.set_icon_tooltip_text(1, tooltip_text)
+
+ def updateFilename(self, basename):
+ """Updates the filename UI element to show the specified file name."""
+ extension = extension_for_muxer(self.settings.muxer)
+ if extension:
+ name = "%s%s%s" % (basename, os.path.extsep, extension)
+ else:
+ name = basename
+ self.fileentry.set_text(name)
+
+ def updateAvailableEncoders(self):
+ """Update the encoder comboboxes to show the available encoders."""
+ video_encoders = self.settings.getVideoEncoders()
+ video_encoder_model = factorylist(video_encoders)
+ self.video_encoder_combo.set_model(video_encoder_model)
+
+ audio_encoders = self.settings.getAudioEncoders()
+ audio_encoder_model = factorylist(audio_encoders)
+ self.audio_encoder_combo.set_model(audio_encoder_model)
+
+ self._updateEncoderCombo(
+ self.video_encoder_combo, self.preferred_vencoder)
+ self._updateEncoderCombo(
+ self.audio_encoder_combo, self.preferred_aencoder)
+
+ def _updateEncoderCombo(self, encoder_combo, preferred_encoder):
+ """Select the specified encoder for the specified encoder combo."""
+ if preferred_encoder:
+ # A preferrence exists, pick it if it can be found in
+ # the current model of the combobox.
+ vencoder = gst.element_factory_find(preferred_encoder)
+ set_combo_value(encoder_combo, vencoder, default_index=0)
+ else:
+ # No preferrence exists, pick the first encoder from
+ # the current model of the combobox.
+ encoder_combo.set_active(0)
+
+ def _elementSettingsDialog(self, factory, settings_attr):
+ """Open a dialog to edit the properties for the specified factory.
+
+ @param factory: An element factory whose properties the user will edit.
+ @type factory: gst.ElementFactory
+ @param settings_attr: The MultimediaSettings attribute holding
+ the properties.
+ @type settings_attr: str
+ """
+ properties = getattr(self.settings, settings_attr)
+ self.dialog = GstElementSettingsDialog(factory, properties=properties)
+ self.dialog.window.set_transient_for(self.window)
+ self.dialog.ok_btn.connect("clicked", self._okButtonClickedCb, settings_attr)
+ self.dialog.window.run()
+
+ def startAction(self):
+ """ Start the render process """
+ self._pipeline.set_state(gst.STATE_NULL)
+ self._pipeline.set_mode(ges.TIMELINE_MODE_SMART_RENDER)
+ encodebin = self._pipeline.get_by_name("internal-encodebin")
+ self._gstSigId[encodebin] = encodebin.connect("element-added",
+ self._elementAddedCb)
+ self.timestarted = time.time()
+ self._pipeline.set_state(gst.STATE_PLAYING)
+
+ def _cancelRender(self, progress):
+ self.debug("aborting render")
+ self._shutDown()
+ self._destroyProgressWindow()
+
+ def _shutDown(self):
+ """ The render process has been aborted, shutdown the gstreamer pipeline
+ and disconnect from its signals """
+ self._pipeline.set_state(gst.STATE_NULL)
+ self._disconnectFromGst()
+ self._pipeline.set_mode(ges.TIMELINE_MODE_PREVIEW)
+
+ def _pauseRender(self, progress):
+ togglePlayback(self._pipeline)
+
+ def _destroyProgressWindow(self):
+ """ Handle the completion or the cancellation of the render process. """
+ self.progress.window.destroy()
+ self.progress = None
+ self.window.show() # Show the encoding dialog again
+
+ def _disconnectFromGst(self):
+ for obj, id in self._gstSigId.iteritems():
+ obj.disconnect(id)
+ self._gstSigId = {}
+ self._seeker.disconnect_by_function(self._updatePositionCb)
+
+ def _updateProjectSettings(self):
+ """Updates the settings of the project if the render settings changed.
+ """
+ settings = self.project.getSettings()
+ if (settings.muxer == self.settings.muxer
+ and settings.aencoder == self.settings.aencoder
+ and settings.vencoder == self.settings.vencoder
+ and settings.containersettings == self.settings.containersettings
+ and settings.acodecsettings == self.settings.acodecsettings
+ and settings.vcodecsettings == self.settings.vcodecsettings
+ and settings.render_scale == self.settings.render_scale):
+ # No setting which can be changed in the Render dialog
+ # and which we want to save have been changed.
+ return
+ settings.setEncoders(muxer=self.settings.muxer,
+ aencoder=self.settings.aencoder,
+ vencoder=self.settings.vencoder)
+ settings.containersettings = self.settings.containersettings
+ settings.acodecsettings = self.settings.acodecsettings
+ settings.vcodecsettings = self.settings.vcodecsettings
+ settings.setVideoProperties(render_scale=self.settings.render_scale)
+ # Signal that the project settings have been changed.
+ self.project.setSettings(settings)
+
+ def destroy(self):
+ self._updateProjectSettings()
+ self.window.destroy()
+
+ #------------------- Callbacks ------------------------------------------#
+
+ #-- UI callbacks
+ def _okButtonClickedCb(self, unused_button, settings_attr):
+ setattr(self.settings, settings_attr, self.dialog.getSettings())
+ self.dialog.window.destroy()
+
+ def _renderButtonClickedCb(self, unused_button):
+ """ The render button inside the render dialog has been clicked,
+ start the rendering process. """
+ self.outfile = os.path.join(self.filebutton.get_uri(),
+ self.fileentry.get_text())
+ self.progress = EncodingProgressDialog(self.app, self)
+ self.window.hide() # Hide the rendering settings dialog while rendering
+
+ # FIXME GES: Handle presets here!
+ # FIXME: Handle audio-only or video-only here
+ self.containerprofile = gst.pbutils.EncodingContainerProfile(None, None,
+ gst.Caps(self.muxertype), None)
+ self.videoprofile = gst.pbutils.EncodingVideoProfile(gst.Caps(self.videotype),
+ None, self.settings.getVideoCaps(True), 0)
+ self.audioprofile = gst.pbutils.EncodingAudioProfile(gst.Caps(self.audiotype), None,
+ self.settings.getAudioCaps(), 0)
+
+ self.containerprofile.add_profile(self.videoprofile)
+ self.containerprofile.add_profile(self.audioprofile)
+ self._pipeline.set_render_settings(self.outfile, self.containerprofile)
+ self.startAction()
+ self.progress.window.show()
+ self.progress.connect("cancel", self._cancelRender)
+ self.progress.connect("pause", self._pauseRender)
+ bus = self._pipeline.get_bus()
+ bus.add_signal_watch()
+ self._gstSigId[bus] = bus.connect('message', self._busMessageCb)
+ self._seeker.connect("position-changed", self._updatePositionCb)
+
+ def _closeButtonClickedCb(self, unused_button):
+ self.debug("Render dialog's Close button clicked")
+ self.destroy()
+
+ def _deleteEventCb(self, window, event):
+ self.debug("Render dialog is being deleted")
+ self.destroy()
+
+ #-- GStreamer callbacks
+ def _busMessageCb(self, unused_bus, message):
+ if message.type == gst.MESSAGE_EOS: # Render complete
+ self.debug("got EOS message, render complete")
+ self._shutDown()
+ self._destroyProgressWindow()
+ elif message.type == gst.MESSAGE_STATE_CHANGED and self.progress:
+ prev, state, pending = message.parse_state_changed()
+ self.progress.setState(state)
+
+ def _updatePositionCb(self, seeker, position):
+ if self.progress:
+ text = None
+ timediff = time.time() - self.timestarted
+ length = self._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_ETA(int(totaltime * gst.SECOND))
+ self.progress.updatePosition(fraction, text)
+
+ def _elementAddedCb(self, bin, element):
+ # Setting properties on gst.Element-s has they are added to the
+ # gst.Encodebin
+ if element.get_factory() == get_combo_value(self.video_encoder_combo):
+ for setting in self.settings.vcodecsettings:
+ element.set_property(setting, self.settings.vcodecsettings[setting])
+ elif element.get_factory() == get_combo_value(self.audio_encoder_combo):
+ for setting in self.settings.acodecsettings:
+ element.set_property(setting, self.settings.vcodecsettings[setting])
+
+ #-- Settings changed callbacks
+ def _scaleSpinbuttonChangedCb(self, button):
+ render_scale = self.scale_spinbutton.get_value()
+ self.settings.setVideoProperties(render_scale=render_scale)
+ self.updateResolution()
+
+ def updateResolution(self):
+ width, height = self.settings.getVideoWidthAndHeight(render=True)
+ self.resolution_label.set_text(u"%dÃ%d" % (width, height))
+
+ def _projectSettingsButtonClickedCb(self, button):
+ from pitivi.ui.projectsettings import ProjectSettingsDialog
+ dialog = ProjectSettingsDialog(self.window, self.project)
+ dialog.window.connect("destroy", self._projectSettingsDestroyCb)
+ dialog.window.run()
+
+ def _projectSettingsDestroyCb(self, dialog):
+ """Handle the destruction of the ProjectSettingsDialog."""
+ settings = self.project.getSettings()
+ self.settings.setVideoProperties(width=settings.videowidth,
+ height=settings.videoheight,
+ framerate=settings.videorate)
+ self.settings.setAudioProperties(nbchanns=settings.audiochannels,
+ rate=settings.audiorate,
+ depth=settings.audiodepth)
+ self._displaySettings()
+
+ def _frameRateComboChangedCb(self, combo):
+ framerate = get_combo_value(combo)
+ self.settings.setVideoProperties(framerate=framerate)
+
+ def _videoEncoderComboChangedCb(self, combo):
+ vencoder = get_combo_value(combo).get_name()
+ for template in gst.registry_get_default().lookup_feature(vencoder).get_static_pad_templates():
+ if template.name_template == "src":
+ self.videotype = template.get_caps().to_string()
+ for elem in self.videotype.split(","):
+ if "{" in elem or "[" in elem:
+ self.videotype = self.videotype[:self.videotype.index(elem) - 1]
+ break
+ self.settings.setEncoders(vencoder=vencoder)
+ if not self.muxer_combo_changing:
+ # The user directly changed the video encoder combo.
+ self.preferred_vencoder = vencoder
+
+ def _videoSettingsButtonClickedCb(self, button):
+ factory = get_combo_value(self.video_encoder_combo)
+ self._elementSettingsDialog(factory, 'vcodecsettings')
+
+ def _channelsComboChangedCb(self, combo):
+ self.settings.setAudioProperties(nbchanns=get_combo_value(combo))
+
+ def _sampleDepthComboChangedCb(self, combo):
+ self.settings.setAudioProperties(depth=get_combo_value(combo))
+
+ def _sampleRateComboChangedCb(self, combo):
+ self.settings.setAudioProperties(rate=get_combo_value(combo))
+
+ def _audioEncoderChangedComboCb(self, combo):
+ aencoder = get_combo_value(combo).get_name()
+ self.settings.setEncoders(aencoder=aencoder)
+ for template in gst.registry_get_default().lookup_feature(aencoder).get_static_pad_templates():
+ if template.name_template == "src":
+ self.audiotype = template.get_caps().to_string()
+ for elem in self.audiotype.split(","):
+ if "{" in elem or "[" in elem:
+ self.audiotype = self.audiotype[:self.audiotype.index(elem) - 1]
+ break
+ if not self.muxer_combo_changing:
+ # The user directly changed the audio encoder combo.
+ self.preferred_aencoder = aencoder
+
+ def _audioSettingsButtonClickedCb(self, button):
+ factory = get_combo_value(self.audio_encoder_combo)
+ self._elementSettingsDialog(factory, 'acodecsettings')
+
+ def _muxerComboChangedCb(self, muxer_combo):
+ """Handle the changing of the container format combobox."""
+ muxer = get_combo_value(muxer_combo).get_name()
+ for template in gst.registry_get_default().lookup_feature(muxer).get_static_pad_templates():
+ if template.name_template == "src":
+ self.muxertype = template.get_caps().to_string()
+ self.settings.setEncoders(muxer=muxer)
+
+ # Update the extension of the filename.
+ basename = os.path.splitext(self.fileentry.get_text())[0]
+ self.updateFilename(basename)
+
+ # Update muxer-dependent widgets.
+ self.muxer_combo_changing = True
+ try:
+ self.updateAvailableEncoders()
+ finally:
+ self.muxer_combo_changing = False
diff --git a/pitivi/ui/Makefile.am b/pitivi/ui/Makefile.am
index 5b1fb8e..4c1acd9 100644
--- a/pitivi/ui/Makefile.am
+++ b/pitivi/ui/Makefile.am
@@ -6,7 +6,6 @@ ui_PYTHON = \
controller.py \
curve.py \
depsmanager.py \
- encodingdialog.py \
filelisterrordialog.py \
mainwindow.py \
prefs.py \
diff --git a/pitivi/ui/mainwindow.py b/pitivi/ui/mainwindow.py
index b5ada4b..5cded30 100644
--- a/pitivi/ui/mainwindow.py
+++ b/pitivi/ui/mainwindow.py
@@ -218,7 +218,7 @@ class PitiviMainWindow(gtk.Window, Loggable):
@param pause: If C{True}, pause the timeline before displaying the dialog.
@type pause: C{bool}
"""
- from encodingdialog import EncodingDialog
+ from encoding import EncodingDialog
dialog = EncodingDialog(self, project)
dialog.window.connect("destroy", self._encodingDialogDestroyCb)
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 3943cf1..b47d7a7 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -15,14 +15,13 @@ data/pitivi.desktop.in.in
pitivi/application.py
pitivi/check.py
pitivi/effects.py
+pitivi/encode.py
pitivi/projectmanager.py
pitivi/project.py
pitivi/settings.py
pitivi/medialibrary.py
pitivi/ui/basetabs.py
pitivi/ui/clipproperties.py
-pitivi/ui/encodingdialog.py
-pitivi/ui/encodingprogress.py
pitivi/ui/filechooserpreview.py
pitivi/ui/filelisterrordialog.py
pitivi/ui/mainwindow.py
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]