~timo-jyrinki/ubuntu/trusty/pitivi/backport_utopic_fixes

« back to all changes in this revision

Viewing changes to pitivi/ui/encodingdialog.py

  • Committer: Package Import Robot
  • Author(s): Sebastian Dröge
  • Date: 2014-04-05 15:28:16 UTC
  • mfrom: (6.1.13 sid)
  • Revision ID: package-import@ubuntu.com-20140405152816-6lijoax4cngiz5j5
Tags: 0.93-3
* debian/control:
  + Depend on python-gi (>= 3.10), older versions do not work
    with pitivi (Closes: #732813).
  + Add missing dependency on gir1.2-clutter-gst-2.0 (Closes: #743692).
  + Add suggests on gir1.2-notify-0.7 and gir1.2-gnomedesktop-3.0.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
# PiTiVi , Non-linear video editor
3
 
#
4
 
#       ui/mainwindow.py
5
 
#
6
 
# Copyright (c) 2005, Edward Hervey <bilboed@bilboed.com>
7
 
#
8
 
# This program is free software; you can redistribute it and/or
9
 
# modify it under the terms of the GNU Lesser General Public
10
 
# License as published by the Free Software Foundation; either
11
 
# version 2.1 of the License, or (at your option) any later version.
12
 
#
13
 
# This program is distributed in the hope that it will be useful,
14
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 
# Lesser General Public License for more details.
17
 
#
18
 
# You should have received a copy of the GNU Lesser General Public
19
 
# License along with this program; if not, write to the
20
 
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21
 
# Boston, MA 02110-1301, USA.
22
 
 
23
 
"""
24
 
Render dialog
25
 
"""
26
 
 
27
 
import os
28
 
import gtk
29
 
import gst
30
 
import pango
31
 
from gettext import gettext as _
32
 
 
33
 
from pitivi import configure
34
 
from pitivi.settings import ExportSettings
35
 
from pitivi.log.loggable import Loggable
36
 
from pitivi.ui.encodingprogress import EncodingProgressDialog
37
 
from pitivi.ui.gstwidget import GstElementSettingsDialog
38
 
from pitivi.actioner import Renderer
39
 
from pitivi.ui.ripple_update_group import RippleUpdateGroup
40
 
from pitivi.ui.common import\
41
 
    model,\
42
 
    frame_rates,\
43
 
    audio_rates,\
44
 
    audio_depths,\
45
 
    audio_channels,\
46
 
    get_combo_value,\
47
 
    set_combo_value
48
 
 
49
 
from pitivi.ui.preset import RenderPresetManager, DuplicatePresetNameException
50
 
 
51
 
 
52
 
def beautify_factoryname(factory):
53
 
    """Returns a nice name for the specified gst.ElementFactory instance."""
54
 
    # only replace lowercase versions of "format", "video", "audio"
55
 
    # otherwise they might be part of a trademark name
56
 
    words_to_remove = ["Muxer", "muxer", "Encoder", "encoder",
57
 
            "format", "video", "audio", "instead"]
58
 
    name = factory.get_longname()
59
 
    for word in words_to_remove:
60
 
        name = name.replace(word, "")
61
 
    return " ".join(word for word in name.split())
62
 
 
63
 
 
64
 
def extension_for_muxer(muxer):
65
 
    """Returns the file extension appropriate for the specified muxer."""
66
 
    exts = {
67
 
        "asfmux": "asf",
68
 
        "avimux": "avi",
69
 
        "ffmux_3g2": "3g2",
70
 
        "ffmux_avm2": "avm2",
71
 
        "ffmux_dvd": "vob",
72
 
        "ffmux_flv": "flv",
73
 
        "ffmux_ipod": "mp4",
74
 
        "ffmux_mpeg": "mpeg",
75
 
        "ffmux_mpegts": "mpeg",
76
 
        "ffmux_psp": "mp4",
77
 
        "ffmux_rm": "rm",
78
 
        "ffmux_svcd": "mpeg",
79
 
        "ffmux_swf": "swf",
80
 
        "ffmux_vcd": "mpeg",
81
 
        "ffmux_vob": "vob",
82
 
        "flvmux": "flv",
83
 
        "gppmux": "3gp",
84
 
        "matroskamux": "mkv",
85
 
        "mj2mux": "mj2",
86
 
        "mp4mux": "mp4",
87
 
        "mpegpsmux": "mpeg",
88
 
        "mpegtsmux": "mpeg",
89
 
        "mvemux": "mve",
90
 
        "mxfmux": "mxf",
91
 
        "oggmux": "ogv",
92
 
        "qtmux": "mov",
93
 
        "webmmux": "webm"}
94
 
    return exts.get(muxer)
95
 
 
96
 
 
97
 
def factorylist(factories):
98
 
    """Create a gtk.ListStore() of sorted, beautified factory names.
99
 
 
100
 
    @param factories: The factories available for creating the list.
101
 
    @type factories: A sequence of gst.ElementFactory instances.
102
 
    """
103
 
    columns = (str, object)
104
 
    data = [(beautify_factoryname(factory), factory)
105
 
            for factory in factories
106
 
            if factory.get_rank() > 0]
107
 
    data.sort(key=lambda x: x[0])
108
 
    return model(columns, data)
109
 
 
110
 
 
111
 
class EncodingDialog(Renderer, Loggable):
112
 
    """Render dialog box.
113
 
 
114
 
    @ivar preferred_aencoder: The last audio encoder selected by the user.
115
 
    @type preferred_aencoder: str
116
 
    @ivar preferred_vencoder: The last video encoder selected by the user.
117
 
    @type preferred_vencoder: str
118
 
    @ivar settings: The settings used for rendering.
119
 
    @type settings: ExportSettings
120
 
    """
121
 
 
122
 
    def __init__(self, app, project, pipeline=None):
123
 
        Loggable.__init__(self)
124
 
 
125
 
        self.app = app
126
 
 
127
 
        self.builder = gtk.Builder()
128
 
        self.builder.add_from_file(os.path.join(configure.get_ui_dir(),
129
 
            "encodingdialog.ui"))
130
 
        self._setProperties()
131
 
        self.builder.connect_signals(self)
132
 
 
133
 
        # UI widgets
134
 
        icon = os.path.join(configure.get_pixmap_dir(), "pitivi-render-16.png")
135
 
        self.window.set_icon_from_file(icon)
136
 
 
137
 
        # FIXME: re-enable this widget when bug #637078 is implemented
138
 
        self.selected_only_button.destroy()
139
 
 
140
 
        # The Render dialog and the Project Settings dialog have some
141
 
        # common settings, for example the audio sample rate.
142
 
        # When these common settings are changed in the Render dialog,
143
 
        # we don't want them to be saved, so we create a copy of the project's
144
 
        # settings to be used by the Render dialog for rendering.
145
 
        render_settings = project.getSettings().copy()
146
 
        # Note: render_settings will end up as self.settings.
147
 
        Renderer.__init__(self, project,
148
 
                pipeline=pipeline, settings=render_settings)
149
 
 
150
 
        # Directory and Filename
151
 
        self.filebutton.set_current_folder(self.app.settings.lastExportFolder)
152
 
        self.updateFilename(self.project.name)
153
 
 
154
 
        # We store these so that when the user tries various container formats,
155
 
        # (AKA muxers) we select these a/v encoders, if they are compatible with
156
 
        # the current container format.
157
 
        self.preferred_vencoder = self.settings.vencoder
158
 
        self.preferred_aencoder = self.settings.aencoder
159
 
 
160
 
        self._initializeComboboxModels()
161
 
        self._displaySettings()
162
 
        self._displayRenderSettings()
163
 
 
164
 
        self.window.connect("delete-event", self._deleteEventCb)
165
 
        self.settings.connect("settings-changed", self._settingsChanged)
166
 
 
167
 
        # Monitor changes
168
 
 
169
 
        self.wg = RippleUpdateGroup()
170
 
        self.wg.addVertex(self.frame_rate_combo, signal="changed")
171
 
        self.wg.addVertex(self.save_render_preset_button,
172
 
                 update_func=self._updateRenderSaveButton)
173
 
        self.wg.addVertex(self.channels_combo, signal="changed")
174
 
        self.wg.addVertex(self.sample_rate_combo, signal="changed")
175
 
        self.wg.addVertex(self.sample_depth_combo, signal="changed")
176
 
        self.wg.addVertex(self.muxercombobox, signal="changed")
177
 
        self.wg.addVertex(self.audio_encoder_combo, signal="changed")
178
 
        self.wg.addVertex(self.video_encoder_combo, signal="changed")
179
 
        self.render_presets = RenderPresetManager()
180
 
        self.render_presets.loadAll()
181
 
 
182
 
        self._fillPresetsTreeview(
183
 
                self.render_preset_treeview, self.render_presets,
184
 
                self._updateRenderPresetButtons)
185
 
 
186
 
        self.wg.addEdge(self.frame_rate_combo,
187
 
            self.save_render_preset_button)
188
 
        self.wg.addEdge(self.audio_encoder_combo,
189
 
            self.save_render_preset_button)
190
 
        self.wg.addEdge(self.video_encoder_combo,
191
 
            self.save_render_preset_button)
192
 
        self.wg.addEdge(self.muxercombobox,
193
 
            self.save_render_preset_button)
194
 
        self.wg.addEdge(self.channels_combo,
195
 
            self.save_render_preset_button)
196
 
        self.wg.addEdge(self.sample_rate_combo,
197
 
            self.save_render_preset_button)
198
 
        self.wg.addEdge(self.sample_depth_combo,
199
 
            self.save_render_preset_button)
200
 
 
201
 
        self._infobarForPresetManager = {
202
 
                self.render_presets: self.render_preset_infobar}
203
 
 
204
 
        # Bind widgets to RenderPresetsManager
205
 
        self.bindCombo(self.render_presets, "channels",
206
 
            self.channels_combo)
207
 
        self.bindCombo(self.render_presets, "sample-rate",
208
 
            self.sample_rate_combo)
209
 
        self.bindCombo(self.render_presets, "depth",
210
 
            self.sample_depth_combo)
211
 
        self.bindCombo(self.render_presets, "acodec",
212
 
            self.audio_encoder_combo)
213
 
        self.bindCombo(self.render_presets, "vcodec",
214
 
            self.video_encoder_combo)
215
 
        self.bindCombo(self.render_presets, "container",
216
 
            self.muxercombobox)
217
 
        self.bindCombo(self.render_presets, "frame-rate",
218
 
            self.frame_rate_combo)
219
 
        self.bindHeight(self.render_presets)
220
 
        self.bindWidth(self.render_presets)
221
 
 
222
 
        self.createNoPreset(self.render_presets)
223
 
 
224
 
    def createNoPreset(self, mgr):
225
 
        mgr.prependPreset(_("No preset"), {
226
 
            "depth": int(get_combo_value(self.sample_depth_combo)),
227
 
            "channels": int(get_combo_value(self.channels_combo)),
228
 
            "sample-rate": int(get_combo_value(self.sample_rate_combo)),
229
 
            "acodec": get_combo_value(self.audio_encoder_combo).get_name(),
230
 
            "vcodec": get_combo_value(self.video_encoder_combo).get_name(),
231
 
            "container": get_combo_value(self.muxercombobox).get_name(),
232
 
            "frame-rate": gst.Fraction(int(get_combo_value(self.frame_rate_combo).num),
233
 
                                        int(get_combo_value(self.frame_rate_combo).denom)),
234
 
            "height": self.getDimension("height"),
235
 
            "width": self.getDimension("width")})
236
 
 
237
 
    def bindCombo(self, mgr, name, widget):
238
 
        if name == "container":
239
 
            mgr.bindWidget(name,
240
 
                lambda x: self.muxer_setter(widget, x),
241
 
                lambda: get_combo_value(widget).get_name())
242
 
 
243
 
        elif name == "acodec":
244
 
            mgr.bindWidget(name,
245
 
                lambda x: self.acodec_setter(widget, x),
246
 
                lambda: get_combo_value(widget).get_name())
247
 
 
248
 
        elif name == "vcodec":
249
 
            mgr.bindWidget(name,
250
 
                lambda x: self.vcodec_setter(widget, x),
251
 
                lambda: get_combo_value(widget).get_name())
252
 
 
253
 
        elif name == "depth":
254
 
            mgr.bindWidget(name,
255
 
                lambda x: self.sample_depth_setter(widget, x),
256
 
                lambda: get_combo_value(widget))
257
 
 
258
 
        elif name == "sample-rate":
259
 
            mgr.bindWidget(name,
260
 
                lambda x: self.sample_rate_setter(widget, x),
261
 
                lambda: get_combo_value(widget))
262
 
 
263
 
        elif name == "channels":
264
 
            mgr.bindWidget(name,
265
 
                lambda x: self.channels_setter(widget, x),
266
 
                lambda: get_combo_value(widget))
267
 
 
268
 
        elif name == "frame-rate":
269
 
            mgr.bindWidget(name,
270
 
                lambda x: self.framerate_setter(widget, x),
271
 
                lambda: get_combo_value(widget))
272
 
 
273
 
    def muxer_setter(self, widget, value):
274
 
        set_combo_value(widget, gst.element_factory_find(value))
275
 
        self.settings.setEncoders(muxer=value)
276
 
 
277
 
        # Update the extension of the filename.
278
 
        basename = os.path.splitext(self.fileentry.get_text())[0]
279
 
        self.updateFilename(basename)
280
 
 
281
 
        # Update muxer-dependent widgets.
282
 
        self.muxer_combo_changing = True
283
 
        try:
284
 
            self.updateAvailableEncoders()
285
 
        finally:
286
 
            self.muxer_combo_changing = False
287
 
 
288
 
    def acodec_setter(self, widget, value):
289
 
        set_combo_value(widget, gst.element_factory_find(value))
290
 
        self.settings.setEncoders(aencoder=value)
291
 
        if not self.muxer_combo_changing:
292
 
            # The user directly changed the audio encoder combo.
293
 
            self.preferred_aencoder = value
294
 
 
295
 
    def vcodec_setter(self, widget, value):
296
 
        set_combo_value(widget, gst.element_factory_find(value))
297
 
        self.settings.setEncoders(vencoder=value)
298
 
        if not self.muxer_combo_changing:
299
 
            # The user directly changed the video encoder combo.
300
 
            self.preferred_vencoder = value
301
 
 
302
 
    def sample_depth_setter(self, widget, value):
303
 
        set_combo_value(widget, value)
304
 
        self.settings.setAudioProperties(depth=value)
305
 
 
306
 
    def sample_rate_setter(self, widget, value):
307
 
        set_combo_value(widget, value)
308
 
        self.settings.setAudioProperties(rate=value)
309
 
 
310
 
    def channels_setter(self, widget, value):
311
 
        set_combo_value(widget, value)
312
 
        self.settings.setAudioProperties(nbchanns=value)
313
 
 
314
 
    def framerate_setter(self, widget, value):
315
 
        set_combo_value(widget, value)
316
 
        self.settings.setVideoProperties(framerate=value)
317
 
 
318
 
    def bindHeight(self, mgr):
319
 
        mgr.bindWidget("height",
320
 
                       lambda x: self.settings.setVideoProperties(height=x),
321
 
                       lambda: 0)
322
 
 
323
 
    def bindWidth(self, mgr):
324
 
        mgr.bindWidget("width",
325
 
                       lambda x: self.settings.setVideoProperties(width=x),
326
 
                       lambda: 0)
327
 
 
328
 
    def getDimension(self, dimension):
329
 
        value = self.settings.getVideoWidthAndHeight()
330
 
        if dimension == "height":
331
 
            return value[1]
332
 
        elif dimension == "width":
333
 
            return value[0]
334
 
 
335
 
    def _fillPresetsTreeview(self, treeview, mgr, update_buttons_func):
336
 
        """Set up the specified treeview to display the specified presets.
337
 
 
338
 
        @param treeview: The treeview for displaying the presets.
339
 
        @type treeview: TreeView
340
 
        @param mgr: The preset manager.
341
 
        @type mgr: PresetManager
342
 
        @param update_buttons_func: A function which updates the buttons for
343
 
        removing and saving a preset, enabling or disabling them accordingly.
344
 
        @type update_buttons_func: function
345
 
        """
346
 
        renderer = gtk.CellRendererText()
347
 
        renderer.props.editable = True
348
 
        column = gtk.TreeViewColumn("Preset", renderer, text=0)
349
 
        treeview.append_column(column)
350
 
        treeview.props.headers_visible = False
351
 
        model = mgr.getModel()
352
 
        treeview.set_model(model)
353
 
        model.connect("row-inserted", self._newPresetCb,
354
 
            column, renderer, treeview)
355
 
        renderer.connect("edited", self._presetNameEditedCb, mgr)
356
 
        renderer.connect("editing-started", self._presetNameEditingStartedCb,
357
 
            mgr)
358
 
        treeview.get_selection().connect("changed", self._presetChangedCb,
359
 
            mgr, update_buttons_func)
360
 
        treeview.connect("focus-out-event", self._treeviewDefocusedCb, mgr)
361
 
 
362
 
    def _newPresetCb(self, model, path, iter_, column, renderer, treeview):
363
 
        """Handle the addition of a preset to the model of the preset manager.
364
 
        """
365
 
        treeview.set_cursor_on_cell(path, column, renderer, start_editing=True)
366
 
        treeview.grab_focus()
367
 
 
368
 
    def _presetNameEditedCb(self, renderer, path, new_text, mgr):
369
 
        """Handle the renaming of a preset."""
370
 
        try:
371
 
            mgr.renamePreset(path, new_text)
372
 
            self._updateRenderPresetButtons()
373
 
        except DuplicatePresetNameException:
374
 
            error_markup = _('"%s" already exists.') % new_text
375
 
            self._showPresetManagerError(mgr, error_markup)
376
 
 
377
 
    def _presetNameEditingStartedCb(self, renderer, editable, path, mgr):
378
 
        """Handle the start of a preset renaming."""
379
 
        self._hidePresetManagerError(mgr)
380
 
 
381
 
    def _treeviewDefocusedCb(self, widget, event, mgr):
382
 
        """Handle the treeview loosing the focus."""
383
 
        self._hidePresetManagerError(mgr)
384
 
 
385
 
    def _showPresetManagerError(self, mgr, error_markup):
386
 
        """Show the specified error on the infobar associated with the manager.
387
 
 
388
 
        @param mgr: The preset manager for which to show the error.
389
 
        @type mgr: PresetManager
390
 
        """
391
 
        infobar = self._infobarForPresetManager[mgr]
392
 
        # The infobar must contain exactly one object in the content area:
393
 
        # a label for displaying the error.
394
 
        label = infobar.get_content_area().children()[0]
395
 
        label.set_markup(error_markup)
396
 
        infobar.show()
397
 
 
398
 
    def _hidePresetManagerError(self, mgr):
399
 
        """Hide the error infobar associated with the manager.
400
 
 
401
 
        @param mgr: The preset manager for which to hide the error infobar.
402
 
        @type mgr: PresetManager
403
 
        """
404
 
        infobar = self._infobarForPresetManager[mgr]
405
 
        infobar.hide()
406
 
 
407
 
    def _updateRenderSaveButton(self, unused_in, button):
408
 
        button.set_sensitive(self.render_presets.isSaveButtonSensitive())
409
 
 
410
 
    @staticmethod
411
 
    def _getUniquePresetName(mgr):
412
 
        """Get a unique name for a new preset for the specified PresetManager.
413
 
        """
414
 
        existing_preset_names = list(mgr.getPresetNames())
415
 
        preset_name = _("New preset")
416
 
        i = 1
417
 
        while preset_name in existing_preset_names:
418
 
            preset_name = _("New preset %d") % i
419
 
            i += 1
420
 
        return preset_name
421
 
 
422
 
    def _addRenderPresetButtonClickedCb(self, button):
423
 
        preset_name = self._getUniquePresetName(self.render_presets)
424
 
        self.render_presets.addPreset(preset_name, {
425
 
            "depth": int(get_combo_value(self.sample_depth_combo)),
426
 
            "channels": int(get_combo_value(self.channels_combo)),
427
 
            "sample-rate": int(get_combo_value(self.sample_rate_combo)),
428
 
            "acodec": get_combo_value(self.audio_encoder_combo).get_name(),
429
 
            "vcodec": get_combo_value(self.video_encoder_combo).get_name(),
430
 
            "container": get_combo_value(self.muxercombobox).get_name(),
431
 
            "frame-rate": gst.Fraction(int(get_combo_value(self.frame_rate_combo).num),
432
 
                                        int(get_combo_value(self.frame_rate_combo).denom)),
433
 
            "height": 0,
434
 
            "width": 0})
435
 
 
436
 
        self.render_presets.restorePreset(preset_name)
437
 
        self._updateRenderPresetButtons()
438
 
 
439
 
    def _saveRenderPresetButtonClickedCb(self, button):
440
 
        self.render_presets.savePreset()
441
 
        self.save_render_preset_button.set_sensitive(False)
442
 
        self.remove_render_preset_button.set_sensitive(True)
443
 
 
444
 
    def _updateRenderPresetButtons(self):
445
 
        can_save = self.render_presets.isSaveButtonSensitive()
446
 
        self.save_render_preset_button.set_sensitive(can_save)
447
 
        can_remove = self.render_presets.isRemoveButtonSensitive()
448
 
        self.remove_render_preset_button.set_sensitive(can_remove)
449
 
 
450
 
    def _removeRenderPresetButtonClickedCb(self, button):
451
 
        selection = self.render_preset_treeview.get_selection()
452
 
        model, iter_ = selection.get_selected()
453
 
        if iter_:
454
 
            self.render_presets.removePreset(model[iter_][0])
455
 
 
456
 
    def _presetChangedCb(self, selection, mgr, update_preset_buttons_func):
457
 
        """Handle the selection of a preset."""
458
 
        model, iter_ = selection.get_selected()
459
 
        if iter_:
460
 
            self.selected_preset = model[iter_][0]
461
 
        else:
462
 
            self.selected_preset = None
463
 
 
464
 
        mgr.restorePreset(self.selected_preset)
465
 
        self._displaySettings()
466
 
        update_preset_buttons_func()
467
 
        self._hidePresetManagerError(mgr)
468
 
 
469
 
    def _setProperties(self):
470
 
        self.window = self.builder.get_object("render-dialog")
471
 
        self.selected_only_button = self.builder.get_object(
472
 
            "selected_only_button")
473
 
        self.frame_rate_combo = self.builder.get_object("frame_rate_combo")
474
 
        self.scale_spinbutton = self.builder.get_object("scale_spinbutton")
475
 
        self.channels_combo = self.builder.get_object("channels_combo")
476
 
        self.sample_rate_combo = self.builder.get_object(
477
 
                        "sample_rate_combo")
478
 
        self.sample_depth_combo = self.builder.get_object(
479
 
                        "sample_depth_combo")
480
 
        self.muxercombobox = self.builder.get_object("muxercombobox")
481
 
        self.audio_encoder_combo = self.builder.get_object(
482
 
            "audio_encoder_combo")
483
 
        self.video_encoder_combo = self.builder.get_object(
484
 
            "video_encoder_combo")
485
 
        self.filebutton = self.builder.get_object("filebutton")
486
 
        self.fileentry = self.builder.get_object("fileentry")
487
 
        self.resolution_label = self.builder.get_object("resolution_label")
488
 
        self.render_preset_treeview = self.builder.get_object(
489
 
                                        "render_preset_treeview")
490
 
        self.save_render_preset_button = self.builder.get_object(
491
 
                                        "save_render_preset_button")
492
 
        self.remove_render_preset_button = self.builder.get_object(
493
 
                                        "remove_render_preset_button")
494
 
        self.render_preset_infobar = self.builder.get_object(
495
 
            "render-preset-infobar")
496
 
 
497
 
    def _settingsChanged(self, settings):
498
 
        self.updateResolution()
499
 
 
500
 
    def _initializeComboboxModels(self):
501
 
        self.frame_rate_combo.set_model(frame_rates)
502
 
        self.channels_combo.set_model(audio_channels)
503
 
        self.sample_rate_combo.set_model(audio_rates)
504
 
        self.sample_depth_combo.set_model(audio_depths)
505
 
        self.muxercombobox.set_model(factorylist(ExportSettings.muxers))
506
 
 
507
 
    def _displaySettings(self):
508
 
        """Display the settings that also change in the ProjectSettingsDialog.
509
 
        """
510
 
        # Video settings
511
 
        set_combo_value(self.frame_rate_combo, self.settings.videorate)
512
 
        # Audio settings
513
 
        set_combo_value(self.channels_combo, self.settings.audiochannels)
514
 
        set_combo_value(self.sample_rate_combo, self.settings.audiorate)
515
 
        set_combo_value(self.sample_depth_combo, self.settings.audiodepth)
516
 
 
517
 
    def _displayRenderSettings(self):
518
 
        """Display the settings which can be changed only in the EncodingDialog.
519
 
        """
520
 
        # Video settings
521
 
        # note: this will trigger an update of the video resolution label
522
 
        self.scale_spinbutton.set_value(self.settings.render_scale)
523
 
        # Muxer settings
524
 
        # note: this will trigger an update of the codec comboboxes
525
 
        set_combo_value(self.muxercombobox,
526
 
            gst.element_factory_find(self.settings.muxer))
527
 
 
528
 
        # File
529
 
        self.filebutton.set_current_folder(self.app.settings.lastExportFolder)
530
 
        self.updateFilename(self.project.name)
531
 
 
532
 
    def _checkForExistingFile(self, *args):
533
 
        """
534
 
        Display a warning icon and tooltip if the file path already exists.
535
 
        """
536
 
        path = self.filebutton.get_current_folder()
537
 
        if not path:
538
 
            # This happens when the window is initialized.
539
 
            return
540
 
        warning_icon = gtk.STOCK_DIALOG_WARNING
541
 
        filename = self.fileentry.get_text()
542
 
        if not filename:
543
 
            tooltip_text = _("A file name is required.")
544
 
        elif filename and os.path.exists(os.path.join(path, filename)):
545
 
            tooltip_text = _("This file already exists.\n"
546
 
                             "If you don't want to overwrite it, choose a "
547
 
                             "different file name or folder.")
548
 
        else:
549
 
            warning_icon = None
550
 
            tooltip_text = None
551
 
        self.fileentry.set_icon_from_stock(1, warning_icon)
552
 
        self.fileentry.set_icon_tooltip_text(1, tooltip_text)
553
 
 
554
 
    def updateFilename(self, basename):
555
 
        """Updates the filename UI element to show the specified file name."""
556
 
        extension = extension_for_muxer(self.settings.muxer)
557
 
        if extension:
558
 
            name = "%s%s%s" % (basename, os.path.extsep, extension)
559
 
        else:
560
 
            name = basename
561
 
        self.fileentry.set_text(name)
562
 
 
563
 
    def _muxerComboChangedCb(self, muxer_combo):
564
 
        """Handle the changing of the container format combobox."""
565
 
        muxer = get_combo_value(muxer_combo).get_name()
566
 
        self.settings.setEncoders(muxer=muxer)
567
 
 
568
 
        # Update the extension of the filename.
569
 
        basename = os.path.splitext(self.fileentry.get_text())[0]
570
 
        self.updateFilename(basename)
571
 
 
572
 
        # Update muxer-dependent widgets.
573
 
        self.muxer_combo_changing = True
574
 
        try:
575
 
            self.updateAvailableEncoders()
576
 
        finally:
577
 
            self.muxer_combo_changing = False
578
 
 
579
 
    def updateAvailableEncoders(self):
580
 
        """Update the encoder comboboxes to show the available encoders."""
581
 
        video_encoders = self.settings.getVideoEncoders()
582
 
        video_encoder_model = factorylist(video_encoders)
583
 
        self.video_encoder_combo.set_model(video_encoder_model)
584
 
 
585
 
        audio_encoders = self.settings.getAudioEncoders()
586
 
        audio_encoder_model = factorylist(audio_encoders)
587
 
        self.audio_encoder_combo.set_model(audio_encoder_model)
588
 
 
589
 
        self._updateEncoderCombo(
590
 
                self.video_encoder_combo, self.preferred_vencoder)
591
 
        self._updateEncoderCombo(
592
 
                self.audio_encoder_combo, self.preferred_aencoder)
593
 
 
594
 
    def _updateEncoderCombo(self, encoder_combo, preferred_encoder):
595
 
        """Select the specified encoder for the specified encoder combo."""
596
 
        if preferred_encoder:
597
 
            # A preferrence exists, pick it if it can be found in
598
 
            # the current model of the combobox.
599
 
            vencoder = gst.element_factory_find(preferred_encoder)
600
 
            set_combo_value(encoder_combo, vencoder, default_index=0)
601
 
        else:
602
 
            # No preferrence exists, pick the first encoder from
603
 
            # the current model of the combobox.
604
 
            encoder_combo.set_active(0)
605
 
 
606
 
    def _scaleSpinbuttonChangedCb(self, button):
607
 
        render_scale = self.scale_spinbutton.get_value()
608
 
        self.settings.setVideoProperties(render_scale=render_scale)
609
 
        self.updateResolution()
610
 
 
611
 
    def updateResolution(self):
612
 
        width, height = self.settings.getVideoWidthAndHeight(render=True)
613
 
        self.resolution_label.set_text(u"%d×%d" % (width, height))
614
 
 
615
 
    def _projectSettingsButtonClickedCb(self, button):
616
 
        from pitivi.ui.projectsettings import ProjectSettingsDialog
617
 
        dialog = ProjectSettingsDialog(self.window, self.project)
618
 
        dialog.window.connect("destroy", self._projectSettingsDestroyCb)
619
 
        dialog.window.run()
620
 
 
621
 
    def _projectSettingsDestroyCb(self, dialog):
622
 
        """Handle the destruction of the ProjectSettingsDialog."""
623
 
        settings = self.project.getSettings()
624
 
        self.settings.setVideoProperties(width=settings.videowidth,
625
 
                                         height=settings.videoheight,
626
 
                                         framerate=settings.videorate)
627
 
        self.settings.setAudioProperties(nbchanns=settings.audiochannels,
628
 
                                         rate=settings.audiorate,
629
 
                                         depth=settings.audiodepth)
630
 
        self._displaySettings()
631
 
 
632
 
    def _frameRateComboChangedCb(self, combo):
633
 
        framerate = get_combo_value(combo)
634
 
        self.settings.setVideoProperties(framerate=framerate)
635
 
 
636
 
    def _videoEncoderComboChangedCb(self, combo):
637
 
        vencoder = get_combo_value(combo).get_name()
638
 
        self.settings.setEncoders(vencoder=vencoder)
639
 
        if not self.muxer_combo_changing:
640
 
            # The user directly changed the video encoder combo.
641
 
            self.preferred_vencoder = vencoder
642
 
 
643
 
    def _videoSettingsButtonClickedCb(self, button):
644
 
        factory = get_combo_value(self.video_encoder_combo)
645
 
        self._elementSettingsDialog(factory, 'vcodecsettings')
646
 
 
647
 
    def _channelsComboChangedCb(self, combo):
648
 
        self.settings.setAudioProperties(nbchanns=get_combo_value(combo))
649
 
 
650
 
    def _sampleDepthComboChangedCb(self, combo):
651
 
        self.settings.setAudioProperties(depth=get_combo_value(combo))
652
 
 
653
 
    def _sampleRateComboChangedCb(self, combo):
654
 
        self.settings.setAudioProperties(rate=get_combo_value(combo))
655
 
 
656
 
    def _audioEncoderChangedComboCb(self, combo):
657
 
        aencoder = get_combo_value(combo).get_name()
658
 
        self.settings.setEncoders(aencoder=aencoder)
659
 
        if not self.muxer_combo_changing:
660
 
            # The user directly changed the audio encoder combo.
661
 
            self.preferred_aencoder = aencoder
662
 
 
663
 
    def _audioSettingsButtonClickedCb(self, button):
664
 
        factory = get_combo_value(self.audio_encoder_combo)
665
 
        self._elementSettingsDialog(factory, 'acodecsettings')
666
 
 
667
 
    def _elementSettingsDialog(self, factory, settings_attr):
668
 
        """Open a dialog to edit the properties for the specified factory.
669
 
 
670
 
        @param factory: An element factory whose properties the user will edit.
671
 
        @type factory: gst.ElementFactory
672
 
        @param settings_attr: The ExportSettings attribute holding
673
 
        the properties.
674
 
        @type settings_attr: str
675
 
        """
676
 
        properties = getattr(self.settings, settings_attr)
677
 
        self.dialog = GstElementSettingsDialog(factory, properties=properties)
678
 
        self.dialog.window.set_transient_for(self.window)
679
 
        self.dialog.ok_btn.connect("clicked", self._okButtonClickedCb, settings_attr)
680
 
        self.dialog.window.run()
681
 
 
682
 
    def _okButtonClickedCb(self, unused_button, settings_attr):
683
 
        setattr(self.settings, settings_attr, self.dialog.getSettings())
684
 
        self.dialog.window.destroy()
685
 
 
686
 
    def _renderButtonClickedCb(self, unused_button):
687
 
        self.outfile = os.path.join(self.filebutton.get_uri(),
688
 
                                    self.fileentry.get_text())
689
 
        self.progress = EncodingProgressDialog(self.app, self)
690
 
        self.window.hide()  # Hide the rendering settings dialog while rendering
691
 
        self.progress.window.show()
692
 
        self.startAction()
693
 
        self.progress.connect("cancel", self._cancelRender)
694
 
        self.progress.connect("pause", self._pauseRender)
695
 
        self.pipeline.connect("state-changed", self._stateChanged)
696
 
 
697
 
    def _cancelRender(self, progress):
698
 
        self.debug("aborting render")
699
 
        self.shutdown()
700
 
 
701
 
    def _pauseRender(self, progress):
702
 
        self.pipeline.togglePlayback()
703
 
 
704
 
    def _stateChanged(self, pipeline, state):
705
 
        self.progress.setState(state)
706
 
 
707
 
    def updatePosition(self, fraction, text):
708
 
        if self.progress:
709
 
            self.progress.updatePosition(fraction, text)
710
 
 
711
 
    def updateUIOnEOS(self):
712
 
        """Handle the ending or the cancellation of the render process."""
713
 
        self.progress.window.destroy()
714
 
        self.progress = None
715
 
        self.window.show()  # Show the encoding dialog again
716
 
        self.pipeline.disconnect_by_function(self._stateChanged)
717
 
 
718
 
    def _closeButtonClickedCb(self, unused_button):
719
 
        self.debug("Render Close button clicked")
720
 
        self.destroy()
721
 
 
722
 
    def _deleteEventCb(self, window, event):
723
 
        self.debug("Render window is being deleted")
724
 
        self.destroy()
725
 
 
726
 
    def _updateProjectSettings(self):
727
 
        """Updates the settings of the project if the render settings changed.
728
 
        """
729
 
        settings = self.project.getSettings()
730
 
        if (settings.muxer == self.settings.muxer
731
 
            and settings.aencoder == self.settings.aencoder
732
 
            and settings.vencoder == self.settings.vencoder
733
 
            and settings.containersettings == self.settings.containersettings
734
 
            and settings.acodecsettings == self.settings.acodecsettings
735
 
            and settings.vcodecsettings == self.settings.vcodecsettings
736
 
            and settings.render_scale == self.settings.render_scale):
737
 
            # No setting which can be changed in the Render dialog
738
 
            # and which we want to save have been changed.
739
 
            return
740
 
        settings.setEncoders(muxer=self.settings.muxer,
741
 
                             aencoder=self.settings.aencoder,
742
 
                             vencoder=self.settings.vencoder)
743
 
        settings.containersettings = self.settings.containersettings
744
 
        settings.acodecsettings = self.settings.acodecsettings
745
 
        settings.vcodecsettings = self.settings.vcodecsettings
746
 
        settings.setVideoProperties(render_scale=self.settings.render_scale)
747
 
        # Signal that the project settings have been changed.
748
 
        self.project.setSettings(settings)
749
 
 
750
 
    def destroy(self):
751
 
        self._updateProjectSettings()
752
 
        self.window.destroy()