qtdemux: Don't emit GstSegment correcting start time when in MSE mode
[platform/upstream/gstreamer.git] / subprojects / gst-python / old_examples / remuxer.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 TimeControl(gtk.HBox):
133     # all labels same size
134     sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
135     __gproperties__ = {'time': (gobject.TYPE_UINT64, 'Time', 'Time',
136                                 # not actually usable: see #335854
137                                 # kept for .notify() usage
138                                 0L, (1<<63)-1, 0L,
139                                 gobject.PARAM_READABLE)}
140
141     def __init__(self, window, label):
142         gtk.HBox.__init__(self)
143         self.pwindow = window
144         self.label = label
145         self.create_ui()
146
147     def get_property(self, param, pspec):
148         if param == 'time':
149             return self.get_time()
150         else:
151             assert param in self.__gproperties__, \
152                    'Unknown property: %s' % param
153
154     def create_ui(self):
155         label = gtk.Label(self.label + ": ")
156         label.show()
157         a = gtk.Alignment(1.0, 0.5)
158         a.add(label)
159         a.set_padding(0, 0, 12, 0)
160         a.show()
161         self.sizegroup.add_widget(a)
162         self.pack_start(a, True, False, 0)
163
164         self.minutes = minutes = gtk.Entry(5)
165         minutes.set_width_chars(5)
166         minutes.set_alignment(1.0)
167         minutes.connect('changed', lambda *x: self.notify('time'))
168         minutes.connect_after('activate', lambda *x: self.activated())
169         label2 = gtk.Label(":")
170         self.seconds = seconds = gtk.Entry(2)
171         seconds.set_width_chars(2)
172         seconds.set_alignment(1.0)
173         seconds.connect('changed', lambda *x: self.notify('time'))
174         seconds.connect_after('activate', lambda *x: self.activated())
175         label3 = gtk.Label(".")
176         self.milliseconds = milliseconds = gtk.Entry(3)
177         milliseconds.set_width_chars(3)
178         milliseconds.set_alignment(0.0)
179         milliseconds.connect('changed', lambda *x: self.notify('time'))
180         milliseconds.connect_after('activate', lambda *x: self.activated())
181         set = gtk.Button('Set')
182         goto = gtk.Button('Go')
183         goto.set_property('image',
184                           gtk.image_new_from_stock(gtk.STOCK_JUMP_TO,
185                                                    gtk.ICON_SIZE_BUTTON))
186         for w in minutes, label2, seconds, label3, milliseconds:
187             w.show()
188             self.pack_start(w, False)
189         set.show()
190         self.pack_start(set, False, False, 6)
191         goto.show()
192         self.pack_start(goto, False, False, 0)
193         set.connect('clicked', lambda *x: self.set_now())
194         goto.connect('clicked', lambda *x: self.activated())
195         pad = gtk.Label("")
196         pad.show()
197         self.pack_start(pad, True, False, 0)
198
199     def get_time(self):
200         time = 0
201         for w, multiplier in ((self.minutes, gst.SECOND*60),
202                               (self.seconds, gst.SECOND),
203                               (self.milliseconds, gst.MSECOND)):
204             text = w.get_text()
205             try:
206                 val = int(text)
207             except ValueError:
208                 val = 0
209             w.set_text(val and str(val) or '0')
210             time += val * multiplier
211         return time
212
213     def set_time(self, time):
214         if time == gst.CLOCK_TIME_NONE:
215             print "Can't set '%s' (invalid time)" % self.label
216             return
217         self.freeze_notify()
218         for w, multiplier in ((self.minutes, gst.SECOND*60),
219                               (self.seconds, gst.SECOND),
220                               (self.milliseconds, gst.MSECOND)):
221             val = time // multiplier
222             w.set_text(str(val))
223             time -= val * multiplier
224         self.thaw_notify()
225
226     def set_now(self):
227         time, dur = self.pwindow.player.query_position()
228         self.set_time(time)
229
230     def activated(self):
231         time = self.get_time()
232         if self.pwindow.player.is_playing():
233             self.pwindow.play_toggled()
234         self.pwindow.player.seek(time)
235         self.pwindow.player.get_state(timeout=gst.MSECOND * 200)
236
237 class ProgressDialog(gtk.Dialog):
238     def __init__(self, title, description, task, parent, flags, buttons):
239         gtk.Dialog.__init__(self, title, parent, flags, buttons)
240         self._create_ui(title, description, task)
241
242     def _create_ui(self, title, description, task):
243         self.set_border_width(6)
244         self.set_resizable(False)
245         self.set_has_separator(False)
246
247         vbox = gtk.VBox()
248         vbox.set_border_width(6)
249         vbox.show()
250         self.vbox.pack_start(vbox, False)
251
252         label = gtk.Label('<big><b>%s</b></big>' % title)
253         label.set_use_markup(True)
254         label.set_alignment(0.0, 0.0)
255         label.show()
256         vbox.pack_start(label, False)
257         
258         label = gtk.Label(description)
259         label.set_use_markup(True)
260         label.set_alignment(0.0, 0.0)
261         label.set_line_wrap(True)
262         label.set_padding(0, 12)
263         label.show()
264         vbox.pack_start(label, False)
265
266         self.progress = progress = gtk.ProgressBar()
267         progress.show()
268         vbox.pack_start(progress, False)
269
270         self.progresstext = label = gtk.Label('')
271         label.set_line_wrap(True)
272         label.set_use_markup(True)
273         label.set_alignment(0.0, 0.0)
274         label.show()
275         vbox.pack_start(label)
276         self.set_task(task)
277
278     def set_task(self, task):
279         self.progresstext.set_markup('<i>%s</i>' % task)
280
281 UNKNOWN = 0
282 SUCCESS = 1
283 FAILURE = 2
284 CANCELLED = 3
285
286 class RemuxProgressDialog(ProgressDialog):
287     def __init__(self, parent, start, stop, fromname, toname):
288         ProgressDialog.__init__(self,
289                                 "Writing to disk",
290                                 ('Writing the selected segment of <b>%s</b> '
291                                  'to <b>%s</b>. This may take some time.'
292                                  % (fromname, toname)),
293                                 'Starting media pipeline',
294                                 parent,
295                                 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
296                                 (gtk.STOCK_CANCEL, CANCELLED,
297                                  gtk.STOCK_CLOSE, SUCCESS))
298         self.start = start
299         self.stop = stop
300         self.update_position(start)
301         self.set_completed(False)
302         
303     def update_position(self, pos):
304         pos = min(max(pos, self.start), self.stop)
305         remaining = self.stop - pos
306         minutes = remaining // (gst.SECOND * 60)
307         seconds = (remaining - minutes * gst.SECOND * 60) // gst.SECOND
308         self.progress.set_text('%d:%02d of video remaining' % (minutes, seconds))
309         self.progress.set_fraction(1.0 - float(remaining) / (self.stop - self.start))
310
311     def set_completed(self, completed):
312         self.set_response_sensitive(CANCELLED, not completed)
313         self.set_response_sensitive(SUCCESS, completed)
314
315 def set_connection_blocked_async_marshalled(pads, proc, *args, **kwargs):
316     def clear_list(l):
317         while l:
318             l.pop()
319
320     to_block = list(pads)
321     to_relink = [(x, x.get_peer()) for x in pads]
322
323     def on_pad_blocked_sync(pad, is_blocked):
324         if pad not in to_block:
325             # can happen after the seek and before unblocking -- racy,
326             # but no prob, bob.
327             return
328         to_block.remove(pad)
329         if not to_block:
330             # marshal to main thread
331             gobject.idle_add(on_pads_blocked)
332
333     def on_pads_blocked():
334         for src, sink in to_relink:
335             src.link(sink)
336         proc(*args, **kwargs)
337         for src, sink in to_relink:
338             src.set_blocked_async(False, lambda *x: None)
339         clear_list(to_relink)
340
341     for src, sink in to_relink:
342         src.unlink(sink)
343         src.set_blocked_async(True, on_pad_blocked_sync)
344
345 class Remuxer(gst.Pipeline):
346
347     __gsignals__ = {'done': (gobject.SIGNAL_RUN_LAST, None, (int,))}
348
349     def __init__(self, fromuri, touri, start, stop):
350         # HACK: should do Pipeline.__init__, but that doesn't do what we
351         # want; there's a bug open aboooot that
352         self.__gobject_init__()
353
354         assert start >= 0
355         assert stop > start
356
357         self.fromuri = fromuri
358         self.touri = None
359         self.start_time = start
360         self.stop_time = stop
361
362         self.src = self.remuxbin = self.sink = None
363         self.resolution = UNKNOWN
364
365         self.window = None
366         self.pdialog = None
367
368         self._query_id = -1
369
370     def do_setup_pipeline(self):
371         self.src = gst.element_make_from_uri(gst.URI_SRC, self.fromuri)
372         self.remuxbin = RemuxBin(self.start_time, self.stop_time)
373         self.sink = gst.element_make_from_uri(gst.URI_SINK, self.touri)
374         self.resolution = UNKNOWN
375
376         if gobject.signal_lookup('allow-overwrite', self.sink.__class__):
377             self.sink.connect('allow-overwrite', lambda *x: True)
378
379         self.add(self.src, self.remuxbin, self.sink)
380
381         self.src.link(self.remuxbin)
382         self.remuxbin.link(self.sink)
383
384     def do_get_touri(self):
385         chooser = gtk.FileChooserDialog('Save as...',
386                                         self.window,
387                                         action=gtk.FILE_CHOOSER_ACTION_SAVE,
388                                         buttons=(gtk.STOCK_CANCEL,
389                                                  CANCELLED,
390                                                  gtk.STOCK_SAVE,
391                                                  SUCCESS))
392         chooser.set_uri(self.fromuri) # to select the folder
393         chooser.unselect_all()
394         chooser.set_do_overwrite_confirmation(True)
395         name = self.fromuri.split('/')[-1][:-4] + '-remuxed.ogg'
396         chooser.set_current_name(name)
397         resp = chooser.run()
398         uri = chooser.get_uri()
399         chooser.destroy()
400
401         if resp == SUCCESS:
402             return uri
403         else:
404             return None
405
406     def _start_queries(self):
407         def do_query():
408             try:
409                 # HACK: self.remuxbin.query() should do the same
410                 # (requires implementing a vmethod, dunno how to do that
411                 # although i think it's possible)
412                 # HACK: why does self.query_position(..) not give useful
413                 # answers? 
414                 pad = self.remuxbin.get_pad('src')
415                 pos, duration = pad.query_position(gst.FORMAT_TIME)
416                 if pos != gst.CLOCK_TIME_NONE:
417                     self.pdialog.update_position(pos)
418             except:
419                 # print 'query failed'
420                 pass
421             return True
422         if self._query_id == -1:
423             self._query_id = gobject.timeout_add(100, # 10 Hz
424                                                  do_query)
425
426     def _stop_queries(self):
427         if self._query_id != -1:
428             gobject.source_remove(self._query_id)
429             self._query_id = -1
430
431     def _bus_watch(self, bus, message):
432         if message.type == gst.MESSAGE_ERROR:
433             print 'error', message
434             self._stop_queries()
435             m = gtk.MessageDialog(self.window,
436                                   gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
437                                   gtk.MESSAGE_ERROR,
438                                   gtk.BUTTONS_CLOSE,
439                                   "Error processing file")
440             gerror, debug = message.parse_error()
441             txt = ('There was an error processing your file: %s\n\n'
442                    'Debug information:\n%s' % (gerror, debug))
443             m.format_secondary_text(txt)
444             m.run()
445             m.destroy()
446             self.response(FAILURE)
447         elif message.type == gst.MESSAGE_WARNING:
448             print 'warning', message
449         elif message.type == gst.MESSAGE_EOS:
450             # print 'eos, woot', message.src
451             name = self.touri
452             if name.startswith('file://'):
453                 name = name[7:]
454             self.pdialog.set_task('Finished writing %s' % name)
455             self.pdialog.update_position(self.stop_time)
456             self._stop_queries()
457             self.pdialog.set_completed(True)
458         elif message.type == gst.MESSAGE_STATE_CHANGED:
459             if message.src == self:
460                 old, new, pending = message.parse_state_changed()
461                 if ((old, new, pending) ==
462                     (gst.STATE_READY, gst.STATE_PAUSED,
463                      gst.STATE_VOID_PENDING)):
464                     self.pdialog.set_task('Processing file')
465                     self.pdialog.update_position(self.start_time)
466                     self._start_queries()
467                     self.set_state(gst.STATE_PLAYING)
468
469     def response(self, response):
470         assert self.resolution == UNKNOWN
471         self.resolution = response
472         self.set_state(gst.STATE_NULL)
473         self.pdialog.destroy()
474         self.pdialog = None
475         self.window.set_sensitive(True)
476         self.emit('done', response)
477
478     def start(self, main_window):
479         self.window = main_window
480         self.touri = self.do_get_touri()
481         if not self.touri:
482             return False
483         self.do_setup_pipeline()
484         bus = self.get_bus()
485         bus.add_signal_watch()
486         bus.connect('message', self._bus_watch)
487         if self.window:
488             # can be None if we are debugging...
489             self.window.set_sensitive(False)
490         fromname = self.fromuri.split('/')[-1]
491         toname = self.touri.split('/')[-1]
492         self.pdialog = RemuxProgressDialog(main_window, self.start_time,
493                                            self.stop_time, fromname, toname)
494         self.pdialog.show()
495         self.pdialog.connect('response', lambda w, r: self.response(r))
496
497         self.set_state(gst.STATE_PAUSED)
498         return True
499         
500     def run(self, main_window):
501         if self.start(main_window):
502             loop = gobject.MainLoop()
503             self.connect('done', lambda *x: gobject.idle_add(loop.quit))
504             loop.run()
505         else:
506             self.resolution = CANCELLED
507         return self.resolution
508         
509 class RemuxBin(gst.Bin):
510     def __init__(self, start_time, stop_time):
511         self.__gobject_init__()
512
513         self.parsefactories = self._find_parsers()
514         self.parsers = []
515
516         self.demux = gst.element_factory_make('oggdemux')
517         self.mux = gst.element_factory_make('oggmux')
518
519         self.add(self.demux, self.mux)
520
521         self.add_pad(gst.GhostPad('sink', self.demux.get_pad('sink')))
522         self.add_pad(gst.GhostPad('src', self.mux.get_pad('src')))
523
524         self.demux.connect('pad-added', self._new_demuxed_pad)
525         self.demux.connect('no-more-pads', self._no_more_pads)
526
527         self.start_time = start_time
528         self.stop_time = stop_time
529
530     def _find_parsers(self):
531         registry = gst.registry_get_default()
532         ret = {}
533         for f in registry.get_feature_list(gst.ElementFactory):
534             if f.get_klass().find('Parser') >= 0:
535                 for t in f.get_static_pad_templates():
536                     if t.direction == gst.PAD_SINK:
537                         for s in t.get_caps():
538                             ret[s.get_name()] = f.get_name()
539                         break
540         return ret
541
542     def _new_demuxed_pad(self, element, pad):
543         format = pad.get_caps()[0].get_name()
544
545         if format not in self.parsefactories:
546             self.async_error("Unsupported media type: %s", format)
547             return
548
549         queue = gst.element_factory_make('queue', None);
550         queue.set_property('max-size-buffers', 1000)
551         parser = gst.element_factory_make(self.parsefactories[format])
552         self.add(queue)
553         self.add(parser)
554         queue.set_state(gst.STATE_PAUSED)
555         parser.set_state(gst.STATE_PAUSED)
556         pad.link(queue.get_compatible_pad(pad))
557         queue.link(parser)
558         parser.link(self.mux)
559         self.parsers.append(parser)
560
561     def _do_seek(self):
562         flags = gst.SEEK_FLAG_FLUSH
563         # HACK: self.seek should work, should try that at some point
564         return self.demux.seek(1.0, gst.FORMAT_TIME, flags,
565                                gst.SEEK_TYPE_SET, self.start_time,
566                                gst.SEEK_TYPE_SET, self.stop_time)
567
568     def _no_more_pads(self, element):
569         pads = [x.get_pad('src') for x in self.parsers]
570         set_connection_blocked_async_marshalled(pads,
571                                                 self._do_seek)
572
573
574 class PlayerWindow(gtk.Window):
575     UPDATE_INTERVAL = 500
576     def __init__(self):
577         gtk.Window.__init__(self)
578         self.set_default_size(600, 425)
579
580         self.create_ui()
581
582         self.player = GstPlayer(self.videowidget)
583
584         def on_eos():
585             self.player.seek(0L)
586             self.play_toggled()
587         self.player.on_eos = lambda *x: on_eos()
588         
589         self.update_id = -1
590         self.changed_id = -1
591         self.seek_timeout_id = -1
592
593         self.p_position = gst.CLOCK_TIME_NONE
594         self.p_duration = gst.CLOCK_TIME_NONE
595
596         def on_delete_event():
597             self.player.stop()
598             gtk.main_quit()
599         self.connect('delete-event', lambda *x: on_delete_event())
600
601     def load_file(self, location):
602         filename = location.split('/')[-1]
603         self.set_title('%s munger' % filename)
604         self.player.set_location(location)
605         if self.videowidget.flags() & gtk.REALIZED:
606             self.play_toggled()
607         else:
608             self.videowidget.connect_after('realize',
609                                            lambda *x: self.play_toggled())
610                                   
611     def create_ui(self):
612         vbox = gtk.VBox()
613         vbox.show()
614         self.add(vbox)
615
616         self.videowidget = VideoWidget()
617         self.videowidget.show()
618         vbox.pack_start(self.videowidget)
619         
620         hbox = gtk.HBox()
621         hbox.show()
622         vbox.pack_start(hbox, fill=False, expand=False)
623         
624         self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0)
625         hscale = gtk.HScale(self.adjustment)
626         hscale.set_digits(2)
627         hscale.set_update_policy(gtk.UPDATE_CONTINUOUS)
628         hscale.connect('button-press-event', self.scale_button_press_cb)
629         hscale.connect('button-release-event', self.scale_button_release_cb)
630         hscale.connect('format-value', self.scale_format_value_cb)
631         hbox.pack_start(hscale)
632         hscale.show()
633         self.hscale = hscale
634
635         table = gtk.Table(2,3)
636         table.show()
637         vbox.pack_start(table, fill=False, expand=False, padding=6)
638
639         self.button = button = gtk.Button(stock=gtk.STOCK_MEDIA_PLAY)
640         button.set_property('can-default', True)
641         button.set_focus_on_click(False)
642         button.show()
643
644         # problem: play and paused are of different widths and cause the
645         # window to re-layout
646         # "solution": add more buttons to a vbox so that the horizontal
647         # width is enough
648         bvbox = gtk.VBox()
649         bvbox.add(button)
650         bvbox.add(gtk.Button(stock=gtk.STOCK_MEDIA_PLAY))
651         bvbox.add(gtk.Button(stock=gtk.STOCK_MEDIA_PAUSE))
652         sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
653         for kid in bvbox.get_children():
654             sizegroup.add_widget(kid)
655         bvbox.show()
656         table.attach(bvbox, 0, 1, 0, 2, gtk.FILL, gtk.FILL)
657         
658         # can't set this property before the button has a window
659         button.set_property('has-default', True)
660         button.connect('clicked', lambda *args: self.play_toggled())
661
662         self.cutin = cut = TimeControl(self, "Cut in time")
663         cut.show()
664         table.attach(cut, 1, 2, 0, 1, gtk.EXPAND, 0, 12)
665
666         self.cutout = cut = TimeControl(self, "Cut out time")
667         cut.show()
668         table.attach(cut, 1, 2, 1, 2, gtk.EXPAND, 0, 12)
669
670         button = gtk.Button("_Open other movie...")
671         button.show()
672         button.connect('clicked', lambda *x: self.do_choose_file())
673         table.attach(button, 2, 3, 0, 1, gtk.FILL, gtk.FILL)
674
675         button = gtk.Button("_Write to disk")
676         button.set_property('image',
677                             gtk.image_new_from_stock(gtk.STOCK_SAVE_AS,
678                                                      gtk.ICON_SIZE_BUTTON))
679         button.connect('clicked', lambda *x: self.do_remux())
680         button.show()
681         table.attach(button, 2, 3, 1, 2, gtk.FILL, gtk.FILL)
682
683         #self.cutin.connect('notify::time', lambda *x: self.check_cutout())
684         #self.cutout.connect('notify::time', lambda *x: self.check_cutin())
685
686     def do_remux(self):
687         if self.player.is_playing():
688             self.play_toggled()
689         in_uri = self.player.get_location()
690         out_uri = in_uri[:-4] + '-remuxed.ogg'
691         r = Remuxer(in_uri, out_uri,
692                     self.cutin.get_time(), self.cutout.get_time())
693         r.run(self)
694
695     def do_choose_file(self):
696         if self.player.is_playing():
697             self.play_toggled()
698         chooser = gtk.FileChooserDialog('Choose a movie to cut cut cut',
699                                         self,
700                                         buttons=(gtk.STOCK_CANCEL,
701                                                  CANCELLED,
702                                                  gtk.STOCK_OPEN,
703                                                  SUCCESS))
704         chooser.set_local_only(False)
705         chooser.set_select_multiple(False)
706         f = gtk.FileFilter()
707         f.set_name("All files")
708         f.add_pattern("*")
709         chooser.add_filter(f)
710         f = gtk.FileFilter()
711         f.set_name("Ogg files")
712         f.add_pattern("*.og[gvax]") # as long as this is the only thing we
713                                # support...
714         chooser.add_filter(f)
715         chooser.set_filter(f)
716         
717         prev = self.player.get_location()
718         if prev:
719             chooser.set_uri(prev)
720
721         resp = chooser.run()
722         uri = chooser.get_uri()
723         chooser.destroy()
724
725         if resp == SUCCESS and uri != None:
726             self.load_file(uri)
727             return True
728         else:
729             return False
730         
731     def check_cutout(self):
732         if self.cutout.get_time() <= self.cutin.get_time():
733             pos, dur = self.player.query_position()
734             self.cutout.set_time(dur)
735
736     def check_cutin(self):
737         if self.cutin.get_time() >= self.cutout.get_time():
738             self.cutin.set_time(0)
739
740     def play_toggled(self):
741         if self.player.is_playing():
742             self.player.pause()
743             self.button.set_label(gtk.STOCK_MEDIA_PLAY)
744         else:
745             self.player.play()
746             if self.update_id == -1:
747                 self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
748                                                      self.update_scale_cb)
749             self.button.set_label(gtk.STOCK_MEDIA_PAUSE)
750
751     def scale_format_value_cb(self, scale, value):
752         if self.p_duration == -1:
753             real = 0
754         else:
755             real = value * self.p_duration / 100
756         
757         seconds = real / gst.SECOND
758
759         return "%02d:%02d" % (seconds / 60, seconds % 60)
760
761     def scale_button_press_cb(self, widget, event):
762         # see seek.c:start_seek
763         gst.debug('starting seek')
764         
765         self.button.set_sensitive(False)
766         self.was_playing = self.player.is_playing()
767         if self.was_playing:
768             self.player.pause()
769
770         # don't timeout-update position during seek
771         if self.update_id != -1:
772             gobject.source_remove(self.update_id)
773             self.update_id = -1
774
775         # make sure we get changed notifies
776         if self.changed_id == -1:
777             self.changed_id = self.hscale.connect('value-changed',
778                 self.scale_value_changed_cb)
779             
780     def scale_value_changed_cb(self, scale):
781         # see seek.c:seek_cb
782         real = long(scale.get_value() * self.p_duration / 100) # in ns
783         gst.debug('value changed, perform seek to %r' % real)
784         self.player.seek(real)
785         # allow for a preroll
786         self.player.get_state(timeout=50*gst.MSECOND) # 50 ms
787
788     def scale_button_release_cb(self, widget, event):
789         # see seek.cstop_seek
790         widget.disconnect(self.changed_id)
791         self.changed_id = -1
792
793         self.button.set_sensitive(True)
794         if self.seek_timeout_id != -1:
795             gobject.source_remove(self.seek_timeout_id)
796             self.seek_timeout_id = -1
797         else:
798             gst.debug('released slider, setting back to playing')
799             if self.was_playing:
800                 self.player.play()
801
802         if self.update_id != -1:
803             self.error('Had a previous update timeout id')
804         else:
805             self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
806                 self.update_scale_cb)
807
808     def update_scale_cb(self):
809         had_duration = self.p_duration != gst.CLOCK_TIME_NONE
810         self.p_position, self.p_duration = self.player.query_position()
811         if self.p_position != gst.CLOCK_TIME_NONE:
812             value = self.p_position * 100.0 / self.p_duration
813             self.adjustment.set_value(value)
814             if not had_duration:
815                 self.cutin.set_time(0)
816         return True
817
818 def main(args):
819     def usage():
820         sys.stderr.write("usage: %s [URI-OF-MEDIA-FILE]\n" % args[0])
821         return 1
822
823     w = PlayerWindow()
824     w.show()
825
826     if len(args) == 1:
827         if not w.do_choose_file():
828             return 1
829     elif len(args) == 2:
830         if not gst.uri_is_valid(args[1]):
831             sys.stderr.write("Error: Invalid URI: %s\n" % args[1])
832             return 1
833         w.load_file(args[1])
834     else:
835         return usage()
836
837     gtk.main()
838
839 if __name__ == '__main__':
840     sys.exit(main(sys.argv))