1
# -*- coding: utf-8 -*-
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
20
DEFAULT_AUDIO_IMAGE = os.path.join(get_pixmap_dir(), "pitivi-sound.png")
25
GlobalSettings.addConfigSection('filechooser-preview')
26
GlobalSettings.addConfigOption('FCEnablePreview',
27
section='filechooser-preview',
28
key='do-preview-on-clip-import',
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)
42
return gst.element_factory_make("playbin2", "preview-player")
44
return gst.element_factory_make("playbin", "preview-player")
47
class PreviewWidget(gtk.VBox, Loggable):
49
def __init__(self, instance):
50
gtk.VBox.__init__(self)
51
Loggable.__init__(self)
53
self.log("Init PreviewWidget")
54
self.connect('destroy', self._destroy_cb)
56
self.settings = instance.settings
57
self.preview_cache = {}
58
self.preview_cache_errors = {}
60
self.discoverer = Discoverer()
61
self.discoverer.connect('discovery-done', self._update_preview_cb)
62
self.discoverer.connect('discovery-error', self._error_detected_cb)
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")
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 = ""
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)
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)
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)
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)
112
self.bbox.pack_start(self.seeker)
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)
122
self.pack_start(self.bbox, expand=False)
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)
129
self.pack_start(self.l_tags, expand=False)
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)
140
self.pack_start(vbox, expand=False, fill=False)
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):
147
self.log("Preview request for " + uri)
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")
157
self.log("Call discoverer for " + uri)
158
self.discoverer.addUri(uri)
160
def _update_preview_cb(self, dscvr, uri, factory):
162
self.error("Discoverer does not handle " + uri)
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)
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:
175
def show_preview(self, uri):
176
self.log("Show preview for " + uri)
177
factory = self.preview_cache.get(uri, None)
179
self.log("No preview for " + uri)
181
if not factory.duration or factory.duration == gst.CLOCK_TIME_NONE:
184
duration = beautify_length(factory.duration)
185
video = factory.getOutputStreams(VideoStream)
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()
200
self.play_button.hide()
202
self.b_zoom_in.show()
203
self.b_zoom_out.show()
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()
216
self.play_button.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"
224
self.current_preview_type = 'audio'
225
self.preview_video.hide()
226
audio = factory.getOutputStreams(AudioStream)
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()
241
self.b_zoom_in.hide()
242
self.b_zoom_out.hide()
245
def show_error(self, uri):
247
self.b_details.show()
249
def clear_preview(self):
250
self.log("Reset PreviewWidget ")
251
self.seeker.set_value(0)
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
261
self.current_selected_uri = ""
262
self.current_preview_type = ""
263
self.preview_image.hide()
264
self.preview_video.hide()
266
def _on_seeker_press_cb(self, widget, event):
268
if event.type == gtk.gdk.BUTTON_PRESS:
269
self.countinuous_seek = True
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)
277
self.player.set_state(gst.STATE_PLAYING)
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)
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)
296
def _update_position(self, *args):
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
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")
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")
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()
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()
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)
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)
352
def _sync_message_cb(self, bus, mess):
353
if mess.type == gst.MESSAGE_ELEMENT:
354
if mess.structure.get_name() == 'prepare-xwindow-id':
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)
361
gtk.gdk.threads_leave()
364
def _tag_found_cb(self, abus, mess):
365
tag_list = mess.parse_tag()
366
acceptable_tags = [gst.TAG_ALBUM_ARTIST,
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,
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()
387
text = self.description + "\n"
389
text = text + "<b>" + key.capitalize() + "</b>: " + self.tags[key] + "\n"
390
self.l_tags.set_markup(text)
392
def _on_b_details_clicked_cb(self, unused_button):
393
mess, detail = self.preview_cache_errors.get(self.current_selected_uri, (None, None))
395
dialog = gtk.MessageDialog(None,
400
dialog.set_icon_name("pitivi")
401
dialog.set_title(_("Error while analyzing a file"))
402
dialog.set_property("secondary-text", detail)
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?
411
del self.preview_cache
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
420
if self.settings.FCpreviewHeight < height_in:
421
h = self.settings.FCpreviewHeight
422
w = width_in * h / height_in
424
return (width_in, height_in)