home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / share / doc / python-gst0.10 / examples / remuxer.py < prev    next >
Encoding:
Python Source  |  2006-07-31  |  28.5 KB  |  835 lines

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