20
20
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21
21
# Boston, MA 02111-1307, USA.
29
from glade import GladeWindow
27
import pitivi.plumber as plumber
32
28
import pitivi.instance as instance
33
29
from pitivi.bin import SmartTimelineBin
34
import pitivi.dnd as dnd
35
30
from pitivi.signalgroup import SignalGroup
37
from gettext import gettext as _
39
def time_to_string(value):
42
ms = value / gst.MSECOND
48
return "%02d:%02d:%02d.%03d" % (hours, mins, sec, ms)
32
from pitivi.utils import time_to_string
50
35
class PitiviViewer(gtk.VBox):
51
36
""" Pitivi's viewer widget with controls """
54
39
gst.log("New PitiviViewer")
55
40
gtk.VBox.__init__(self)
56
41
self.current_time = long(0)
57
self.requested_time = long(0)
42
self.requested_time = gst.CLOCK_TIME_NONE
58
43
self.current_frame = -1
59
44
self.valuechangedid = 0
60
45
self.currentlySeeking = False
75
60
# signal for timeline duration changes : (composition, sigid)
76
61
self._timelineDurationChangedSigId = (None, None)
78
self._addTimelineToPlayground()
80
65
def _connectToProject(self, project):
81
66
"""Connect signal handlers to a project.
88
73
None, self._tmpIsReadyCb)
89
74
self.project_signals.connect(project, "settings-changed",
90
75
None, self._settingsChangedCb)
76
# we should add the timeline to the playground here, so
77
# that the new timeline bin will be added to the
78
# playground when the project loads
79
self._addTimelineToPlayground()
92
81
def _createUi(self):
93
82
""" Creates the Viewer GUI """
95
84
self.set_spacing(5)
98
self.aframe = gtk.AspectFrame(xalign=0.5, yalign=0.5, ratio=4.0/3.0, obey_child=False)
87
self.aframe = gtk.AspectFrame(xalign=0.5, yalign=0.5, ratio=4.0/3.0,
99
89
self.pack_start(self.aframe, expand=True)
100
90
self.drawingarea = ViewerWidget()
101
self.drawingarea.connect_after("realize", self._drawingAreaRealizeCb)
91
self.drawingarea.connect_after("expose-event", self._drawingAreaExposeCb)
102
92
self.aframe.add(self.drawingarea)
147
137
self.timelabel = gtk.Label()
148
self.timelabel.set_markup("<tt>00m00s000 / --m--s---</tt>")
138
self.timelabel.set_markup("<tt>00:00:00.000 / --:--:--.---</tt>")
149
139
self.timelabel.set_alignment(1.0, 0.5)
150
140
self.timelabel.set_padding(5, 5)
151
141
bbox.pack_start(self.timelabel, expand=False, padding=10)
143
# self.detach_button = gtk.Button()
144
# image = gtk.Image()
145
# image.set_from_stock(gtk.STOCK_LEAVE_FULLSCREEN,
146
# gtk.ICON_SIZE_SMALL_TOOLBAR)
147
# self.detach_button.set_image(image)
148
# bbox.pack_end(self.detach_button, expand=False, fill=False)
155
151
self.drag_dest_set(gtk.DEST_DEFAULT_DROP | gtk.DEST_DEFAULT_MOTION,
157
153
gtk.gdk.ACTION_COPY)
158
154
self.connect("drag_data_received", self._dndDataReceivedCb)
160
def _asyncFrameRatioChange(self, ratio):
161
gst.debug("ratio:%f" % ratio)
162
self.aframe.set_property("ratio", ratio)
164
def _videosinkCapsNotifyCb(self, sinkpad, unused_property):
165
caps = sinkpad.get_negotiated_caps()
168
gst.log("caps:%s" % caps.to_string())
157
def setDisplayAspectRatio(self, ratio):
158
""" Sets the DAR of the Viewer to the given ratio """
159
gst.debug("Setting ratio of %f [%r]" % (float(ratio), ratio))
170
width = caps[0]["width"]
171
height = caps[0]["height"]
161
self.aframe.set_property("ratio", float(ratio))
173
gst.warning("Something went wrong when getting the video sink aspect ratio")
176
par = caps[0]["pixel-aspect-ratio"]
179
gobject.idle_add(self._asyncFrameRatioChange, float(width) / float(height))
181
gobject.idle_add(self._asyncFrameRatioChange, float(width * par.num) / float(par.denom * height))
163
gst.warning("could not set ratio !")
183
165
def _createSinkThreads(self):
184
166
""" Creates the sink threads for the playground """
196
178
vscale.link(vqueue)
197
179
vsinkthread.videosink = self.videosink
198
180
vsinkthread.add_pad(gst.GhostPad("sink", cspace.get_pad('sink')))
200
self.videosink.get_pad("sink").connect("notify::caps", self._videosinkCapsNotifyCb)
202
181
self.drawingarea.videosink = self.videosink
216
195
# setting sinkthreads on playground
217
196
instance.PiTiVi.playground.setVideoSinkThread(vsinkthread)
218
197
instance.PiTiVi.playground.setAudioSinkThread(asinkthread)
219
instance.PiTiVi.playground.connect("current-changed", self._currentPlaygroundChangedCb)
198
instance.PiTiVi.playground.connect("current-changed",
199
self._currentPlaygroundChangedCb)
221
201
def _settingsChangedCb(self, unused_project):
222
202
gst.info("current project settings changed")
224
204
# FIXME : do we really need to modify the ratio ??
227
def _drawingAreaRealizeCb(self, drawingarea):
207
def _drawingAreaExposeCb(self, drawingarea, event):
208
drawingarea.disconnect_by_func(self._drawingAreaExposeCb)
228
209
drawingarea.modify_bg(gtk.STATE_NORMAL, drawingarea.style.black)
229
210
self._createSinkThreads()
230
211
if not instance.PiTiVi.playground.play() == gst.STATE_CHANGE_FAILURE:
231
212
self.currentState = gst.STATE_PLAYING
233
216
## gtk.HScale callbacks for self.slider
235
218
def _sliderButtonPressCb(self, slider, unused_event):
280
263
self._doSeek(seekvalue, gst.FORMAT_DEFAULT)
282
265
def _seekTimeoutCb(self):
266
gst.debug("requested_time %s" % gst.TIME_ARGS(self.requested_time))
283
267
self.currentlySeeking = False
284
if not self.current_time == self.requested_time:
268
if (self.requested_time != gst.CLOCK_TIME_NONE) and (self.current_time != self.requested_time):
285
269
self._doSeek(self.requested_time)
287
272
def _doSeek(self, value, format=gst.FORMAT_TIME):
273
gst.debug("%s , currentlySeeking:%r" % (gst.TIME_ARGS(value),
274
self.currentlySeeking))
288
275
if not self.currentlySeeking:
289
276
self.currentlySeeking = True
290
gobject.timeout_add(80, self._seekTimeoutCb)
291
instance.PiTiVi.playground.seekInCurrent(value, format=format)
293
if format == gst.FORMAT_TIME:
294
self.requested_time = value
277
if instance.PiTiVi.playground.seekInCurrent(value, format=format):
278
gst.debug("seek succeeded, request_time = NONE")
279
self.requested_time = gst.CLOCK_TIME_NONE
280
gobject.timeout_add(80, self._seekTimeoutCb)
283
self.currentlySeeking = False
285
if format == gst.FORMAT_TIME:
286
self.requested_time = value
296
288
def _newTime(self, value, frame=-1):
297
289
gst.info("value:%s, frame:%d" % (gst.TIME_ARGS(value), frame))
298
290
self.current_time = value
299
291
self.current_frame = frame
300
self.timelabel.set_markup("<tt>%s / %s</tt>" % (time_to_string(value), time_to_string(instance.PiTiVi.playground.current.length)))
292
self.timelabel.set_markup("<tt>%s / %s</tt>" % (time_to_string(value),
293
time_to_string(instance.PiTiVi.playground.current.length)))
301
294
if not self.moving_slider:
302
295
self.posadjust.set_value(float(value))
309
302
gst.debug("duration : %s" % gst.TIME_ARGS(duration))
310
303
gst.debug("playground.current.length : %s" % gst.TIME_ARGS(instance.PiTiVi.playground.current.length))
311
304
self.posadjust.upper = float(duration)
312
self.timelabel.set_markup("<tt>%s / %s</tt>" % (time_to_string(self.current_time), time_to_string(instance.PiTiVi.playground.current.length)))
305
self.timelabel.set_markup("<tt>%s / %s</tt>" % (time_to_string(self.current_time),
306
time_to_string(instance.PiTiVi.playground.current.length)))
315
309
def _backToDefaultCb(self):
353
347
self._connectToProject(project)
355
349
def _addTimelineToPlayground(self):
356
instance.PiTiVi.playground.addPipeline(instance.PiTiVi.current.getBin())
350
# remove old timeline before proceeding
351
pg = instance.PiTiVi.playground
352
timeline = pg.getTimeline()
355
pg.removePipeline(timeline)
356
# add current timeline
357
pg.addPipeline(instance.PiTiVi.current.getBin())
359
359
## Control gtk.Button callbacks
387
386
def _currentPlaygroundChangedCb(self, playground, smartbin):
388
387
gst.log("smartbin:%s" % smartbin)
389
if smartbin == playground.default:
389
if not smartbin.seekable:
390
# live sources or defaults, no duration/seeking available
390
391
self.slider.set_sensitive(False)
391
392
self.playpause_button.set_sensitive(False)
392
393
self.next_button.set_sensitive(False)
397
398
self._timelineDurationChangedSigId = (None, None)
399
400
if isinstance(smartbin, SmartTimelineBin):
400
gst.info("switching to Timeline, setting duration to %s" % (gst.TIME_ARGS(smartbin.project.timeline.videocomp.duration)))
401
gst.info("switching to Timeline, setting duration to %s" %
402
(gst.TIME_ARGS(smartbin.project.timeline.videocomp.duration)))
401
403
self.posadjust.upper = float(smartbin.project.timeline.videocomp.duration)
402
404
# FIXME : we need to disconnect from this signal !
403
405
sigid = smartbin.project.timeline.videocomp.connect("start-duration-changed",
405
407
self._timelineDurationChangedSigId = (smartbin.project.timeline.videocomp,
408
self.posadjust.upper = float(smartbin.factory.length)
410
self.posadjust.upper = float(smartbin.factory.duration)
409
411
if not self._timelineDurationChangedSigId == (None, None):
410
412
obj, sigid = self._timelineDurationChangedSigId
411
413
obj.disconnect(sigid)
416
418
self.next_button.set_sensitive(True)
417
419
self.back_button.set_sensitive(True)
421
if isinstance(smartbin, SmartTimelineBin):
422
seti = smartbin.project.getSettings()
423
dar = float(seti.videowidth * seti.videopar.num) / float(seti.videoheight * seti.videopar.denom)
424
elif hasattr(smartbin, 'factory'):
425
dar = smartbin.factory.video_info_stream.dar
427
dar = smartbin.width / smartbin.height
428
self.setDisplayAspectRatio(dar)
419
430
def _currentStateCb(self, unused_playground, state):
420
431
gst.info("current state changed : %s" % state)
421
432
if state == int(gst.STATE_PLAYING):
430
441
dav = self.drawingarea.videosink
431
442
gst.log('%s' % dav)
432
443
if dav and dav.realsink and dav.realsink == message.src:
433
self.drawingarea.can_set_xid = True
434
if self.drawingarea.isexposed:
435
self.drawingarea.set_xwindow_id()
444
self.drawingarea.set_xwindow_id()
438
447
class ViewerWidget(gtk.DrawingArea):
446
455
gtk.DrawingArea.__init__(self)
447
456
self.videosink = None
448
457
self.have_set_xid = False
449
self.can_set_xid = False
450
458
self.unset_flags(gtk.DOUBLE_BUFFERED)
451
459
self.unset_flags(gtk.SENSITIVE)
452
self.isexposed = False
454
def do_expose_event(self, unused_event):
455
""" 'expose-event' override """
456
gst.log("expose have_set_xid:%d can_set_xid:%d" % (self.have_set_xid, self.can_set_xid))
457
self.isexposed = True
459
if not self.have_set_xid and self.can_set_xid:
460
self.set_xwindow_id()
461
elif self.have_set_xid:
462
self.videosink.expose()
465
461
def set_xwindow_id(self):
466
462
""" set the widget's XID on the configured videosink. """