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

« back to all changes in this revision

Viewing changes to pitivi/mediafilespreviewer.py

  • Committer: Package Import Robot
  • Author(s): Sebastian Dröge
  • Date: 2014-03-29 15:22:50 UTC
  • mto: (3.1.23 experimental)
  • mto: This revision was merged to the branch mainline in revision 44.
  • Revision ID: package-import@ubuntu.com-20140329152250-flg9onx416bqf3e3
Tags: upstream-0.93
Import upstream version 0.93

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# -*- coding: utf-8 -*-
2
 
import platform
 
2
# Pitivi video editor
 
3
#
 
4
#       pitivi/mediafilespreviewer.py
 
5
#
 
6
# Copyright (c) 2011, Pier Carteri <pier.carteri@gmail.com>
 
7
# Copyright (c) 2012, Thibault Saunier <tsaunier@gnome.org>
 
8
#
 
9
# This program is free software; you can redistribute it and/or
 
10
# modify it under the terms of the GNU Lesser General Public
 
11
# License as published by the Free Software Foundation; either
 
12
# version 2.1 of the License, or (at your option) any later version.
 
13
#
 
14
# This program is distributed in the hope that it will be useful,
 
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
17
# Lesser General Public License for more details.
 
18
#
 
19
# You should have received a copy of the GNU Lesser General Public
 
20
# License along with this program; if not, write to the
 
21
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 
22
# Boston, MA 02110-1301, USA.
 
23
 
3
24
from gettext import gettext as _
4
25
from gi.repository import GLib
5
26
from gi.repository import GObject
8
29
from gi.repository import Gdk
9
30
from gi.repository import GdkPixbuf
10
31
from gi.repository import Pango
11
 
from gi.repository.GstPbutils import Discoverer
 
32
from gi.repository import GstPbutils
12
33
 
13
 
from pitivi.configure import get_pixmap_dir
14
34
from pitivi.settings import GlobalSettings
15
35
from pitivi.utils.loggable import Loggable
16
36
from pitivi.utils.misc import uri_is_valid
 
37
from pitivi.utils.pipeline import AssetPipeline
17
38
from pitivi.utils.ui import beautify_length, beautify_stream, SPACING
18
39
from pitivi.viewer import ViewerWidget
19
40
 
47
68
    Gst.TAG_COPYRIGHT]
48
69
 
49
70
 
50
 
class PreviewWidget(Gtk.VBox, Loggable):
51
 
 
52
 
    def __init__(self, instance):
53
 
        Gtk.VBox.__init__(self)
 
71
class PreviewWidget(Gtk.Grid, Loggable):
 
72
    """
 
73
    Widget for displaying a GStreamer sink with playback controls.
 
74
 
 
75
    @ivar settings: The settings of the app.
 
76
    @type settings: L{GlobalSettings}
 
77
    """
 
78
 
 
79
    def __init__(self, settings, minimal=False):
 
80
        Gtk.Grid.__init__(self)
54
81
        Loggable.__init__(self)
55
82
 
56
83
        self.log("Init PreviewWidget")
57
84
        self.connect('destroy', self._destroy_cb)
58
85
 
59
 
        self.settings = instance.settings
 
86
        self.settings = settings
60
87
        self.preview_cache = {}
61
88
        self.preview_cache_errors = {}
62
89
 
63
 
        self.discoverer = Discoverer.new(Gst.SECOND)
 
90
        self.discoverer = GstPbutils.Discoverer.new(Gst.SECOND)
64
91
 
65
92
        #playbin for play pics
66
 
        self.player = Gst.ElementFactory.make("playbin", "preview-player")
67
 
        bus = self.player.get_bus()
68
 
        bus.add_signal_watch()
69
 
        bus.connect('message', self._bus_message_cb)
70
 
        bus.connect('message::tag', self._tag_found_cb)
71
 
        self.__fakesink = Gst.ElementFactory.make("fakesink", "fakesink")
 
93
        self.player = AssetPipeline(clip=None, name="preview-player")
 
94
        self.player.connect('eos', self._pipelineEosCb)
 
