1
1
# -*- coding: utf-8 -*-
4
# pitivi/mediafilespreviewer.py
6
# Copyright (c) 2011, Pier Carteri <pier.carteri@gmail.com>
7
# Copyright (c) 2012, Thibault Saunier <tsaunier@gnome.org>
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.
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.
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.
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
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
50
class PreviewWidget(Gtk.VBox, Loggable):
52
def __init__(self, instance):
53
Gtk.VBox.__init__(self)
71
class PreviewWidget(Gtk.Grid, Loggable):
73
Widget for displaying a GStreamer sink with playback controls.
75
@ivar settings: The settings of the app.
76
@type settings: L{GlobalSettings}
79
def __init__(self, settings, minimal=False):
80
Gtk.Grid.__init__(self)
54
81
Loggable.__init__(self)
56
83
self.log("Init PreviewWidget")
57
84
self.connect('destroy', self._destroy_cb)
59
self.settings = instance.settings
86
self.settings = settings
60
87
self.preview_cache = {}
61
88
self.preview_cache_errors = {}
63
self.discoverer = Discoverer.new(Gst.SECOND)
90
self.discoverer = GstPbutils.Discoverer.new(Gst.SECOND)
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)
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
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)
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)
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)
104
128
#Scale for position handling
105
129
self.pos_adj = Gtk.Adjustment()
112
136
self.bbox.pack_start(self.seeker, True, True, 0)
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)
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)
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)
140
self.pack_start(vbox, False, False, 0)
166
self.attach(vbox, 0, 4, 1, 1)
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)
169
self.remove(self.l_tags)
170
self.bbox.remove(self.b_zoom_in)
171
self.bbox.remove(self.b_zoom_out)
149
173
def add_preview_request(self, dialogbox):
150
174
"""add a preview request """
170
194
info = self.discoverer.discover_uri(uri)
171
195
except Exception, e:
173
self.preview_cache_errors[uri] = e
174
if self.current_selected_uri == uri:
196
self.preview_cache_errors[uri] = e
197
if self.current_selected_uri == uri:
178
201
if self.current_selected_uri == uri:
179
202
self.show_preview(uri, info)
181
204
def show_preview(self, uri, info):
184
206
self.preview_cache[uri] = info
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(),
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)
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])
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()
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()
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")
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")
300
def togglePlayback(self):
275
306
def clear_preview(self):
276
self.log("Reset PreviewWidget ")
307
self.log("Reset PreviewWidget")
277
308
self.seeker.set_value(0)
279
310
self.l_error.hide()
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
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)
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))
324
def _update_position(self, *args):
341
self.player.simple_seek(value)
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)
349
def _pipelineErrorCb(self, unused_message, unused_detail):
350
self.player.setState(Gst.State.NULL)
351
self.is_playing = False
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
330
def _on_preview_video_realize_cb(self, widget):
331
if platform.system() == 'Windows':
332
xid = widget.get_window().get_handle()
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)
337
363
def _on_start_stop_clicked_cb(self, button):
338
364
if self.is_playing:
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,
436
dialog = Gtk.MessageDialog(transient_for=None,
438
message_type=Gtk.MessageType.WARNING,
439
buttons=Gtk.ButtonsType.OK,
415
441
dialog.set_icon_name("pitivi")
416
442
dialog.set_title(_("Error while analyzing a file"))
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?
425
del self.preview_cache
427
450
def __get_best_size(self, width_in, height_in):
428
451
if width_in > height_in: