~timo-jyrinki/ubuntu/trusty/pitivi/merge_debian_0.93-3

« back to all changes in this revision

Viewing changes to pitivi/ui/filechooserpreview.py

  • Committer: Timo Jyrinki
  • Author(s): Sebastian Dröge
  • Date: 2014-04-05 13:28:16 UTC
  • mfrom: (1.5.8)
  • Revision ID: timo.jyrinki@canonical.com-20140405132816-wmv1rhbtmlxmx3ag
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
 
import gobject
3
 
import gst
4
 
import gtk
5
 
import pango
6
 
import os
7
 
 
8
 
from pitivi.log.loggable import Loggable
9
 
from pitivi.discoverer import Discoverer
10
 
from pitivi.ui.common import beautify_stream
11
 
from pitivi.stream import AudioStream, VideoStream
12
 
from pitivi.utils import beautify_length, uri_is_valid
13
 
from pitivi.configure import get_pixmap_dir
14
 
from pitivi.factories.file import PictureFileSourceFactory
15
 
from pitivi.settings import GlobalSettings
16
 
from gettext import gettext as _
17
 
from pitivi.ui.common import SPACING
18
 
from pitivi.ui.viewer import ViewerWidget
19
 
 
20
 
DEFAULT_AUDIO_IMAGE = os.path.join(get_pixmap_dir(), "pitivi-sound.png")
21
 
 
22
 
PREVIEW_WIDTH = 250
23
 
PREVIEW_HEIGHT = 100
24
 
 
25
 
GlobalSettings.addConfigSection('filechooser-preview')
26
 
GlobalSettings.addConfigOption('FCEnablePreview',
27
 
    section='filechooser-preview',
28
 
    key='do-preview-on-clip-import',
29
 
    default=True)
30
 
GlobalSettings.addConfigOption('FCpreviewWidth',
31
 
    section='filechooser-preview',
32
 
    key='video-preview-width',
33
 
    default=PREVIEW_WIDTH)
34
 
GlobalSettings.addConfigOption('FCpreviewHeight',
35
 
    section='filechooser-preview',
36
 
    key='video-preview-height',
37
 
    default=PREVIEW_HEIGHT)
38
 
 
39
 
 
40
 
def get_playbin():
41
 
    try:
42
 
        return gst.element_factory_make("playbin2", "preview-player")
43
 
    except:
44
 
        return gst.element_factory_make("playbin", "preview-player")
45
 
 
46
 
 
47
 
class PreviewWidget(gtk.VBox, Loggable):
48
 
 
49
 
    def __init__(self, instance):
50
 
        gtk.VBox.__init__(self)
51
 
        Loggable.__init__(self)
52
 
 
53
 
        self.log("Init PreviewWidget")
54
 
        self.connect('destroy', self._destroy_cb)
55
 
 
56
 
        self.settings = instance.settings
57
 
        self.preview_cache = {}
58
 
        self.preview_cache_errors = {}
59
 
 
60
 
        self.discoverer = Discoverer()
61
 
        self.discoverer.connect('discovery-done', self._update_preview_cb)
62
 
        self.discoverer.connect('discovery-error', self._error_detected_cb)
63
 
 
64
 
        #playbin for play pics
65
 
        self.player = get_playbin()
66
 
        bus = self.player.get_bus()
67
 
        bus.add_signal_watch()
68
 
        bus.connect('message', self._bus_message_cb)
69
 
        bus.enable_sync_message_emission()
70
 
        bus.connect('sync-message::element', self._sync_message_cb)
71
 
        bus.connect('message::tag', self._tag_found_cb)
72
 
        self.__videosink = self.player.get_property("video-sink")
73
 
        self.__fakesink = gst.element_factory_make("fakesink", "fakesink")
74
 
 
75
 
        #some global variables for preview handling
76
 
        self.is_playing = False
77
 
        self.time_format = gst.Format(gst.FORMAT_TIME)
78
 
        self.original_dims = (PREVIEW_WIDTH, PREVIEW_HEIGHT)
79
 
        self.countinuous_seek = False
80
 
        self.current_selected_uri = ""
81
 
        self.current_preview_type = ""
82
 
        self.description = ""
83
 
        self.tags = {}
84
 
 
85
 
        # Gui elements:
86
 
        # Drawing area for video output
87
 
        self.preview_video = ViewerWidget()