95
        self.player.connect('error', self._pipelineErrorCb)
 
96
        self.player._bus.connect('message::tag', self._tag_found_cb)
72
97
 
73
98
        #some global variables for preview handling
74
99
        self.is_playing = False
75
 
        self.time_format = Gst.Format(Gst.Format.TIME)
76
100
        self.original_dims = (PREVIEW_WIDTH, PREVIEW_HEIGHT)
77
101
        self.countinuous_seek = False
78
102
        self.slider_being_used = False
83
107
 
84
108
        # Gui elements:
85
109
        # Drawing area for video output
86
 
        self.preview_video = ViewerWidget()
87
 
        self.preview_video.connect("realize", self._on_preview_video_realize_cb)
88
 
        self.preview_video.modify_bg(Gtk.StateType.NORMAL, self.preview_video.get_style().black)
89
 
        self.preview_video.set_double_buffered(False)
90
 
        self.pack_start(self.preview_video, False, True, 0)
 
110
        self.preview_video = ViewerWidget(realizedCb=self._on_preview_video_realize_cb)
 
111
        self.preview_video.props.hexpand = minimal
 
112
        self.preview_video.props.vexpand = minimal
 
113
        self.attach(self.preview_video, 0, 0, 1, 1)
91
114
 
92
115
        # An image for images and audio
93
116
        self.preview_image = Gtk.Image()
94
117
        self.preview_image.set_size_request(self.settings.FCpreviewWidth, self.settings.FCpreviewHeight)
95
118
        self.preview_image.show()
96
 
        self.pack_start(self.preview_image, False, True, 0)
 
119
        self.attach(self.preview_image, 0, 1, 1, 1)
97
120
 
98
121
        # Play button
99
122
        self.bbox = Gtk.HBox()
100
 
        self.play_button = Gtk.ToolButton(Gtk.STOCK_MEDIA_PLAY)
 
123
        self.play_button = Gtk.ToolButton()
 
124
        self.play_button.set_icon_name("media-playback-start")
101
125
        self.play_button.connect("clicked", self._on_start_stop_clicked_cb)
102
 
        self.bbox.pack_start(self.play_button, False, True, 0)
 
126
        self.bbox.pack_start(self.play_button, False, False, 0)
103
127
 
104
128
        #Scale for position handling
105
129
        self.pos_adj = Gtk.Adjustment()
112
136
        self.bbox.pack_start(self.seeker, True, True, 0)
113
137
 
114
138
        # Zoom buttons
115
 
        self.b_zoom_in = Gtk.ToolButton(Gtk.STOCK_ZOOM_IN)
 
139
        self.b_zoom_in = Gtk.ToolButton()
 
140
        self.b_zoom_in.set_icon_name("zoom-in")
116
141
        self.b_zoom_in.connect("clicked", self._on_zoom_clicked_cb, 1)
117
 
        self.b_zoom_out = Gtk.ToolButton(Gtk.STOCK_ZOOM_OUT)
 
142
        self.b_zoom_out = Gtk.ToolButton()
 
143
        self.b_zoom_out.set_icon_name("zoom-out")
118
144
        self.b_zoom_out.connect("clicked", self._on_zoom_clicked_cb, -1)
119
145
        self.bbox.pack_start(self.b_zoom_in, False, True, 0)
120
146
        self.bbox.pack_start(self.b_zoom_out, False, True, 0)
121
147
        self.bbox.show_all()
122
 
        self.pack_start(self.bbox, False, False, 0)
 
148
        self.attach(self.bbox, 0, 2, 1, 1)
123
149
 
124
150
        # Label for metadata tags
125
151
        self.l_tags = Gtk.Label()
126
152
        self.l_tags.set_justify(Gtk.Justification.LEFT)
127
153
        self.l_tags.set_ellipsize(Pango.EllipsizeMode.END)
128
154
        self.l_tags.show()
129
 
        self.pack_start(self.l_tags, False, False, 0)
 
155
        self.attach(self.l_tags, 0, 3, 1, 1)
130
156
 
131
157
        # Error handling
132
158
        vbox = Gtk.VBox()
133
159
        vbox.set_spacing(SPACING)
134
160
        self.l_error = Gtk.Label(label=_("Pitivi can not preview this file."))
135
 
        self.b_details = Gtk.Button(_("More info"))
 
161
        self.b_details = Gtk.Button.new_with_label(_("More info"))
136
162
        self.b_details.connect('clicked', self._on_b_details_clicked_cb)
137
163
        vbox.pack_start(self.l_error, True, True, 0)
138
164
        vbox.pack_start(self.b_details, False, False, 0)
139
165
        vbox.show()
140
 
        self.pack_start(vbox, False, False, 0)
 
166
        self.attach(vbox, 0, 4, 1, 1)
141
167
 
142
 
    def setMinimal(self):
143
 
        self.remove(self.l_tags)
144
 
        self.b_zoom_in.hide()
145
 
        self.b_zoom_out.hide()
146
 
        # Allow expanding/filling and pack the video preview below the controls
147
 
        self.set_child_packing(self.preview_video, True, True, 0, Gtk.PackType.END)
 
168
        if minimal:
 
169
            self.remove(self.l_tags)
 
170
            self.bbox.remove(self.b_zoom_in)
 
171
            self.bbox.remove(self.b_zoom_out)
148
172
 
149
173
    def add_preview_request(self, dialogbox):
150
174
        """add a preview request """
169
193
            try:
170
194
                info = self.discoverer.discover_uri(uri)
171
195
            except Exception, e:
172
 
                if e is not None:
173
 
                    self.preview_cache_errors[uri] = e
174
 
                    if self.current_selected_uri == uri:
175
 
                        self.show_error(uri)
176
 
                    return
 
196
                self.preview_cache_errors[uri] = e
 
197
                if self.current_selected_uri == uri:
 
198
                    self.show_error(uri)
 
199
                return
177
200
 
178
201
            if self.current_selected_uri == uri:
179
202
                self.show_preview(uri, info)
180
203
 
181
204
    def show_preview(self, uri, info):
182
 
 
183
205
        if info:
184
206
            self.preview_cache[uri] = info
185
207
        else:
215
237
            else:
216
238
                self.current_preview_type = 'video'
217
239
                self.preview_image.hide()
218
 
                self.player.set_property("uri", self.current_selected_uri)
219
 
                self.player.set_state(Gst.State.PAUSED)
 
240
                self.player.setClipUri(self.current_selected_uri)
 
241
                self.player.setState(Gst.State.PAUSED)
220
242
                self.pos_adj.props.upper = duration
221
 
                w, h = self.__get_best_size((video.get_par_num() / video.get_par_denom()) * video.get_width(),
222
 
                    video.get_height())
 
243
                video_width = (video.get_par_num() / video.get_par_denom()) * video.get_width()
 
244
                video_height = video.get_height()
 
245
                w, h = self.__get_best_size(video_width, video_height)
223
246
                self.preview_video.set_size_request(w, h)
 
247
                self.preview_video.setDisplayAspectRatio(float(video_width) / video_height)
224
248
                self.preview_video.show()
 
249
                self.player.connectWithViewer(self.preview_video)
225
250
                self.bbox.show()
226
251
                self.play_button.show()
227
252
                self.seeker.show()
228
253
                self.b_zoom_in.show()
229
254
                self.b_zoom_out.show()
230
 
                self.description = _("<b>Resolution</b>: %d×%d") % \
231
 
                    ((video.get_par_num() / video.get_par_denom()) * video.get_width(), video.get_height()) +\
232
 
                    "\n" + _("<b>Duration</b>: %s") % pretty_duration + "\n"
 
255
                self.description = "\n".join([
 
256
                    _("<b>Resolution</b>: %d×%d") % (video_width, video_height),
 
257
                    _("<b>Duration</b>: %s") % pretty_duration])
233
258
        else:
234
259
            self.current_preview_type = 'audio'
235
260
            self.preview_video.hide()
243
268
            self.preview_image.set_from_icon_name("audio-x-generic", Gtk.IconSize.DIALOG)
244
269
            self.preview_image.show()
245
270
            self.preview_image.set_size_request(PREVIEW_WIDTH, PREVIEW_HEIGHT)
246
 
            self.description = beautify_stream(audio) + "\n" + \
247
 
                _("<b>Duration</b>: %s") % pretty_duration + "\n"
248
 
            self.player.set_state(Gst.State.NULL)
249
 
            self.player.set_property("uri", self.current_selected_uri)
250
 
            self.player.set_property("video-sink", self.__fakesink)
251
 
            self.player.set_state(Gst.State.PAUSED)
 
271
            self.description = "\n".join([
 
272
                beautify_stream(audio),
 
273
                _("<b>Duration</b>: %s") % pretty_duration])
 
274
            self.player.setState(Gst.State.NULL)
 
275
            self.player.setClipUri(self.current_selected_uri)
 
276
            self.player.setState(Gst.State.PAUSED)
252
277
            self.play_button.show()
253
278
            self.seeker.show()
254
279
            self.b_zoom_in.hide()
255
280
            self.b_zoom_out.hide()
256
281
            self.bbox.show()
257
282
 
258
 
    def show_error(self, uri):
 
283
    def show_error(self, unused_uri):
259
284
        self.l_error.show()
260
285
        self.b_details.show()
261
286
 
262
287
    def play(self):
263
 
        self.player.set_state(Gst.State.PLAYING)
 
288
        self.player.setState(Gst.State.PLAYING)
264
289
        self.is_playing = True
265
290
        self.play_button.set_stock_id(Gtk.STOCK_MEDIA_PAUSE)
266
291
        GLib.timeout_add(250, self._update_position)
267
292
        self.debug("Preview started")
268
293
 
269
294
    def pause(self):
270
 
        self.player.set_state(Gst.State.PAUSED)
 
295
        self.player.setState(Gst.State.PAUSED)
271
296
        self.is_playing = False
272
297
        self.play_button.set_stock_id(Gtk.STOCK_MEDIA_PLAY)
273
298
        self.log("Preview paused")
274
299
 
 
300
    def togglePlayback(self):
 
301
        if self.is_playing:
 
302
            self.pause()
 
303
        else:
 
304
            self.play()
 
305
 
275
306
    def clear_preview(self):
276
 
        self.log("Reset PreviewWidget ")
 
307
        self.log("Reset PreviewWidget")
277
308
        self.seeker.set_value(0)
278
309
        self.bbox.hide()
279
310
        self.l_error.hide()
281
312
        self.description = ""
282
313
        self.l_tags.set_markup("")
283
314
        self.play_button.set_stock_id(Gtk.STOCK_MEDIA_PLAY)
284
 
        self.player.set_state(Gst.State.NULL)
 
315
        self.player.setState(Gst.State.NULL)
285
316
        self.is_playing = False
286
317
        self.tags = {}
287
318
        self.current_selected_uri = ""
294
325
        if event.type == Gdk.EventType.BUTTON_PRESS:
295
326
            self.countinuous_seek = True
296
327
            if self.is_playing:
297
 
                self.player.set_state(Gst.State.PAUSED)
 
328
                self.player.setState(Gst.State.PAUSED)
298
329
        elif event.type == Gdk.EventType.BUTTON_RELEASE:
299
330
            self.countinuous_seek = False
300
331
            value = long(widget.get_value())
301
 
            self.player.seek_simple(self.time_format, Gst.SeekFlags.FLUSH, value)
 
332
            self.player.simple_seek(value)
302
333
            if self.is_playing:
303
 
                self.player.set_state(Gst.State.PLAYING)
 
334
                self.player.setState(Gst.State.PLAYING)
304
335
            # Now, allow gobject timeout to continue updating the slider pos:
305
336
            self.slider_being_used = False
306
337
 
307
338
    def _on_motion_notify_cb(self, widget, event):
308
339
        if self.countinuous_seek:
309
340
            value = long(widget.get_value())
