Back to development
[platform/upstream/gstreamer.git] / subprojects / gst-python / old_examples / synchronizer.py
1 #!/usr/bin/env python
2 # -*- Mode: Python -*-
3 # vi:si:et:sw=4:sts=4:ts=4
4
5 import pygtk
6 pygtk.require('2.0')
7
8 import sys
9
10 import gobject
11 gobject.threads_init()
12
13 import pygst
14 pygst.require('0.10')
15 import gst
16 import gst.interfaces
17 import gtk
18 gtk.gdk.threads_init()
19
20 class GstPlayer:
21     def __init__(self, videowidget):
22         self.playing = False
23         self.player = gst.element_factory_make("playbin", "player")
24         self.videowidget = videowidget
25
26         bus = self.player.get_bus()
27         bus.enable_sync_message_emission()
28         bus.add_signal_watch()
29         bus.connect('sync-message::element', self.on_sync_message)
30         bus.connect('message', self.on_message)
31
32     def on_sync_message(self, bus, message):
33         if message.structure is None:
34             return
35         if message.structure.get_name() == 'prepare-xwindow-id':
36             # Sync with the X server before giving the X-id to the sink
37             gtk.gdk.threads_enter()
38             gtk.gdk.display_get_default().sync()
39             self.videowidget.set_sink(message.src)
40             message.src.set_property('force-aspect-ratio', True)
41             gtk.gdk.threads_leave()
42             
43     def on_message(self, bus, message):
44         t = message.type
45         if t == gst.MESSAGE_ERROR:
46             err, debug = message.parse_error()
47             print "Error: %s" % err, debug
48             if self.on_eos:
49                 self.on_eos()
50             self.playing = False
51         elif t == gst.MESSAGE_EOS:
52             if self.on_eos:
53                 self.on_eos()
54             self.playing = False
55
56     def set_location(self, location):
57         self.player.set_state(gst.STATE_NULL)
58         self.player.set_property('uri', location)
59
60     def get_location(self):
61         return self.player.get_property('uri')
62
63     def query_position(self):
64         "Returns a (position, duration) tuple"
65         try:
66             position, format = self.player.query_position(gst.FORMAT_TIME)
67         except:
68             position = gst.CLOCK_TIME_NONE
69
70         try:
71             duration, format = self.player.query_duration(gst.FORMAT_TIME)
72         except:
73             duration = gst.CLOCK_TIME_NONE
74
75         return (position, duration)
76
77     def seek(self, location):
78         """
79         @param location: time to seek to, in nanoseconds
80         """
81         gst.debug("seeking to %r" % location)
82         event = gst.event_new_seek(1.0, gst.FORMAT_TIME,
83             gst.SEEK_FLAG_FLUSH,
84             gst.SEEK_TYPE_SET, location,
85             gst.SEEK_TYPE_NONE, 0)
86
87         res = self.player.send_event(event)
88         if res:
89             gst.info("setting new stream time to 0")
90             self.player.set_new_stream_time(0L)
91         else:
92             gst.error("seek to %r failed" % location)
93
94     def pause(self):
95         gst.info("pausing player")
96         self.player.set_state(gst.STATE_PAUSED)
97         self.playing = False
98
99     def play(self):
100         gst.info("playing player")
101         self.player.set_state(gst.STATE_PLAYING)
102         self.playing = True
103         
104     def stop(self):
105         self.player.set_state(gst.STATE_NULL)
106         gst.info("stopped player")
107
108     def get_state(self, timeout=1):
109         return self.player.get_state(timeout=timeout)
110
111     def is_playing(self):
112         return self.playing
113     
114 class VideoWidget(gtk.DrawingArea):
115     def __init__(self):
116         gtk.DrawingArea.__init__(self)
117         self.imagesink = None
118         self.unset_flags(gtk.DOUBLE_BUFFERED)
119
120     def do_expose_event(self, event):
121         if self.imagesink:
122             self.imagesink.expose()
123             return False
124         else:
125             return True
126
127     def set_sink(self, sink):
128         assert self.window.xid
129         self.imagesink = sink
130         self.imagesink.set_xwindow_id(self.window.xid)
131
132 class SyncPoints(gtk.VBox):
133     def __init__(self, window):
134         gtk.VBox.__init__(self)
135         self.pwindow = window
136         self.create_ui()
137
138     def get_time_as_str(self, iter, i):
139         value = self.model.get_value(iter, i)
140         ret = ''
141         for div, sep, mod, pad in ((gst.SECOND*60, '', 0, 0),
142                                    (gst.SECOND, ':', 60, 2),
143                                    (gst.MSECOND, '.', 1000, 3)):
144             n = value // div
145             if mod:
146                 n %= mod
147             ret += sep + ('%%0%dd' % pad) % n
148         return ret
149
150     def create_ui(self):
151         self.model = model = gtk.ListStore(gobject.TYPE_UINT64,
152                                            gobject.TYPE_UINT64)
153         self.view = view = gtk.TreeView(self.model)
154
155         renderer = gtk.CellRendererText()
156         column = gtk.TreeViewColumn("Audio time", renderer)
157         def time_to_text(column, cell, method, iter, i):
158             cell.set_property('text', self.get_time_as_str(iter, i))
159         column.set_cell_data_func(renderer, time_to_text, 0)
160         column.set_expand(True)
161         column.set_clickable(True)
162         view.append_column(column)
163         
164         renderer = gtk.CellRendererText()
165         column = gtk.TreeViewColumn("Video time", renderer)
166         column.set_cell_data_func(renderer, time_to_text, 1)
167         column.set_expand(True)
168         view.append_column(column)
169         
170         view.show()
171         self.pack_start(view, True, True, 6)
172
173         hbox = gtk.HBox(False, 0)
174         hbox.show()
175         self.pack_start(hbox, False, False, 0)
176
177         add = gtk.Button(stock=gtk.STOCK_ADD)
178         add.show()
179         def add_and_select(*x):
180             iter = model.append()
181             self.view.get_selection().select_iter(iter)
182             self.changed()
183         add.connect("clicked", add_and_select)
184         hbox.pack_end(add, False, False, 0)
185         
186         remove = gtk.Button(stock=gtk.STOCK_REMOVE)
187         remove.show()
188         def remove_selected(*x):
189             model, iter = self.view.get_selection().get_selected()
190             model.remove(iter)
191             self.changed()
192         remove.connect("clicked", remove_selected)
193         hbox.pack_end(remove, False, False, 0)
194         
195         pad = gtk.Label('   ')
196         pad.show()
197         hbox.pack_end(pad)
198
199         label = gtk.Label("Set: ")
200         label.show()
201         hbox.pack_start(label)
202
203         a = gtk.Button("A_udio")
204         a.show()
205         a.connect("clicked", lambda *x: self.set_selected_audio_now())
206         hbox.pack_start(a)
207
208         l = gtk.Label(" / ")
209         l.show()
210         hbox.pack_start(l)
211
212         v = gtk.Button("_Video")
213         v.show()
214         v.connect("clicked", lambda *x: self.set_selected_video_now())
215         hbox.pack_start(v)
216
217     def get_sync_points(self):
218         def get_value(row, i):
219             return self.model.get_value(row.iter, i)
220         pairs = [(get_value(row, 1), get_value(row, 0)) for row in self.model]
221         pairs.sort()
222         ret = []
223         maxdiff = 0
224         for pair in pairs:
225             maxdiff = max(maxdiff, abs(pair[1] - pair[0]))
226             ret.extend(pair)
227         return ret, maxdiff
228
229     def changed(self):
230         print 'Sync times now:'
231         for index, row in enumerate(self.model):
232             print 'A/V %d: %s -- %s' % (index,
233                                         self.get_time_as_str(row.iter, 0),
234                                         self.get_time_as_str(row.iter, 1))
235             
236
237     def set_selected_audio(self, time):
238         sel = self.view.get_selection()
239         model, iter = sel.get_selected()
240         if iter:
241             model.set_value(iter, 0, time)
242         self.changed()
243
244     def set_selected_video(self, time):
245         sel = self.view.get_selection()
246         model, iter = sel.get_selected()
247         if iter:
248             model.set_value(iter, 1, time)
249         self.changed()
250
251     def set_selected_audio_now(self):
252         time, dur = self.pwindow.player.query_position()
253         self.set_selected_audio(time)
254
255     def set_selected_video_now(self):
256         # pause and preroll first
257         if self.pwindow.player.is_playing():
258             self.pwindow.play_toggled()
259         self.pwindow.player.get_state(timeout=gst.MSECOND * 200)
260
261         time, dur = self.pwindow.player.query_position()
262         self.set_selected_video(time)
263
264     def seek_and_pause(self, time):
265         if self.pwindow.player.is_playing():
266             self.pwindow.play_toggled()
267         self.pwindow.player.seek(time)
268         if self.pwindow.player.is_playing():
269             self.pwindow.play_toggled()
270         self.pwindow.player.get_state(timeout=gst.MSECOND * 200)
271
272 class ProgressDialog(gtk.Dialog):
273     def __init__(self, title, description, task, parent, flags, buttons):
274         gtk.Dialog.__init__(self, title, parent, flags, buttons)
275         self._create_ui(title, description, task)
276
277     def _create_ui(self, title, description, task):
278         self.set_border_width(6)
279         self.set_resizable(False)
280         self.set_has_separator(False)
281
282         vbox = gtk.VBox()
283         vbox.set_border_width(6)
284         vbox.show()
285         self.vbox.pack_start(vbox, False)
286
287         label = gtk.Label('<big><b>%s</b></big>' % title)
288         label.set_use_markup(True)
289         label.set_alignment(0.0, 0.0)
290         label.show()
291         vbox.pack_start(label, False)
292         
293         label = gtk.Label(description)
294         label.set_use_markup(True)
295         label.set_alignment(0.0, 0.0)
296         label.set_line_wrap(True)
297         label.set_padding(0, 12)
298         label.show()
299         vbox.pack_start(label, False)
300
301         self.progress = progress = gtk.ProgressBar()
302         progress.show()
303         vbox.pack_start(progress, False)
304
305         self.progresstext = label = gtk.Label('')
306         label.set_line_wrap(True)
307         label.set_use_markup(True)
308         label.set_alignment(0.0, 0.0)
309         label.show()
310         vbox.pack_start(label)
311         self.set_task(task)
312
313     def set_task(self, task):
314         self.progresstext.set_markup('<i>%s</i>' % task)
315
316 UNKNOWN = 0
317 SUCCESS = 1
318 FAILURE = 2
319 CANCELLED = 3
320
321 class RemuxProgressDialog(ProgressDialog):
322     def __init__(self, parent, fromname, toname):
323         ProgressDialog.__init__(self,
324                                 "Writing to disk",
325                                 ('Writing the newly synchronized <b>%s</b> '
326                                  'to <b>%s</b>. This may take some time.'
327                                  % (fromname, toname)),
328                                 'Starting media pipeline',
329                                 parent,
330                                 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
331                                 (gtk.STOCK_CANCEL, CANCELLED,
332                                  gtk.STOCK_CLOSE, SUCCESS))
333         self.set_completed(False)
334         
335     def update_position(self, pos, dur):
336         remaining = dur - pos
337         minutes = remaining // (gst.SECOND * 60)
338         seconds = (remaining - minutes * gst.SECOND * 60) // gst.SECOND
339         self.progress.set_text('%d:%02d of video remaining' % (minutes, seconds))
340         self.progress.set_fraction(1.0 - float(remaining) / dur)
341
342     def set_completed(self, completed):
343         self.set_response_sensitive(CANCELLED, not completed)
344         self.set_response_sensitive(SUCCESS, completed)
345
346 class Resynchronizer(gst.Pipeline):
347
348     __gsignals__ = {'done': (gobject.SIGNAL_RUN_LAST, None, (int,))}
349
350     def __init__(self, fromuri, touri, (syncpoints, maxdiff)):
351         # HACK: should do Pipeline.__init__, but that doesn't do what we
352         # want; there's a bug open aboooot that
353         self.__gobject_init__()
354
355         self.fromuri = fromuri
356         self.touri = None
357         self.syncpoints = syncpoints
358         self.maxdiff = maxdiff
359
360         self.src = self.resyncbin = self.sink = None
361         self.resolution = UNKNOWN
362
363         self.window = None
364         self.pdialog = None
365
366         self._query_id = -1
367
368     def do_setup_pipeline(self):
369         self.src = gst.element_make_from_uri(gst.URI_SRC, self.fromuri)
370         self.resyncbin = ResyncBin(self.syncpoints, self.maxdiff)
371         self.sink = gst.element_make_from_uri(gst.URI_SINK, self.touri)
372         self.resolution = UNKNOWN
373
374         if gobject.signal_lookup('allow-overwrite', self.sink.__class__):
375             self.sink.connect('allow-overwrite', lambda *x: True)
376
377         self.add(self.src, self.resyncbin, self.sink)
378
379         self.src.link(self.resyncbin)
380         self.resyncbin.link(self.sink)
381
382     def do_get_touri(self):
383         chooser = gtk.FileChooserDialog('Save as...',
384                                         self.window,
385                                         action=gtk.FILE_CHOOSER_ACTION_SAVE,
386                                         buttons=(gtk.STOCK_CANCEL,
387                                                  CANCELLED,
388                                                  gtk.STOCK_SAVE,
389                                                  SUCCESS))
390         chooser.set_uri(self.fromuri) # to select the folder
391         chooser.unselect_all()
392         chooser.set_do_overwrite_confirmation(True)
393         name = self.fromuri.split('/')[-1][:-4] + '-remuxed.ogg'
394         chooser.set_current_name(name)
395         resp = chooser.run()
396         uri = chooser.get_uri()
397         chooser.destroy()
398
399         if resp == SUCCESS:
400             return uri
401         else:
402             return None
403
404     def _start_queries(self):
405         def do_query():
406             try:
407                 # HACK: self.remuxbin.query() should do the same
408                 # (requires implementing a vmethod, dunno how to do that
409                 # although i think it's possible)
410                 # HACK: why does self.query_position(..) not give useful
411                 # answers? 
412                 pad = self.resyncbin.get_pad('src')
413                 pos, format = pad.query_position(gst.FORMAT_TIME)
414                 dur, format = pad.query_duration(gst.FORMAT_TIME)
415                 if pos != gst.CLOCK_TIME_NONE:
416                     self.pdialog.update_position(pos, duration)
417             except:
418                 # print 'query failed'
419                 pass
420             return True
421         if self._query_id == -1:
422             self._query_id = gobject.timeout_add(100, # 10 Hz
423                                                  do_query)
424
425     def _stop_queries(self):
426         if self._query_id != -1:
427             gobject.source_remove(self._query_id)
428             self._query_id = -1
429
430     def _bus_watch(self, bus, message):
431         if message.type == gst.MESSAGE_ERROR:
432             print 'error', message
433             self._stop_queries()
434             m = gtk.MessageDialog(self.window,
435                                   gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
436                                   gtk.MESSAGE_ERROR,
437                                   gtk.BUTTONS_CLOSE,
438                                   "Error processing file")
439             gerror, debug = message.parse_error()
440             txt = ('There was an error processing your file: %s\n\n'
441                    'Debug information:\n%s' % (gerror, debug))
442             m.format_secondary_text(txt)
443             m.run()
444             m.destroy()
445             self.response(FAILURE)
446         elif message.type == gst.MESSAGE_WARNING:
447             print 'warning', message
448         elif message.type == gst.MESSAGE_EOS:
449             # print 'eos, woot', message.src
450             name = self.touri
451             if name.startswith('file://'):
452                 name = name[7:]
453             self.pdialog.set_task('Finished writing %s' % name)
454             self.pdialog.update_position(1,1)
455             self._stop_queries()
456             self.pdialog.set_completed(True)
457         elif message.type == gst.MESSAGE_STATE_CHANGED:
458             if message.src == self:
459                 old, new, pending = message.parse_state_changed()
460                 if ((old, new, pending) ==
461                     (gst.STATE_READY, gst.STATE_PAUSED,
462                      gst.STATE_VOID_PENDING)):
463                     self.pdialog.set_task('Processing file')
464                     self._start_queries()
465                     self.set_state(gst.STATE_PLAYING)
466
467     def response(self, response):
468         assert self.resolution == UNKNOWN
469         self.resolution = response
470         self.set_state(gst.STATE_NULL)
471         self.pdialog.destroy()
472         self.pdialog = None
473         self.window.set_sensitive(True)
474         self.emit('done', response)
475
476     def start(self, main_window):
477         self.window = main_window
478         self.touri = self.do_get_touri()
479         if not self.touri:
480             return False
481         self.do_setup_pipeline()
482         bus = self.get_bus()
483         bus.add_signal_watch()
484         bus.connect('message', self._bus_watch)
485         if self.window:
486             # can be None if we are debugging...
487             self.window.set_sensitive(False)
488         fromname = self.fromuri.split('/')[-1]
489         toname = self.touri.split('/')[-1]
490         self.pdialog = RemuxProgressDialog(main_window, fromname, toname)
491         self.pdialog.show()
492         self.pdialog.connect('response', lambda w, r: self.response(r))
493
494         self.set_state(gst.STATE_PAUSED)
495         return True
496         
497     def run(self, main_window):
498         if self.start(main_window):
499             loop = gobject.MainLoop()
500             self.connect('done', lambda *x: gobject.idle_add(loop.quit))
501             loop.run()
502         else:
503             self.resolution = CANCELLED
504         return self.resolution
505         
506 class ResyncBin(gst.Bin):
507     def __init__(self, sync_points, maxdiff):
508         self.__gobject_init__()
509
510         self.parsefactories = self._find_parsers()
511         self.parsers = []
512
513         self.demux = gst.element_factory_make('oggdemux')
514         self.mux = gst.element_factory_make('oggmux')
515
516         self.add(self.demux, self.mux)
517
518         self.add_pad(gst.GhostPad('sink', self.demux.get_pad('sink')))
519         self.add_pad(gst.GhostPad('src', self.mux.get_pad('src')))
520
521         self.demux.connect('pad-added', self._new_demuxed_pad)
522
523         self.sync_points = sync_points
524         self.maxdiff = maxdiff
525
526     def _find_parsers(self):
527         registry = gst.registry_get_default()
528         ret = {}
529         for f in registry.get_feature_list(gst.ElementFactory):
530             if f.get_klass().find('Parser') >= 0:
531                 for t in f.get_static_pad_templates():
532                     if t.direction == gst.PAD_SINK:
533                         for s in t.get_caps():
534                             ret[s.get_name()] = f.get_name()
535                         break
536         return ret
537
538     def _new_demuxed_pad(self, element, pad):
539         format = pad.get_caps()[0].get_name()
540
541         if format not in self.parsefactories:
542             self.async_error("Unsupported media type: %s", format)
543             return
544
545         queue = gst.element_factory_make('queue', 'queue_' + format)
546         queue.set_property('max-size-buffers', 0)
547         queue.set_property('max-size-bytes', 0)
548         print self.maxdiff
549         queue.set_property('max-size-time', int(self.maxdiff * 1.5))
550         parser = gst.element_factory_make(self.parsefactories[format])
551         self.add(queue)
552         self.add(parser)
553         queue.set_state(gst.STATE_PAUSED)
554         parser.set_state(gst.STATE_PAUSED)
555         pad.link(queue.get_compatible_pad(pad))
556         queue.link(parser)
557         parser.link(self.mux)
558         self.parsers.append(parser)
559
560         print repr(self.sync_points)
561
562         if 'video' in format:
563             parser.set_property('synchronization-points',
564                                 self.sync_points)
565
566 class PlayerWindow(gtk.Window):
567     UPDATE_INTERVAL = 500
568     def __init__(self):
569         gtk.Window.__init__(self)
570         self.set_default_size(600, 500)
571
572         self.create_ui()
573
574         self.player = GstPlayer(self.videowidget)
575
576         def on_eos():
577             self.player.seek(0L)
578             self.play_toggled()
579         self.player.on_eos = lambda *x: on_eos()
580         
581         self.update_id = -1
582         self.changed_id = -1
583         self.seek_timeout_id = -1
584
585         self.p_position = gst.CLOCK_TIME_NONE
586         self.p_duration = gst.CLOCK_TIME_NONE
587
588         def on_delete_event():
589             self.player.stop()
590             gtk.main_quit()
591         self.connect('delete-event', lambda *x: on_delete_event())
592
593     def load_file(self, location):
594         filename = location.split('/')[-1]
595         self.set_title('%s munger' % filename)
596         self.player.set_location(location)
597         if self.videowidget.flags() & gtk.REALIZED:
598             self.play_toggled()
599         else:
600             self.videowidget.connect_after('realize',
601                                            lambda *x: self.play_toggled())
602                                   
603     def create_ui(self):
604         vbox = gtk.VBox()
605         vbox.show()
606         self.add(vbox)
607
608         self.videowidget = VideoWidget()
609         self.videowidget.show()
610         vbox.pack_start(self.videowidget)
611         
612         hbox = gtk.HBox()
613         hbox.show()
614         vbox.pack_start(hbox, fill=False, expand=False)
615         
616         self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0)
617         hscale = gtk.HScale(self.adjustment)
618         hscale.set_digits(2)
619         hscale.set_update_policy(gtk.UPDATE_CONTINUOUS)
620         hscale.connect('button-press-event', self.scale_button_press_cb)
621         hscale.connect('button-release-event', self.scale_button_release_cb)
622         hscale.connect('format-value', self.scale_format_value_cb)
623         hbox.pack_start(hscale)
624         hscale.show()
625         self.hscale = hscale
626
627         table = gtk.Table(3,3)
628         table.show()
629         vbox.pack_start(table, fill=False, expand=False, padding=6)
630
631         self.button = button = gtk.Button(stock=gtk.STOCK_MEDIA_PLAY)
632         button.set_property('can-default', True)
633         button.set_focus_on_click(False)
634         button.show()
635
636         # problem: play and paused are of different widths and cause the
637         # window to re-layout
638         # "solution": add more buttons to a vbox so that the horizontal
639         # width is enough
640         bvbox = gtk.VBox()
641         bvbox.add(button)
642         bvbox.add(gtk.Button(stock=gtk.STOCK_MEDIA_PLAY))
643         bvbox.add(gtk.Button(stock=gtk.STOCK_MEDIA_PAUSE))
644         sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
645         for kid in bvbox.get_children():
646             sizegroup.add_widget(kid)
647         bvbox.show()
648         table.attach(bvbox, 0, 1, 1, 3, gtk.FILL, gtk.FILL)
649         
650         # can't set this property before the button has a window
651         button.set_property('has-default', True)
652         button.connect('clicked', lambda *args: self.play_toggled())
653
654         self.sync = sync = SyncPoints(self)
655         sync.show()
656         table.attach(sync, 1, 2, 0, 3, gtk.EXPAND, gtk.EXPAND|gtk.FILL, 12)
657         # nasty things to get sizes
658         l = gtk.Label('\n\n\n')
659         l.show()
660         table.attach(l, 0, 1, 0, 1, 0, 0, 0)
661         l = gtk.Label('\n\n\n')
662         l.show()
663         table.attach(l, 2, 3, 0, 1, 0, 0, 0)
664
665         button = gtk.Button("_Open other movie...")
666         button.show()
667         button.connect('clicked', lambda *x: self.do_choose_file())
668         table.attach(button, 2, 3, 1, 2, gtk.FILL, gtk.FILL)
669
670         button = gtk.Button("_Write to disk")
671         button.set_property('image',
672                             gtk.image_new_from_stock(gtk.STOCK_SAVE_AS,
673                                                      gtk.ICON_SIZE_BUTTON))
674         button.connect('clicked', lambda *x: self.do_remux())
675         button.show()
676         table.attach(button, 2, 3, 2, 3, gtk.FILL, gtk.FILL)
677
678     def do_remux(self):
679         if self.player.is_playing():
680             self.play_toggled()
681         in_uri = self.player.get_location()
682         out_uri = in_uri[:-4] + '-remuxed.ogg'
683         r = Resynchronizer(in_uri, out_uri, self.sync.get_sync_points())
684         r.run(self)
685
686     def do_choose_file(self):
687         if self.player.is_playing():
688             self.play_toggled()
689         chooser = gtk.FileChooserDialog('Choose a movie to bork bork bork',
690                                         self,
691                                         buttons=(gtk.STOCK_CANCEL,
692                                                  CANCELLED,
693                                                  gtk.STOCK_OPEN,
694                                                  SUCCESS))
695         chooser.set_local_only(False)
696         chooser.set_select_multiple(False)
697         f = gtk.FileFilter()
698         f.set_name("All files")
699         f.add_pattern("*")
700         chooser.add_filter(f)
701         f = gtk.FileFilter()
702         f.set_name("Ogg files")
703         f.add_pattern("*.ogg") # as long as this is the only thing we
704                                # support...
705         chooser.add_filter(f)
706         chooser.set_filter(f)
707         
708         prev = self.player.get_location()
709         if prev:
710             chooser.set_uri(prev)
711
712         resp = chooser.run()
713         uri = chooser.get_uri()
714         chooser.destroy()
715
716         if resp == SUCCESS:
717             self.load_file(uri)
718             return True
719         else:
720             return False
721         
722     def play_toggled(self):
723         if self.player.is_playing():
724             self.player.pause()
725             self.button.set_label(gtk.STOCK_MEDIA_PLAY)
726         else:
727             self.player.play()
728             if self.update_id == -1:
729                 self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
730                                                      self.update_scale_cb)
731             self.button.set_label(gtk.STOCK_MEDIA_PAUSE)
732
733     def scale_format_value_cb(self, scale, value):
734         if self.p_duration == -1:
735             real = 0
736         else:
737             real = value * self.p_duration / 100
738         
739         seconds = real / gst.SECOND
740
741         return "%02d:%02d" % (seconds / 60, seconds % 60)
742
743     def scale_button_press_cb(self, widget, event):
744         # see seek.c:start_seek
745         gst.debug('starting seek')
746         
747         self.button.set_sensitive(False)
748         self.was_playing = self.player.is_playing()
749         if self.was_playing:
750             self.player.pause()
751
752         # don't timeout-update position during seek
753         if self.update_id != -1:
754             gobject.source_remove(self.update_id)
755             self.update_id = -1
756
757         # make sure we get changed notifies
758         if self.changed_id == -1:
759             self.changed_id = self.hscale.connect('value-changed',
760                 self.scale_value_changed_cb)
761             
762     def scale_value_changed_cb(self, scale):
763         # see seek.c:seek_cb
764         real = long(scale.get_value() * self.p_duration / 100) # in ns
765         gst.debug('value changed, perform seek to %r' % real)
766         self.player.seek(real)
767         # allow for a preroll
768         self.player.get_state(timeout=50*gst.MSECOND) # 50 ms
769
770     def scale_button_release_cb(self, widget, event):
771         # see seek.cstop_seek
772         widget.disconnect(self.changed_id)
773         self.changed_id = -1
774
775         self.button.set_sensitive(True)
776         if self.seek_timeout_id != -1:
777             gobject.source_remove(self.seek_timeout_id)
778             self.seek_timeout_id = -1
779         else:
780             gst.debug('released slider, setting back to playing')
781             if self.was_playing:
782                 self.player.play()
783
784         if self.update_id != -1:
785             self.error('Had a previous update timeout id')
786         else:
787             self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
788                 self.update_scale_cb)
789
790     def update_scale_cb(self):
791         had_duration = self.p_duration != gst.CLOCK_TIME_NONE
792         self.p_position, self.p_duration = self.player.query_position()
793         if self.p_position != gst.CLOCK_TIME_NONE:
794             value = self.p_position * 100.0 / self.p_duration
795             self.adjustment.set_value(value)
796         return True
797
798 def main(args):
799     def usage():
800         sys.stderr.write("usage: %s [URI-OF-MEDIA-FILE]\n" % args[0])
801         return 1
802
803     w = PlayerWindow()
804     w.show()
805
806     if len(args) == 1:
807         if not w.do_choose_file():
808             return 1
809     elif len(args) == 2:
810         if not gst.uri_is_valid(args[1]):
811             sys.stderr.write("Error: Invalid URI: %s\n" % args[1])
812             return 1
813         w.load_file(args[1])
814     else:
815         return usage()
816
817     gtk.main()
818
819 if __name__ == '__main__':
820     sys.exit(main(sys.argv))