88
 
        self.preview_video.modify_bg(gtk.STATE_NORMAL, self.preview_video.style.black)
89
 
        self.pack_start(self.preview_video, expand=False)
90
 
 
91
 
        # An image for images and audio
92
 
        self.preview_image = gtk.Image()
93
 
        self.preview_image.set_size_request(self.settings.FCpreviewWidth, self.settings.FCpreviewHeight)
94
 
        self.preview_image.show()
95
 
        self.pack_start(self.preview_image, expand=False)
96
 
 
97
 
        # Play button
98
 
        self.bbox = gtk.HBox()
99
 
        self.play_button = gtk.ToolButton(gtk.STOCK_MEDIA_PLAY)
100
 
        self.play_button.connect("clicked", self._on_start_stop_clicked_cb)
101
 
        self.bbox.pack_start(self.play_button, expand=False)
102
 
 
103
 
        #Scale for position handling
104
 
        self.pos_adj = gtk.Adjustment()
105
 
        self.seeker = gtk.HScale(self.pos_adj)
106
 
        self.seeker.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
107
 
        self.seeker.connect('button-press-event', self._on_seeker_press_cb)
108
 
        self.seeker.connect('button-release-event', self._on_seeker_press_cb)
109
 
        self.seeker.connect('motion-notify-event', self._on_motion_notify_cb)
110
 
        self.seeker.set_draw_value(False)
111
 
        self.seeker.show()
112
 
        self.bbox.pack_start(self.seeker)
113
 
 
114
 
        # Zoom buttons
115
 
        self.b_zoom_in = gtk.ToolButton(gtk.STOCK_ZOOM_IN)
116
 
        self.b_zoom_in.connect("clicked", self._on_zoom_clicked_cb, 1)
117
 
        self.b_zoom_out = gtk.ToolButton(gtk.STOCK_ZOOM_OUT)
118
 
        self.b_zoom_out.connect("clicked", self._on_zoom_clicked_cb, -1)
119
 
        self.bbox.pack_start(self.b_zoom_in, expand=False)
120
 
        self.bbox.pack_start(self.b_zoom_out, expand=False)
121
 
        self.bbox.show_all()
122
 
        self.pack_start(self.bbox, expand=False)
123
 
 
124
 
        # Label for metadata tags
125
 
        self.l_tags = gtk.Label()
126
 
        self.l_tags.set_justify(gtk.JUSTIFY_LEFT)
127
 
        self.l_tags.set_ellipsize(pango.ELLIPSIZE_END)
128
 
        self.l_tags.show()
129
 
        self.pack_start(self.l_tags, expand=False)
130
 
 
131
 
        # Error handling
132
 
        vbox = gtk.VBox()
133
 
        vbox.set_spacing(SPACING)
134
 
        self.l_error = gtk.Label(_("PiTiVi can not preview this file."))
135
 
        self.b_details = gtk.Button(_("More info"))
136
 
        self.b_details.connect('clicked', self._on_b_details_clicked_cb)
137
 
        vbox.pack_start(self.l_error)
138
 
        vbox.pack_start(self.b_details, expand=False, fill=False)
139
 
        vbox.show()
140
 
        self.pack_start(vbox, expand=False, fill=False)
141
 
 
142
 
    def add_preview_request(self, dialogbox):
143
 
        """add a preview request """
144
 
        uri = dialogbox.get_preview_uri()
145
 
        if uri is None or not uri_is_valid(uri):
146
 
            return
147
 
        self.log("Preview request for " + uri)
148
 
        self.clear_preview()
149
 
        self.current_selected_uri = uri
150
 
        if uri in self.preview_cache:  # Already discovered
151
 
            self.log(uri + " already in cache")
152
 
            self.show_preview(uri)
153
 
        elif uri in self.preview_cache_errors:
154
 
            self.log(uri + " already in error cache")
155
 
            self.show_error(uri)
156
 
        else:
157
 
            self.log("Call discoverer for " + uri)
158
 
            self.discoverer.addUri(uri)
159
 
 
160
 
    def _update_preview_cb(self, dscvr, uri, factory):
161
 
        if factory is None:
162
 
            self.error("Discoverer does not handle " + uri)
163
 
        # Add to cache
164
 
        self.preview_cache[uri] = factory
165
 
        # Show uri only if is the selected one
166
 
        if self.current_selected_uri == uri:
167
 
            self.show_preview(uri)
168
 
 
169
 
    def _error_detected_cb(self, discoverer, uri, mess, details):
170
 
        if details is not None:
171
 
            self.preview_cache_errors[uri] = (mess, details)
172
 
            if self.current_selected_uri == uri:
173
 
                self.show_error(uri)
174
 
 
175
 
    def show_preview(self, uri):
176
 
        self.log("Show preview for " + uri)
177
 
        factory = self.preview_cache.get(uri, None)
178
 
        if factory is None:
179
 
            self.log("No preview for " + uri)
180
 
            return
181
 
        if not factory.duration or factory.duration == gst.CLOCK_TIME_NONE:
182
 
            duration = ''
183
 
        else:
184
 
            duration = beautify_length(factory.duration)
185
 
        video = factory.getOutputStreams(VideoStream)
186
 
        if video:
187
 
            video = video[0]
188
 
            if type(factory) == PictureFileSourceFactory:
189
 
                self.current_preview_type = 'image'
190
 
                self.preview_video.hide()
191
 
                pixbuf = gtk.gdk.pixbuf_new_from_file(gst.uri_get_location(uri))
192
 
                pixbuf_w = pixbuf.get_width()
193
 
                pixbuf_h = pixbuf.get_height()
194
 
                w, h = self.__get_best_size(pixbuf_w, pixbuf_h)
195
 
                pixbuf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_NEAREST)
196
 
                self.preview_image.set_from_pixbuf(pixbuf)
197
 
                self.preview_image.set_size_request(self.settings.FCpreviewWidth, self.settings.FCpreviewHeight)
198
 
                self.preview_image.show()
199
 
                self.bbox.show()
200
 
                self.play_button.hide()
201
 
                self.seeker.hide()
202
 
                self.b_zoom_in.show()
203
 
                self.b_zoom_out.show()
204
 
            else:
205
 
                self.current_preview_type = 'video'
206
 
                self.preview_image.hide()
207
 
                self.player.set_property("video-sink", self.__videosink)
208
 
                self.player.set_property("uri", self.current_selected_uri)
209
 
                self.player.set_state(gst.STATE_PAUSED)
210
 
                self.clip_duration = factory.duration
211
 
                self.pos_adj.upper = self.clip_duration
212
 
                w, h = self.__get_best_size(video.par * video.width, video.height)
213
 
                self.preview_video.set_size_request(w, h)
214
 
                self.preview_video.show()
215
 
                self.bbox.show()
216
 
                self.play_button.show()
217
 
                self.seeker.show()
218
 
                self.b_zoom_in.show()
219
 
                self.b_zoom_out.show()
220
 
                self.description = _(u"<b>Resolution</b>: %d×%d") % \
221
 
                    (video.par * video.width, video.height) + "\n" + \
222
 
                    _("<b>Duration</b>: %s") % duration + "\n"
223
 
        else:
224
 
            self.current_preview_type = 'audio'
225
 
            self.preview_video.hide()
226
 
            audio = factory.getOutputStreams(AudioStream)
227
 
            audio = audio[0]
228
 
            self.clip_duration = factory.duration
229
 
            self.pos_adj.upper = self.clip_duration
230
 
            self.preview_image.set_from_file(DEFAULT_AUDIO_IMAGE)
231
 
            self.preview_image.show()
232
 
            self.preview_image.set_size_request(PREVIEW_WIDTH, PREVIEW_HEIGHT)
233
 
            self.description = beautify_stream(audio) + "\n" + \
234
 
                _("<b>Duration</b>: %s") % duration + "\n"
235
 
            self.player.set_state(gst.STATE_NULL)
236
 
            self.player.set_property("uri", self.current_selected_uri)
237
 
            self.player.set_property("video-sink", self.__fakesink)
238
 
            self.player.set_state(gst.STATE_PAUSED)
239
 
            self.play_button.show()
240
 
            self.seeker.show()
241
 
            self.b_zoom_in.hide()
242
 
            self.b_zoom_out.hide()
243
 
            self.bbox.show()
244
 
 
245
 
    def show_error(self, uri):
246
 
        self.l_error.show()
247
 
        self.b_details.show()
248
 
 
249
 
    def clear_preview(self):
250
 
        self.log("Reset PreviewWidget ")
251
 
        self.seeker.set_value(0)
252
 
        self.bbox.hide()
253
 
        self.l_error.hide()
254
 
        self.b_details.hide()
255
 
        self.description = ""
256
 
        self.l_tags.set_markup("")
257
 
        self.play_button.set_stock_id(gtk.STOCK_MEDIA_PLAY)
258
 
        self.player.set_state(gst.STATE_NULL)
259
 
        self.is_playing = False
260
 
        self.tags = {}
261
 
        self.current_selected_uri = ""
262
 
        self.current_preview_type = ""
263
 
        self.preview_image.hide()
264
 
        self.preview_video.hide()
265
 
 
266
 
    def _on_seeker_press_cb(self, widget, event):
267
 
        event.button = 2
268
 
        if event.type == gtk.gdk.BUTTON_PRESS:
269
 
            self.countinuous_seek = True
270
 
            if self.is_playing:
271
 
                self.player.set_state(gst.STATE_PAUSED)
272
 
        elif event.type == gtk.gdk.BUTTON_RELEASE:
273
 
            self.countinuous_seek = False
274
 
            value = long(widget.get_value())
275
 
            self.player.seek_simple(self.time_format, gst.SEEK_FLAG_FLUSH, value)
276
 
            if self.is_playing:
277
 
                self.player.set_state(gst.STATE_PLAYING)
278
 
 
279
 
    def _on_motion_notify_cb(self, widget, event):
280
 
        if self.countinuous_seek:
281
 
            value = widget.get_value()
282
 
            self.player.seek_simple(self.time_format, gst.SEEK_FLAG_FLUSH, value)
283
 
 
284
 
    def _bus_message_cb(self, bus, message):
285
 
        if message.type == gst.MESSAGE_EOS:
286
 
            self.player.set_state(gst.STATE_NULL)
287
 
            self.is_playing = False
288
 
            self.play_button.set_stock_id(gtk.STOCK_MEDIA_PLAY)
289
 
            self.pos_adj.set_value(0)
290
 
        elif message.type == gst.MESSAGE_ERROR:
291
 
            self.player.set_state(gst.STATE_NULL)
292
 
            self.is_playing = False
293
 
            err, dbg = message.parse_error()
294
 
            self.error("Error: %s " % err, dbg)
295
 
 
296
 
    def _update_position(self, *args):
297
 
        if self.is_playing:
298
 
            curr_pos = self.player.query_position(self.time_format, None)[0]
299
 
            self.pos_adj.set_value(long(curr_pos))
300
 
        return self.is_playing
301
 
 
302
 
    def _on_start_stop_clicked_cb(self, button):
303
 
        if button.get_stock_id() == gtk.STOCK_MEDIA_PLAY:
304
 
            self.player.set_state(gst.STATE_PLAYING)
305
 
            gobject.timeout_add(1000, self._update_position)
306
 
            self.is_playing = True
307
 
            button.set_stock_id(gtk.STOCK_MEDIA_PAUSE)
308
 
            self.log("Preview started")
309
 
        else:
310
 
            self.player.set_state(gst.STATE_PAUSED)
311
 
            self.is_playing = False
312
 
            button.set_stock_id(gtk.STOCK_MEDIA_PLAY)
313
 
            self.log("Preview paused")
314
 
 
315
 
    def _on_zoom_clicked_cb(self, button, increment):
316
 
        if self.current_preview_type == 'video':
317
 
            w, h = self.preview_video.get_size_request()
318
 
            if increment > 0:
319
 
                w *= 1.2
320
 
                h *= 1.2
321
 
            else:
322
 
                w *= 0.8
323
 
                h *= 0.8
324
 
                if (w, h) < self.original_dims:
325
 
                    (w, h) = self.original_dims
326
 
            self.preview_video.set_size_request(int(w), int(h))
327
 
            self.settings.FCpreviewWidth = int(w)
328
 
            self.settings.FCpreviewHeight = int(h)
329
 
        elif self.current_preview_type == 'image':
330
 
            pixbuf = self.preview_image.get_pixbuf()
331
 
            w = pixbuf.get_width()
332
 
            h = pixbuf.get_height()
333
 
            if increment > 0:
334
 
                w *= 1.2
335
 
                h *= 1.2
336
 
            else:
337
 
                w *= 0.8
338
 
                h *= 0.8
339
 
                if (w, h) < self.original_dims:
340
 
                    (w, h) = self.original_dims
341
 
            pixbuf = gtk.gdk.pixbuf_new_from_file(gst.uri_get_location(self.current_selected_uri))
342
 
            pixbuf = pixbuf.scale_simple(int(w), int(h), gtk.gdk.INTERP_BILINEAR)
343
 
 
344
 
            w = max(w, self.settings.FCpreviewWidth)
345
 
            h = max(h, self.settings.FCpreviewHeight)
346
 
            self.preview_image.set_size_request(int(w), int(h))
347
 
            self.preview_image.set_from_pixbuf(pixbuf)
348
 
            self.preview_image.show()
349
 
            self.settings.FCpreviewWidth = int(w)
350
 
            self.settings.FCpreviewHeight = int(h)
351
 
 
352
 
    def _sync_message_cb(self, bus, mess):
353
 
        if mess.type == gst.MESSAGE_ELEMENT:
354
 
            if mess.structure.get_name() == 'prepare-xwindow-id':
355
 
                sink = mess.src
356
 
                sink.set_property('force-aspect-ratio', True)
357
 
                sink.set_property("handle-expose", True)
358
 
                gtk.gdk.threads_enter()
359
 
                sink.set_xwindow_id(self.preview_video.window_xid)
360
 
                sink.expose()
361
 
                gtk.gdk.threads_leave()
362
 
        return gst.BUS_PASS
363
 
 
364
 
    def _tag_found_cb(self, abus, mess):
365
 
        tag_list = mess.parse_tag()
366
 
        acceptable_tags = [gst.TAG_ALBUM_ARTIST,
367
 
                            gst.TAG_ARTIST,
368
 
                            gst.TAG_TITLE,
369
 
                            gst.TAG_ALBUM,
370
 
                            gst.TAG_BITRATE,
371
 
                            gst.TAG_COMPOSER,
372
 
                            gst.TAG_GENRE,
373
 
                            gst.TAG_PERFORMER,
374
 
                            gst.TAG_DATE]
375
 
        for tag in tag_list.keys():
376
 
            tag_type = gst.tag_get_tag_type(tag)
377
 
            if tag in acceptable_tags and tag_type in (gobject.TYPE_STRING,
378
 
                                   gobject.TYPE_DOUBLE,
379
 
                                   gobject.TYPE_FLOAT,
380
 
                                   gobject.TYPE_INT,
381
 
                                   gobject.TYPE_UINT):
382
 
                name = gst.tag_get_nick(tag)
383
 
                value = unicode(tag_list[tag]).replace('<', ' ').replace('>', ' ')
384
 
                self.tags[name] = value
385
 
        keys = self.tags.keys()
386
 
        keys.sort()
387
 
        text = self.description + "\n"
388
 
        for key in keys:
389
 
            text = text + "<b>" + key.capitalize() + "</b>: " + self.tags[key] + "\n"
390
 
        self.l_tags.set_markup(text)
391
 
 
392
 
    def _on_b_details_clicked_cb(self, unused_button):
393
 
        mess, detail = self.preview_cache_errors.get(self.current_selected_uri, (None, None))
394
 
        if mess is not None:
395
 
            dialog = gtk.MessageDialog(None,
396
 
                gtk.DIALOG_MODAL,
397
 
                gtk.MESSAGE_WARNING,
398
 
                gtk.BUTTONS_OK,
399
 
                mess)
400
 
            dialog.set_icon_name("pitivi")
401
 
            dialog.set_title(_("Error while analyzing a file"))
402
 
            dialog.set_property("secondary-text", detail)
403
 
            dialog.run()
404
 
            dialog.destroy()
405
 
 
406
 
    def _destroy_cb(self, widget):
407
 
        self.player.set_state(gst.STATE_NULL)
408
 
        self.is_playing = False
409
 
        #FIXME: are the following lines really needed?
410
 
        del self.player
411
 
        del self.preview_cache
412
 
 
413
 
    def __get_best_size(self, width_in, height_in):
414
 
        if width_in > height_in:
415
 
            if self.settings.FCpreviewWidth < width_in:
416
 
                w = self.settings.FCpreviewWidth
417
 
                h = height_in * w / width_in
418
 
                return (w, h)
419
 
        else:
420
 
            if self.settings.FCpreviewHeight < height_in:
421
 
                h = self.settings.FCpreviewHeight
422
 
                w = width_in * h / height_in
423
 
                return (w, h)
424
 
        return (width_in, height_in)