310
 
            self.player.seek_simple(self.time_format, Gst.SeekFlags.FLUSH, value)
311
 
 
312
 
    def _bus_message_cb(self, bus, message):
313
 
        if message.type == Gst.MessageType.EOS:
314
 
            self.player.set_state(Gst.State.NULL)
315
 
            self.is_playing = False
316
 
            self.play_button.set_stock_id(Gtk.STOCK_MEDIA_PLAY)
317
 
            self.pos_adj.set_value(0)
318
 
        elif message.type == Gst.MessageType.ERROR:
319
 
            self.player.set_state(Gst.State.NULL)
320
 
            self.is_playing = False
321
 
            err, dbg = message.parse_error()
322
 
            self.error("Error: %s %s" % (err, dbg))
323
 
 
324
 
    def _update_position(self, *args):
 
341
            self.player.simple_seek(value)
 
342
 
 
343
    def _pipelineEosCb(self, unused_pipeline):
 
344
        self.player.setState(Gst.State.NULL)
 
345
        self.is_playing = False
 
346
        self.play_button.set_stock_id(Gtk.STOCK_MEDIA_PLAY)
 
347
        self.pos_adj.set_value(0)
 
348
 
 
349
    def _pipelineErrorCb(self, unused_message, unused_detail):
 
350
        self.player.setState(Gst.State.NULL)
 
351
        self.is_playing = False
 
352
 
 
353
    def _update_position(self, *unused_args):
325
354
        if self.is_playing and not self.slider_being_used:
326
 
            curr_pos = self.player.query_position(self.time_format)[1]
 
355
            curr_pos = self.player.getPosition()
327
356
            self.pos_adj.set_value(long(curr_pos))
328
357
        return self.is_playing
329
358
 
330
 
    def _on_preview_video_realize_cb(self, widget):
331
 
        if platform.system() == 'Windows':
332
 
            xid = widget.get_window().get_handle()
333
 
        else:
334
 
            xid = widget.get_window().get_xid()
335
 
        self.player.set_window_handle(xid)
 
359
    def _on_preview_video_realize_cb(self, unused_drawing_area, unused_widget):
 
360
        if self.current_preview_type == 'video':
 
361
            self.player.connectWithViewer(self.preview_video)
336
362
 
337
363
    def _on_start_stop_clicked_cb(self, button):
338
364
        if self.is_playing:
399
425
        tag_list.foreach(self._appendTag, None)
400
426
        keys = self.tags.keys()
401
427
        keys.sort()
402
 
        text = self.description + "\n"
 
428
        text = self.description + "\n\n"
403
429
        for key in keys:
404
430
            text = text + "<b>" + key.capitalize() + "</b>: " + self.tags[key] + "\n"
405
431
        self.l_tags.set_markup(text)
407
433
    def _on_b_details_clicked_cb(self, unused_button):
408
434
        mess = self.preview_cache_errors.get(self.current_selected_uri, None)
409
435
        if mess is not None:
410
 
            dialog = Gtk.MessageDialog(None,
411
 
                Gtk.DialogFlags.MODAL,
412
 
                Gtk.MessageType.WARNING,
413
 
                Gtk.ButtonsType.OK,
414
 
                str(mess))
 
436
            dialog = Gtk.MessageDialog(transient_for=None,
 
437
                modal=True,
 
438
                message_type=Gtk.MessageType.WARNING,
 
439
                buttons=Gtk.ButtonsType.OK,
 
440
                text=str(mess))
415
441
            dialog.set_icon_name("pitivi")
416
442
            dialog.set_title(_("Error while analyzing a file"))
417
443
            dialog.run()
418
444
            dialog.destroy()
419
445
 
420
446
    def _destroy_cb(self, widget):
421
 
        self.player.set_state(Gst.State.NULL)
 
447
        self.player.setState(Gst.State.NULL)
422
448
        self.is_playing = False
423
 
        #FIXME: are the following lines really needed?
424
 
        del self.player
425
 
        del self.preview_cache
426
449
 
427
450
    def __get_best_size(self, width_in, height_in):
428
451
        if width_in > height_in: