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

« back to all changes in this revision

Viewing changes to pitivi/ui/encodingdialog.py

  • Committer: Bazaar Package Importer
  • Author(s): Sebastien Bacher
  • Date: 2011-05-26 15:29:58 UTC
  • mfrom: (3.1.20 experimental)
  • Revision ID: james.westby@ubuntu.com-20110526152958-90je1myzzjly26vw
Tags: 0.13.9.90-1ubuntu1
* Resynchronize on Debian
* debian/control:
  - Depend on python-launchpad-integration
  - Drop hal from Recommends to Suggests. This version has the patch applied
    to not crash without hal.
* debian/patches/01_lpi.patch:
  - launchpad integration  
* debian/rules:
  - Use gnome.mk so a translation template is built

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
"""
25
25
 
26
26
import os
27
 
import time
28
27
import gtk
29
28
import gst
30
 
from urlparse import urlparse
31
 
import urllib
32
29
 
33
30
from gettext import gettext as _
34
31
 
35
32
import pitivi.configure as configure
36
33
from pitivi.log.loggable import Loggable
37
 
from pitivi.ui.exportsettingswidget import ExportSettingsDialog
 
34
from pitivi.ui.encodingprogress import EncodingProgressDialog
 
35
from pitivi.ui.gstwidget import GstElementSettingsDialog
38
36
from pitivi.ui.glade import GladeWindow
39
 
from pitivi.action import render_action_for_uri, ViewAction
40
 
from pitivi.factories.base import SourceFactory
41
 
from pitivi.factories.timeline import TimelineSourceFactory
42
 
from pitivi.settings import export_settings_to_render_settings
43
 
from pitivi.stream import VideoStream, AudioStream
44
 
from pitivi.utils import beautify_length
45
 
 
46
 
class EncodingDialog(GladeWindow, Loggable):
 
37
from pitivi.actioner import Renderer
 
38
from pitivi.ui.common import\
 
39
    model,\
 
40
    frame_rates,\
 
41
    audio_rates,\
 
42
    audio_depths,\
 
43
    audio_channels,\
 
44
    get_combo_value,\
 
45
    set_combo_value
 
46
 
 
47
def beautify_factoryname(factory):
 
48
    # only replace lowercase versions of "format", "video", "audio"
 
49
    # otherwise they might be part of a trademark name
 
50
    words = ["Muxer", "muxer", "Encoder", "encoder",
 
51
            "format", "video", "audio", "instead"]
 
52
    name = factory.get_longname()
 
53
    for word in words:
 
54
        name = name.replace(word, "")
 
55
    parts = name.split(" ")
 
56
    ret = " ".join(p.strip() for p in parts).strip()
 
57
 
 
58
    return ret
 
59
 
 
60
def filter_recommended(muxers):
 
61
    return [m for m in muxers if m.get_rank() > 0]
 
62
 
 
63
def extension_for_muxer(muxer):
 
64
    """Returns the file extension appropriate for the specified muxer."""
 
65
    exts = {
 
66
        "asfmux": "asf",
 
67
        "avimux" : "avi",
 
68
        "ffmux_3g2": "3g2",
 
69
        "ffmux_avm2": "avm2",
 
70
        "ffmux_dvd": "vob",
 
71
        "ffmux_flv": "flv",
 
72
        "ffmux_ipod": "mp4",
 
73
        "ffmux_mpeg": "mpeg",
 
74
        "ffmux_mpegts": "mpeg",
 
75
        "ffmux_psp": "mp4",
 
76
        "ffmux_rm": "rm",
 
77
        "ffmux_svcd": "mpeg",
 
78
        "ffmux_swf": "swf",
 
79
        "ffmux_vcd": "mpeg",
 
80
        "ffmux_vob": "vob",
 
81
        "flvmux": "flv",
 
82
        "gppmux": "3gp",
 
83
        "matroskamux" : "mkv",
 
84
        "mj2mux": "mj2",
 
85
        "mp4mux": "mp4",
 
86
        "mpegpsmux": "mpeg",
 
87
        "mpegtsmux": "mpeg",
 
88
        "mvemux": "mve",
 
89
        "mxfmux" : "mxf",
 
90
        "oggmux" : "ogv",
 
91
        "qtmux"  : "mov",
 
92
        "webmmux": "webm"}
 
93
    return exts.get(muxer)
 
94
 
 
95
def factorylist(factories):
 
96
    """ Given a sequence of factories, returns a gtk.ListStore()
 
97
    of sorted, beautified factory names """
 
98
    columns = (str, object)
 
99
    data = [(beautify_factoryname(factory), factory)
 
100
            for factory in filter_recommended(factories)]
 
101
    data.sort(key=lambda x: x[0])
 
102
    return model(columns, data)
 
103
 
 
104
import pango
 
105
 
 
106
def ellipsize(combo):
 
107
    cell_view = combo.get_children()[0]
 
108
    cell_renderer = cell_view.get_cell_renderers()[0]
 
109
    cell_renderer.props.ellipsize = pango.ELLIPSIZE_END
 
110
 
 
111
class EncodingDialog(GladeWindow, Renderer, Loggable):
47
112
    """ Encoding dialog box """
48
113
    glade_file = "encodingdialog.glade"
49
114
 
52
117
        GladeWindow.__init__(self)
53
118
 
54
119
        self.app = app
 
120
        self.project = project
 
121
        self.settings = self.project.getSettings()
55
122
 
56
123
        # UI widgets
57
 
        self.progressbar = self.widgets["progressbar"]
58
 
        self.filebutton = self.widgets["filebutton"]
59
 
        self.settingsbutton = self.widgets["settingsbutton"]
60
 
        self.cancelbutton = self.widgets["cancelbutton"]
61
 
        self.recordbutton = self.widgets["recordbutton"]
62
 
        self.recordbutton.set_sensitive(False)
63
 
        self.vinfo = self.widgets["videoinfolabel"]
64
 
        self.ainfo = self.widgets["audioinfolabel"]
65
124
        self.window.set_icon_from_file(configure.get_pixmap_dir() + "/pitivi-render-16.png")
66
125
 
67
 
        # grab the Pipeline and settings
68
 
        self.project = project
69
 
        if pipeline != None:
70
 
            self.pipeline = pipeline
71
 
        else:
72
 
            self.pipeline = self.project.pipeline
73
 
        self.detectStreamTypes()
74
 
 
75
 
        self.outfile = None
76
 
        self.rendering = False
77
 
        self.renderaction = None
78
 
        self.settings = project.getSettings()
 
126
        # FIXME: re-enable this widget when bug #637078 is implemented
 
127
        self.selected_only_button.destroy()
 
128
 
 
129
        Renderer.__init__(self, project, pipeline)
 
130
 
 
131
        # Encoder settings
 
132
        self.preferred_vencoder = self.settings.vencoder
 
133
        self.preferred_aencoder = self.settings.aencoder
 
134
 
79
135
        self.timestarted = 0
80
136
        self._displaySettings()
81
137
 
82
138
        self.window.connect("delete-event", self._deleteEventCb)
 
139
        self.settings.connect("settings-changed", self._settingsChanged)
 
140
        self.settings.connect("encoders-changed", self._settingsChanged)
83
141
 
84
 
    def _shutDown(self):
85
 
        self.debug("shutting down")
86
 
        # Abort recording
87
 
        self.removeRecordAction()
88
 
        self.destroy()
 
142
    def _settingsChanged(self, settings):
 
143
        self.updateResolution()
89
144
 
90
145
    def _displaySettings(self):
91
 
        if self.have_video:
92
 
            self.vinfo.set_markup(self.settings.getVideoDescription())
93
 
        else:
94
 
            self.vinfo.set_markup("no video")
95
 
 
96
 
        if self.have_audio:
97
 
            self.ainfo.set_markup(self.settings.getAudioDescription())
98
 
        else:
99
 
            self.ainfo.set_markup("no audio")
100
 
 
101
 
    def _fileButtonClickedCb(self, button):
102
 
        dialog = gtk.FileChooserDialog(title=_("Choose file to render to"),
103
 
                                       parent=self.window,
104
 
                                       buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
105
 
                                                gtk.STOCK_OK, gtk.RESPONSE_ACCEPT),
106
 
                                       action=gtk.FILE_CHOOSER_ACTION_SAVE)
107
 
        dialog.set_icon_name("pitivi")
108
 
        if self.outfile:
109
 
            fullfilename = urlparse(self.outfile).path
110
 
            dialog.set_filename(urllib.url2pathname(fullfilename))
111
 
            dialog.set_current_name(urllib.url2pathname(os.path.basename(fullfilename)))
112
 
        else:
113
 
            dialog.set_current_folder(self.app.settings.lastExportFolder)
114
 
 
115
 
        res = dialog.run()
116
 
        dialog.hide()
117
 
        if res == gtk.RESPONSE_ACCEPT:
118
 
            self.outfile = dialog.get_uri()
119
 
            shortfilename = os.path.basename(urlparse(self.outfile).path)
120
 
            button.set_label(urllib.url2pathname(shortfilename))
121
 
            self.recordbutton.set_sensitive(True)
122
 
            self.progressbar.set_text("")
123
 
            self.app.settings.lastExportFolder = dialog.get_current_folder()
124
 
        dialog.destroy()
125
 
 
126
 
    def _positionCb(self, unused_pipeline, position):
127
 
        self.debug("%r %r", unused_pipeline, position)
128
 
        timediff = time.time() - self.timestarted
129
 
        length = self.project.timeline.duration
130
 
        self.progressbar.set_fraction(float(min(position, length)) / float(length))
131
 
        if timediff > 5.0 and position:
132
 
            # only display ETA after 5s in order to have enough averaging and
133
 
            # if the position is non-null
134
 
            totaltime = (timediff * float(length) / float(position)) - timediff
135
 
            length = beautify_length(int(totaltime * gst.SECOND))
136
 
            if length:
137
 
                self.progressbar.set_text(_("About %s left") % length)
138
 
 
139
 
    def _changeSourceSettings(self, settings):
140
 
        videocaps = settings.getVideoCaps()
141
 
        for source in self.project.sources.getSources():
142
 
            source.setFilterCaps(videocaps)
143
 
 
144
 
    def _recordButtonClickedCb(self, unused_button):
145
 
        self.debug("Rendering")
146
 
        if self.outfile and not self.rendering:
147
 
            self.addRecordAction()
148
 
            self.pipeline.play()
149
 
            self.timestarted = time.time()
150
 
            self.rendering = True
151
 
            self.cancelbutton.set_label("gtk-cancel")
152
 
            self.progressbar.set_text(_("Rendering"))
153
 
            self.recordbutton.set_sensitive(False)
154
 
            self.filebutton.set_sensitive(False)
155
 
            self.settingsbutton.set_sensitive(False)
156
 
 
157
 
    def _settingsButtonClickedCb(self, unused_button):
158
 
        dialog = ExportSettingsDialog(self.app, self.settings)
159
 
        res = dialog.run()
160
 
        dialog.hide()
161
 
        if res == gtk.RESPONSE_ACCEPT:
162
 
            self.settings = dialog.getSettings()
163
 
            self._displaySettings()
164
 
        dialog.destroy()
165
 
 
166
 
    def _eosCb(self, unused_pipeline):
167
 
        self.debug("EOS !")
168
 
        self.rendering = False
169
 
        self.progressbar.set_text(_("Rendering Complete"))
170
 
        self.progressbar.set_fraction(1.0)
171
 
        self.recordbutton.set_sensitive(False)
172
 
        self.filebutton.set_sensitive(True)
173
 
        self.settingsbutton.set_sensitive(True)
174
 
        self.cancelbutton.set_label("gtk-close")
175
 
        self.removeRecordAction()
 
146
        # Video settings
 
147
        self.frame_rate_combo.set_model(frame_rates)
 
148
        set_combo_value(self.frame_rate_combo, self.settings.videorate)
 
149
        # note: this will trigger an update of the video resolution label
 
150
        self.scale_spinbutton.set_value(self.settings.render_scale)
 
151
 
 
152
        # Audio settings
 
153
        self.channels_combo.set_model(audio_channels)
 
154
        set_combo_value(self.channels_combo, self.settings.audiochannels)
 
155
 
 
156
        self.sample_rate_combo.set_model(audio_rates)
 
157
        set_combo_value(self.sample_rate_combo, self.settings.audiorate)
 
158
 
 
159
        self.sample_depth_combo.set_model(audio_depths)
 
160
        set_combo_value(self.sample_depth_combo, self.settings.audiodepth)
 
161
 
 
162
        # Muxer
 
163
        self.muxercombobox.set_model(factorylist(self.settings.muxers))
 
164
        # note: this will trigger an update of the codec comboboxes
 
165
        set_combo_value(self.muxercombobox,
 
166
            gst.element_factory_find(self.settings.muxer))
 
167
 
 
168
        ellipsize(self.muxercombobox)
 
169
        ellipsize(self.audio_encoder_combo)
 
170
        ellipsize(self.video_encoder_combo)
 
171
 
 
172
        # File
 
173
        self.filebutton.set_current_folder(self.app.settings.lastExportFolder)
 
174
        self.updateFilename(self.project.name)
 
175
 
 
176
    def updateFilename(self, basename):
 
177
        """Updates the filename UI element to show the specified file name."""
 
178
        extension = extension_for_muxer(self.settings.muxer)
 
179
        if extension:
 
180
            name = "%s%s%s" % (basename, os.path.extsep, extension)
 
181
        else:
 
182
            name = basename
 
183
        self.fileentry.set_text(name)
 
184
 
 
185
    def _muxerComboChangedCb(self, muxer_combo):
 
186
        muxer = get_combo_value(muxer_combo).get_name()
 
187
        self.settings.setEncoders(muxer=muxer)
 
188
 
 
189
        # Update the extension of the filename.
 
190
        basename = os.path.splitext(self.fileentry.get_text())[0]
 
191
        self.updateFilename(basename)
 
192
 
 
193
        # Update muxer-dependent widgets.
 
194
        self.muxer_combo_changing = True
 
195
        try:
 
196
            self.updateAvailableEncoders()
 
197
        finally:
 
198
            self.muxer_combo_changing = False
 
199
 
 
200
    def updateAvailableEncoders(self):
 
201
        """Update the encoder comboboxes to show the available encoders."""
 
202
        video_encoders = self.settings.getVideoEncoders()
 
203
        video_encoder_model = factorylist(video_encoders)
 
204
        self.video_encoder_combo.set_model(video_encoder_model)
 
205
 
 
206
        audio_encoders = self.settings.getAudioEncoders()
 
207
        audio_encoder_model = factorylist(audio_encoders)
 
208
        self.audio_encoder_combo.set_model(audio_encoder_model)
 
209
 
 
210
        self._updateEncoderCombo(
 
211
                self.video_encoder_combo, self.preferred_vencoder)
 
212
        self._updateEncoderCombo(
 
213
                self.audio_encoder_combo, self.preferred_aencoder)
 
214
 
 
215
    def _updateEncoderCombo(self, encoder_combo, preferred_encoder):
 
216
        """Select the specified encoder for the specified encoder combo."""
 
217
        if preferred_encoder:
 
218
            # A preferrence exists, pick it if it can be found in
 
219
            # the current model of the combobox.
 
220
            vencoder = gst.element_factory_find(preferred_encoder)
 
221
            set_combo_value(encoder_combo, vencoder, default_index=0)
 
222
        else:
 
223
            # No preferrence exists, pick the first encoder from
 
224
            # the current model of the combobox.
 
225
            encoder_combo.set_active(0)
 
226
 
 
227
    def _scaleSpinbuttonChangedCb(self, button):
 
228
        render_scale = self.scale_spinbutton.get_value()
 
229
        self.settings.setVideoProperties(render_scale=render_scale)
 
230
        self.updateResolution()
 
231
 
 
232
    def updateResolution(self):
 
233
        width, height = self.settings.getVideoWidthAndHeight(render=True)
 
234
        self.resolution_label.set_text("%d x %d" % (width, height))
 
235
 
 
236
    def _projectSettingsButtonClickedCb(self, button):
 
237
        from pitivi.ui.projectsettings import ProjectSettingsDialog
 
238
        d = ProjectSettingsDialog(self.window, self.project)
 
239
        d.window.connect("destroy", self._projectSettingsDestroyCb)
 
240
        d.run()
 
241
 
 
242
    def _projectSettingsDestroyCb(self, dialog):
 
243
        self._displaySettings()
 
244
 
 
245
    def _frameRateComboChangedCb(self, combo):
 
246
        framerate = get_combo_value(combo)
 
247
        self.settings.setVideoProperties(framerate=framerate)
 
248
 
 
249
    def _videoEncoderComboChangedCb(self, combo):
 
250
        vencoder = get_combo_value(combo).get_name()
 
251
        self.settings.setEncoders(vencoder=vencoder)
 
252
        if not self.muxer_combo_changing:
 
253
            # The user directly changed the video encoder combo.
 
254
            self.preferred_vencoder = vencoder
 
255
 
 
256
    def _videoSettingsButtonClickedCb(self, button):
 
257
        self._elementSettingsDialog(self.video_encoder_combo, 'vcodecsettings')
 
258
 
 
259
    def _channelsComboChangedCb(self, combo):
 
260
        self.settings.setAudioProperties(nbchanns=get_combo_value(combo))
 
261
 
 
262
    def _sampleDepthComboChangedCb(self, combo):
 
263
        self.settings.setAudioProperties(depth=get_combo_value(combo))
 
264
 
 
265
    def _sampleRateComboChangedCb(self, combo):
 
266
        self.settings.setAudioProperties(rate=get_combo_value(combo))
 
267
 
 
268
    def _audioEncoderChangedComboCb(self, combo):
 
269
        aencoder = get_combo_value(combo).get_name()
 
270
        self.settings.setEncoders(aencoder=aencoder)
 
271
        if not self.muxer_combo_changing:
 
272
            # The user directly changed the audio encoder combo.
 
273
            self.preferred_aencoder = aencoder
 
274
 
 
275
    def _audioSettingsButtonClickedCb(self, button):
 
276
        self._elementSettingsDialog(self.audio_encoder_combo, 'acodecsettings')
 
277
 
 
278
    def _elementSettingsDialog(self, combo, settings_attr):
 
279
        factory = get_combo_value(combo)
 
280
        settings = getattr(self.settings, settings_attr)
 
281
        dialog = GstElementSettingsDialog(factory, settings)
 
282
 
 
283
        response = dialog.run()
 
284
        if response == gtk.RESPONSE_OK:
 
285
            setattr(self.settings, settings_attr, dialog.getSettings())
 
286
        dialog.destroy()
 
287
 
 
288
    def _renderButtonClickedCb(self, unused_button):
 
289
        self.outfile = self.filebutton.get_uri() + "/" + self.fileentry.get_text()
 
290
        self.progress = EncodingProgressDialog(self.app, self)
 
291
        self.window.hide() # Hide the rendering settings dialog while rendering
 
292
        self.progress.show()
 
293
        self.startAction()
 
294
        self.progress.connect("cancel", self._cancelRender)
 
295
        self.progress.connect("pause", self._pauseRender)
 
296
        self.pipeline.connect("state-changed", self._stateChanged)
 
297
 
 
298
    def _cancelRender(self, progress):
 
299
        self.debug("aborting render")
 
300
        self.shutdown()
 
301
 
 
302
    def _pauseRender(self, progress):
 
303
        self.pipeline.togglePlayback()
 
304
 
 
305
    def _stateChanged(self, pipeline, state):
 
306
        self.progress.setState(state)
 
307
 
 
308
    def updatePosition(self, fraction, text):
 
309
        if self.progress:
 
310
            self.progress.updatePosition(fraction, text)
 
311
 
 
312
    def updateUIOnEOS(self):
 
313
        """
 
314
        When a render completes or is cancelled, update the UI
 
315
        """
 
316
        self.progress.destroy()
 
317
        self.progress = None
 
318
        self.window.show()  # Show the encoding dialog again
 
319
        self.pipeline.disconnect_by_function(self._stateChanged)
176
320
 
177
321
    def _cancelButtonClickedCb(self, unused_button):
178
322
        self.debug("Cancelling !")
179
 
        self._shutDown()
 
323
        self.destroy()
180
324
 
181
325
    def _deleteEventCb(self, window, event):
182
326
        self.debug("delete event")
183
 
        self._shutDown()
184
 
 
185
 
    def detectStreamTypes(self):
186
 
        self.have_video = False
187
 
        self.have_audio = False
188
 
 
189
 
        # we can only render TimelineSourceFactory
190
 
        sources = [factory for factory in self.pipeline.factories.keys()
191
 
                if isinstance(factory, SourceFactory)]
192
 
        timeline_source = sources[0]
193
 
        assert isinstance(timeline_source, TimelineSourceFactory)
194
 
 
195
 
        for track in timeline_source.timeline.tracks:
196
 
            if isinstance(track.stream, AudioStream) and track.duration > 0:
197
 
                self.have_audio = True
198
 
            elif isinstance(track.stream, VideoStream) and \
199
 
                    track.duration > 0:
200
 
                self.have_video = True
201
 
 
202
 
    def addRecordAction(self):
203
 
        self.debug("renderaction %r", self.renderaction)
204
 
        if self.renderaction == None:
205
 
            self.pipeline.connect('position', self._positionCb)
206
 
            self.pipeline.connect('eos', self._eosCb)
207
 
            self.debug("Setting pipeline to STOP")
208
 
            self.pipeline.stop()
209
 
            settings = export_settings_to_render_settings(self.settings,
210
 
                    self.have_video, self.have_audio)
211
 
            self.debug("Creating RenderAction")
212
 
            sources = [factory for factory in self.pipeline.factories
213
 
                    if isinstance(factory, SourceFactory)]
214
 
            self.renderaction = render_action_for_uri(self.outfile,
215
 
                    settings, *sources)
216
 
            self.debug("setting action on pipeline")
217
 
            self.pipeline.addAction(self.renderaction)
218
 
            self.debug("Activating render action")
219
 
            self.renderaction.activate()
220
 
            self.debug("Setting all active ViewAction to sync=False")
221
 
            for ac in self.pipeline.actions:
222
 
                if isinstance(ac, ViewAction) and ac.isActive():
223
 
                    ac.setSync(False)
224
 
            self.debug("Updating all sources to render settings")
225
 
            self._changeSourceSettings(self.settings)
226
 
            self.debug("setting pipeline to PAUSE")
227
 
            self.pipeline.pause()
228
 
            self.debug("done")
229
 
 
230
 
    def removeRecordAction(self):
231
 
        self.debug("renderaction %r", self.renderaction)
232
 
        if self.renderaction:
233
 
            self.pipeline.stop()
234
 
            self.renderaction.deactivate()
235
 
            self.pipeline.removeAction(self.renderaction)
236
 
            self.debug("putting all active ViewActions back to sync=True")
237
 
            for ac in self.pipeline.actions:
238
 
                if isinstance(ac, ViewAction) and ac.isActive():
239
 
                    ac.setSync(True)
240
 
            self._changeSourceSettings(self.project.getSettings())
241
 
            self.pipeline.pause()
242
 
            self.pipeline.disconnect_by_function(self._positionCb)
243
 
            self.pipeline.disconnect_by_function(self._eosCb)
244
 
            self.renderaction = None
 
327
        self.destroy()
 
328
 
 
329
    def destroy(self):
 
330
        # TODO: Do this only when the settings actually changed.
 
331
        self.project.setSettings(self.settings)
 
332
        GladeWindow.destroy(self)