From 3274a3ac031907aca2abfbb469945951e9214c9a Mon Sep 17 00:00:00 2001 From: Andy Wingo Date: Fri, 4 Aug 2006 16:42:15 +0000 Subject: [PATCH] examples/synchronizer.py: New file, a bit of a hack to remuxer.py, but for resyncing a bad ogg. Only UI at the moment.. Original commit message from CVS: 2006-08-04 Andy Wingo * examples/synchronizer.py: New file, a bit of a hack to remuxer.py, but for resyncing a bad ogg. Only UI at the moment.. --- ChangeLog | 5 + common | 2 +- examples/synchronizer.py | 839 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 845 insertions(+), 1 deletion(-) create mode 100755 examples/synchronizer.py diff --git a/ChangeLog b/ChangeLog index 658119f..5b871e2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2006-08-04 Andy Wingo + + * examples/synchronizer.py: New file, a bit of a hack to + remuxer.py, but for resyncing a bad ogg. Only UI at the moment.. + 2006-07-28 Andy Wingo * examples/remuxer.py (RemuxBin._do_seek, Remuxer._bus_watch): Use diff --git a/common b/common index ef97fb3..e9ea99f 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit ef97fb3278d98a1fdb32e5c6b2a7467116ffc160 +Subproject commit e9ea99f6e89d7e1af3a0a859bfeb0ed6ecf2e3a9 diff --git a/examples/synchronizer.py b/examples/synchronizer.py new file mode 100755 index 0000000..320de13 --- /dev/null +++ b/examples/synchronizer.py @@ -0,0 +1,839 @@ +#!/usr/bin/env python +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 + +import pygtk +pygtk.require('2.0') + +import sys + +import gobject +gobject.threads_init() + +import pygst +pygst.require('0.10') +import gst +import gst.interfaces +import gtk + +class GstPlayer: + def __init__(self, videowidget): + self.playing = False + self.player = gst.element_factory_make("playbin", "player") + self.videowidget = videowidget + + bus = self.player.get_bus() + bus.enable_sync_message_emission() + bus.add_signal_watch() + bus.connect('sync-message::element', self.on_sync_message) + bus.connect('message', self.on_message) + + def on_sync_message(self, bus, message): + if message.structure is None: + return + if message.structure.get_name() == 'prepare-xwindow-id': + self.videowidget.set_sink(message.src) + message.src.set_property('force-aspect-ratio', True) + + def on_message(self, bus, message): + t = message.type + if t == gst.MESSAGE_ERROR: + err, debug = message.parse_error() + print "Error: %s" % err, debug + if self.on_eos: + self.on_eos() + self.playing = False + elif t == gst.MESSAGE_EOS: + if self.on_eos: + self.on_eos() + self.playing = False + + def set_location(self, location): + self.player.set_state(gst.STATE_NULL) + self.player.set_property('uri', location) + + def get_location(self): + return self.player.get_property('uri') + + def query_position(self): + "Returns a (position, duration) tuple" + try: + position, format = self.player.query_position(gst.FORMAT_TIME) + except: + position = gst.CLOCK_TIME_NONE + + try: + duration, format = self.player.query_duration(gst.FORMAT_TIME) + except: + duration = gst.CLOCK_TIME_NONE + + return (position, duration) + + def seek(self, location): + """ + @param location: time to seek to, in nanoseconds + """ + gst.debug("seeking to %r" % location) + event = gst.event_new_seek(1.0, gst.FORMAT_TIME, + gst.SEEK_FLAG_FLUSH, + gst.SEEK_TYPE_SET, location, + gst.SEEK_TYPE_NONE, 0) + + res = self.player.send_event(event) + if res: + gst.info("setting new stream time to 0") + self.player.set_new_stream_time(0L) + else: + gst.error("seek to %r failed" % location) + + def pause(self): + gst.info("pausing player") + self.player.set_state(gst.STATE_PAUSED) + self.playing = False + + def play(self): + gst.info("playing player") + self.player.set_state(gst.STATE_PLAYING) + self.playing = True + + def stop(self): + self.player.set_state(gst.STATE_NULL) + gst.info("stopped player") + + def get_state(self, timeout=1): + return self.player.get_state(timeout=timeout) + + def is_playing(self): + return self.playing + +class VideoWidget(gtk.DrawingArea): + def __init__(self): + gtk.DrawingArea.__init__(self) + self.imagesink = None + self.unset_flags(gtk.DOUBLE_BUFFERED) + + def do_expose_event(self, event): + if self.imagesink: + self.imagesink.expose() + return False + else: + return True + + def set_sink(self, sink): + assert self.window.xid + self.imagesink = sink + self.imagesink.set_xwindow_id(self.window.xid) + +class SyncPoints(gtk.VBox): + def __init__(self, window): + gtk.VBox.__init__(self) + self.pwindow = window + self.create_ui() + + def create_ui(self): + self.model = model = gtk.ListStore(gobject.TYPE_UINT64, + gobject.TYPE_UINT64) + self.view = view = gtk.TreeView(self.model) + + def time_to_text(column, cell, method, iter, i): + value = model.get_value(iter, i) + ret = '' + for div, sep, mod, pad in ((gst.SECOND*60, '', 0, 0), + (gst.SECOND, ':', 60, 2), + (gst.MSECOND, '.', 1000, 3)): + n = value // div + if mod: + n %= mod + ret += sep + ('%%0%dd' % pad) % n + cell.set_property('text', ret) + + renderer = gtk.CellRendererText() + column = gtk.TreeViewColumn("Audio time", renderer) + column.set_cell_data_func(renderer, time_to_text, 0) + column.set_expand(True) + column.set_clickable(True) + view.append_column(column) + + renderer = gtk.CellRendererText() + column = gtk.TreeViewColumn("Video time", renderer) + column.set_cell_data_func(renderer, time_to_text, 1) + column.set_expand(True) + view.append_column(column) + + view.show() + self.pack_start(view, True, True, 6) + + hbox = gtk.HBox(False, 0) + hbox.show() + self.pack_start(hbox, False, False, 0) + + add = gtk.Button(stock=gtk.STOCK_ADD) + add.show() + def add_and_select(*x): + iter = model.append() + self.view.get_selection().select_iter(iter) + add.connect("clicked", add_and_select) + hbox.pack_end(add, False, False, 0) + + remove = gtk.Button(stock=gtk.STOCK_REMOVE) + remove.show() + def remove_selected(*x): + model, iter = self.view.get_selection().get_selected() + model.remove(iter) + remove.connect("clicked", remove_selected) + hbox.pack_end(remove, False, False, 0) + + pad = gtk.Label(' ') + pad.show() + hbox.pack_end(pad) + + label = gtk.Label("Set: ") + label.show() + hbox.pack_start(label) + + a = gtk.Button("A_udio") + a.show() + a.connect("clicked", lambda *x: self.set_selected_audio_now()) + hbox.pack_start(a) + + l = gtk.Label(" / ") + l.show() + hbox.pack_start(l) + + v = gtk.Button("_Video") + v.show() + v.connect("clicked", lambda *x: self.set_selected_video_now()) + hbox.pack_start(v) + + def set_selected_audio(self, time): + sel = self.view.get_selection() + model, iter = sel.get_selected() + if iter: + model.set_value(iter, 0, time) + + def set_selected_video(self, time): + sel = self.view.get_selection() + model, iter = sel.get_selected() + if iter: + model.set_value(iter, 1, time) + + def set_selected_audio_now(self): + # pause and preroll first + if self.pwindow.player.is_playing(): + self.pwindow.play_toggled() + self.pwindow.player.get_state(timeout=gst.MSECOND * 200) + + time, dur = self.pwindow.player.query_position() + self.set_selected_audio(time) + + def set_selected_video_now(self): + # pause and preroll first + if self.pwindow.player.is_playing(): + self.pwindow.play_toggled() + self.pwindow.player.get_state(timeout=gst.MSECOND * 200) + + time, dur = self.pwindow.player.query_position() + self.set_selected_video(time) + + def seek_and_pause(self, time): + if self.pwindow.player.is_playing(): + self.pwindow.play_toggled() + self.pwindow.player.seek(time) + if self.pwindow.player.is_playing(): + self.pwindow.play_toggled() + self.pwindow.player.get_state(timeout=gst.MSECOND * 200) + +class ProgressDialog(gtk.Dialog): + def __init__(self, title, description, task, parent, flags, buttons): + gtk.Dialog.__init__(self, title, parent, flags, buttons) + self._create_ui(title, description, task) + + def _create_ui(self, title, description, task): + self.set_border_width(6) + self.set_resizable(False) + self.set_has_separator(False) + + vbox = gtk.VBox() + vbox.set_border_width(6) + vbox.show() + self.vbox.pack_start(vbox, False) + + label = gtk.Label('%s' % title) + label.set_use_markup(True) + label.set_alignment(0.0, 0.0) + label.show() + vbox.pack_start(label, False) + + label = gtk.Label(description) + label.set_use_markup(True) + label.set_alignment(0.0, 0.0) + label.set_line_wrap(True) + label.set_padding(0, 12) + label.show() + vbox.pack_start(label, False) + + self.progress = progress = gtk.ProgressBar() + progress.show() + vbox.pack_start(progress, False) + + self.progresstext = label = gtk.Label('') + label.set_line_wrap(True) + label.set_use_markup(True) + label.set_alignment(0.0, 0.0) + label.show() + vbox.pack_start(label) + self.set_task(task) + + def set_task(self, task): + self.progresstext.set_markup('%s' % task) + +UNKNOWN = 0 +SUCCESS = 1 +FAILURE = 2 +CANCELLED = 3 + +class RemuxProgressDialog(ProgressDialog): + def __init__(self, parent, start, stop, fromname, toname): + ProgressDialog.__init__(self, + "Writing to disk", + ('Writing the selected segment of %s ' + 'to %s. This may take some time.' + % (fromname, toname)), + 'Starting media pipeline', + parent, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_CANCEL, CANCELLED, + gtk.STOCK_CLOSE, SUCCESS)) + self.start = start + self.stop = stop + self.update_position(start) + self.set_completed(False) + + def update_position(self, pos): + pos = min(max(pos, self.start), self.stop) + remaining = self.stop - pos + minutes = remaining // (gst.SECOND * 60) + seconds = (remaining - minutes * gst.SECOND * 60) // gst.SECOND + self.progress.set_text('%d:%02d of video remaining' % (minutes, seconds)) + self.progress.set_fraction(1.0 - float(remaining) / (self.stop - self.start)) + + def set_completed(self, completed): + self.set_response_sensitive(CANCELLED, not completed) + self.set_response_sensitive(SUCCESS, completed) + +def set_connection_blocked_async_marshalled(pads, proc, *args, **kwargs): + def clear_list(l): + while l: + l.pop() + + to_block = list(pads) + to_relink = [(x, x.get_peer()) for x in pads] + + def on_pad_blocked_sync(pad, is_blocked): + if pad not in to_block: + # can happen after the seek and before unblocking -- racy, + # but no prob, bob. + return + to_block.remove(pad) + if not to_block: + # marshal to main thread + gobject.idle_add(on_pads_blocked) + + def on_pads_blocked(): + for src, sink in to_relink: + src.link(sink) + proc(*args, **kwargs) + for src, sink in to_relink: + src.set_blocked_async(False, lambda *x: None) + clear_list(to_relink) + + for src, sink in to_relink: + src.unlink(sink) + src.set_blocked_async(True, on_pad_blocked_sync) + +class Remuxer(gst.Pipeline): + + __gsignals__ = {'done': (gobject.SIGNAL_RUN_LAST, None, (int,))} + + def __init__(self, fromuri, touri, start, stop): + # HACK: should do Pipeline.__init__, but that doesn't do what we + # want; there's a bug open aboooot that + self.__gobject_init__() + + assert start >= 0 + assert stop > start + + self.fromuri = fromuri + self.touri = None + self.start_time = start + self.stop_time = stop + + self.src = self.remuxbin = self.sink = None + self.resolution = UNKNOWN + + self.window = None + self.pdialog = None + + self._query_id = -1 + + def do_setup_pipeline(self): + self.src = gst.element_make_from_uri(gst.URI_SRC, self.fromuri) + self.remuxbin = RemuxBin(self.start_time, self.stop_time) + self.sink = gst.element_make_from_uri(gst.URI_SINK, self.touri) + self.resolution = UNKNOWN + + if gobject.signal_lookup('allow-overwrite', self.sink.__class__): + self.sink.connect('allow-overwrite', lambda *x: True) + + self.add(self.src, self.remuxbin, self.sink) + + self.src.link(self.remuxbin) + self.remuxbin.link(self.sink) + + def do_get_touri(self): + chooser = gtk.FileChooserDialog('Save as...', + self.window, + action=gtk.FILE_CHOOSER_ACTION_SAVE, + buttons=(gtk.STOCK_CANCEL, + CANCELLED, + gtk.STOCK_SAVE, + SUCCESS)) + chooser.set_uri(self.fromuri) # to select the folder + chooser.unselect_all() + chooser.set_do_overwrite_confirmation(True) + name = self.fromuri.split('/')[-1][:-4] + '-remuxed.ogg' + chooser.set_current_name(name) + resp = chooser.run() + uri = chooser.get_uri() + chooser.destroy() + + if resp == SUCCESS: + return uri + else: + return None + + def _start_queries(self): + def do_query(): + try: + # HACK: self.remuxbin.query() should do the same + # (requires implementing a vmethod, dunno how to do that + # although i think it's possible) + # HACK: why does self.query_position(..) not give useful + # answers? + pad = self.remuxbin.get_pad('src') + pos, duration = pad.query_position(gst.FORMAT_TIME) + if pos != gst.CLOCK_TIME_NONE: + self.pdialog.update_position(pos) + except: + # print 'query failed' + pass + return True + if self._query_id == -1: + self._query_id = gobject.timeout_add(100, # 10 Hz + do_query) + + def _stop_queries(self): + if self._query_id != -1: + gobject.source_remove(self._query_id) + self._query_id = -1 + + def _bus_watch(self, bus, message): + if message.type == gst.MESSAGE_ERROR: + print 'error', message + self._stop_queries() + m = gtk.MessageDialog(self.window, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + "Error processing file") + gerror, debug = message.parse_error() + txt = ('There was an error processing your file: %s\n\n' + 'Debug information:\n%s' % (gerror, debug)) + m.format_secondary_text(txt) + m.run() + m.destroy() + self.response(FAILURE) + elif message.type == gst.MESSAGE_WARNING: + print 'warning', message + elif message.type == gst.MESSAGE_EOS: + # print 'eos, woot', message.src + name = self.touri + if name.startswith('file://'): + name = name[7:] + self.pdialog.set_task('Finished writing %s' % name) + self.pdialog.update_position(self.stop_time) + self._stop_queries() + self.pdialog.set_completed(True) + elif message.type == gst.MESSAGE_STATE_CHANGED: + if message.src == self: + old, new, pending = message.parse_state_changed() + if ((old, new, pending) == + (gst.STATE_READY, gst.STATE_PAUSED, + gst.STATE_VOID_PENDING)): + self.pdialog.set_task('Processing file') + self.pdialog.update_position(self.start_time) + self._start_queries() + self.set_state(gst.STATE_PLAYING) + + def response(self, response): + assert self.resolution == UNKNOWN + self.resolution = response + self.set_state(gst.STATE_NULL) + self.pdialog.destroy() + self.pdialog = None + self.window.set_sensitive(True) + self.emit('done', response) + + def start(self, main_window): + self.window = main_window + self.touri = self.do_get_touri() + if not self.touri: + return False + self.do_setup_pipeline() + bus = self.get_bus() + bus.add_signal_watch() + bus.connect('message', self._bus_watch) + if self.window: + # can be None if we are debugging... + self.window.set_sensitive(False) + fromname = self.fromuri.split('/')[-1] + toname = self.touri.split('/')[-1] + self.pdialog = RemuxProgressDialog(main_window, self.start_time, + self.stop_time, fromname, toname) + self.pdialog.show() + self.pdialog.connect('response', lambda w, r: self.response(r)) + + self.set_state(gst.STATE_PAUSED) + return True + + def run(self, main_window): + if self.start(main_window): + loop = gobject.MainLoop() + self.connect('done', lambda *x: gobject.idle_add(loop.quit)) + loop.run() + else: + self.resolution = CANCELLED + return self.resolution + +class RemuxBin(gst.Bin): + def __init__(self, start_time, stop_time): + self.__gobject_init__() + + self.parsefactories = self._find_parsers() + self.parsers = [] + + self.demux = gst.element_factory_make('oggdemux') + self.mux = gst.element_factory_make('oggmux') + + self.add(self.demux, self.mux) + + self.add_pad(gst.GhostPad('sink', self.demux.get_pad('sink'))) + self.add_pad(gst.GhostPad('src', self.mux.get_pad('src'))) + + self.demux.connect('pad-added', self._new_demuxed_pad) + self.demux.connect('no-more-pads', self._no_more_pads) + + self.start_time = start_time + self.stop_time = stop_time + + def _find_parsers(self): + registry = gst.registry_get_default() + ret = {} + for f in registry.get_feature_list(gst.ElementFactory): + if f.get_klass().find('Parser') >= 0: + for t in f.get_static_pad_templates(): + if t.direction == gst.PAD_SINK: + for s in t.get_caps(): + ret[s.get_name()] = f.get_name() + break + return ret + + def _new_demuxed_pad(self, element, pad): + format = pad.get_caps()[0].get_name() + + if format not in self.parsefactories: + self.async_error("Unsupported media type: %s", format) + return + + queue = gst.element_factory_make('queue', 'queue_' + format) + queue.set_property('max-size-buffers', 1000) + parser = gst.element_factory_make(self.parsefactories[format]) + self.add(queue) + self.add(parser) + queue.set_state(gst.STATE_PAUSED) + parser.set_state(gst.STATE_PAUSED) + pad.link(queue.get_compatible_pad(pad)) + queue.link(parser) + parser.link(self.mux) + self.parsers.append(parser) + + def _do_seek(self): + flags = gst.SEEK_FLAG_FLUSH + # HACK: self.seek should work, should try that at some point + return self.demux.seek(1.0, gst.FORMAT_TIME, flags, + gst.SEEK_TYPE_SET, self.start_time, + gst.SEEK_TYPE_SET, self.stop_time) + + def _no_more_pads(self, element): + pads = [x.get_pad('src') for x in self.parsers] + set_connection_blocked_async_marshalled(pads, + self._do_seek) + + +class PlayerWindow(gtk.Window): + UPDATE_INTERVAL = 500 + def __init__(self): + gtk.Window.__init__(self) + self.set_default_size(600, 500) + + self.create_ui() + + self.player = GstPlayer(self.videowidget) + + def on_eos(): + self.player.seek(0L) + self.play_toggled() + self.player.on_eos = lambda *x: on_eos() + + self.update_id = -1 + self.changed_id = -1 + self.seek_timeout_id = -1 + + self.p_position = gst.CLOCK_TIME_NONE + self.p_duration = gst.CLOCK_TIME_NONE + + def on_delete_event(): + self.player.stop() + gtk.main_quit() + self.connect('delete-event', lambda *x: on_delete_event()) + + def load_file(self, location): + filename = location.split('/')[-1] + self.set_title('%s munger' % filename) + self.player.set_location(location) + if self.videowidget.flags() & gtk.REALIZED: + self.play_toggled() + else: + self.videowidget.connect_after('realize', + lambda *x: self.play_toggled()) + + def create_ui(self): + vbox = gtk.VBox() + vbox.show() + self.add(vbox) + + self.videowidget = VideoWidget() + self.videowidget.show() + vbox.pack_start(self.videowidget) + + hbox = gtk.HBox() + hbox.show() + vbox.pack_start(hbox, fill=False, expand=False) + + self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0) + hscale = gtk.HScale(self.adjustment) + hscale.set_digits(2) + hscale.set_update_policy(gtk.UPDATE_CONTINUOUS) + hscale.connect('button-press-event', self.scale_button_press_cb) + hscale.connect('button-release-event', self.scale_button_release_cb) + hscale.connect('format-value', self.scale_format_value_cb) + hbox.pack_start(hscale) + hscale.show() + self.hscale = hscale + + table = gtk.Table(3,3) + table.show() + vbox.pack_start(table, fill=False, expand=False, padding=6) + + self.button = button = gtk.Button(stock=gtk.STOCK_MEDIA_PLAY) + button.set_property('can-default', True) + button.set_focus_on_click(False) + button.show() + + # problem: play and paused are of different widths and cause the + # window to re-layout + # "solution": add more buttons to a vbox so that the horizontal + # width is enough + bvbox = gtk.VBox() + bvbox.add(button) + bvbox.add(gtk.Button(stock=gtk.STOCK_MEDIA_PLAY)) + bvbox.add(gtk.Button(stock=gtk.STOCK_MEDIA_PAUSE)) + sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + for kid in bvbox.get_children(): + sizegroup.add_widget(kid) + bvbox.show() + table.attach(bvbox, 0, 1, 1, 3, gtk.FILL, gtk.FILL) + + # can't set this property before the button has a window + button.set_property('has-default', True) + button.connect('clicked', lambda *args: self.play_toggled()) + + self.sync = sync = SyncPoints(self) + sync.show() + table.attach(sync, 1, 2, 0, 3, gtk.EXPAND, gtk.EXPAND|gtk.FILL, 12) + # nasty things to get sizes + l = gtk.Label('\n\n\n') + l.show() + table.attach(l, 0, 1, 0, 1, 0, 0, 0) + l = gtk.Label('\n\n\n') + l.show() + table.attach(l, 2, 3, 0, 1, 0, 0, 0) + + button = gtk.Button("_Open other movie...") + button.show() + button.connect('clicked', lambda *x: self.do_choose_file()) + table.attach(button, 2, 3, 1, 2, gtk.FILL, gtk.FILL) + + button = gtk.Button("_Write to disk") + button.set_property('image', + gtk.image_new_from_stock(gtk.STOCK_SAVE_AS, + gtk.ICON_SIZE_BUTTON)) + button.connect('clicked', lambda *x: self.do_remux()) + button.show() + table.attach(button, 2, 3, 2, 3, gtk.FILL, gtk.FILL) + + def do_remux(self): + if self.player.is_playing(): + self.play_toggled() + in_uri = self.player.get_location() + out_uri = in_uri[:-4] + '-remuxed.ogg' + raise NotImplementedError() + r = Remuxer(in_uri, out_uri, + self.cutin.get_time(), self.cutout.get_time()) + r.run(self) + + def do_choose_file(self): + if self.player.is_playing(): + self.play_toggled() + chooser = gtk.FileChooserDialog('Choose a movie to bork bork bork', + self, + buttons=(gtk.STOCK_CANCEL, + CANCELLED, + gtk.STOCK_OPEN, + SUCCESS)) + chooser.set_local_only(False) + chooser.set_select_multiple(False) + f = gtk.FileFilter() + f.set_name("All files") + f.add_pattern("*") + chooser.add_filter(f) + f = gtk.FileFilter() + f.set_name("Ogg files") + f.add_pattern("*.ogg") # as long as this is the only thing we + # support... + chooser.add_filter(f) + chooser.set_filter(f) + + prev = self.player.get_location() + if prev: + chooser.set_uri(prev) + + resp = chooser.run() + uri = chooser.get_uri() + chooser.destroy() + + if resp == SUCCESS: + self.load_file(uri) + return True + else: + return False + + def play_toggled(self): + if self.player.is_playing(): + self.player.pause() + self.button.set_label(gtk.STOCK_MEDIA_PLAY) + else: + self.player.play() + if self.update_id == -1: + self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL, + self.update_scale_cb) + self.button.set_label(gtk.STOCK_MEDIA_PAUSE) + + def scale_format_value_cb(self, scale, value): + if self.p_duration == -1: + real = 0 + else: + real = value * self.p_duration / 100 + + seconds = real / gst.SECOND + + return "%02d:%02d" % (seconds / 60, seconds % 60) + + def scale_button_press_cb(self, widget, event): + # see seek.c:start_seek + gst.debug('starting seek') + + self.button.set_sensitive(False) + self.was_playing = self.player.is_playing() + if self.was_playing: + self.player.pause() + + # don't timeout-update position during seek + if self.update_id != -1: + gobject.source_remove(self.update_id) + self.update_id = -1 + + # make sure we get changed notifies + if self.changed_id == -1: + self.changed_id = self.hscale.connect('value-changed', + self.scale_value_changed_cb) + + def scale_value_changed_cb(self, scale): + # see seek.c:seek_cb + real = long(scale.get_value() * self.p_duration / 100) # in ns + gst.debug('value changed, perform seek to %r' % real) + self.player.seek(real) + # allow for a preroll + self.player.get_state(timeout=50*gst.MSECOND) # 50 ms + + def scale_button_release_cb(self, widget, event): + # see seek.cstop_seek + widget.disconnect(self.changed_id) + self.changed_id = -1 + + self.button.set_sensitive(True) + if self.seek_timeout_id != -1: + gobject.source_remove(self.seek_timeout_id) + self.seek_timeout_id = -1 + else: + gst.debug('released slider, setting back to playing') + if self.was_playing: + self.player.play() + + if self.update_id != -1: + self.error('Had a previous update timeout id') + else: + self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL, + self.update_scale_cb) + + def update_scale_cb(self): + had_duration = self.p_duration != gst.CLOCK_TIME_NONE + self.p_position, self.p_duration = self.player.query_position() + if self.p_position != gst.CLOCK_TIME_NONE: + value = self.p_position * 100.0 / self.p_duration + self.adjustment.set_value(value) + return True + +def main(args): + def usage(): + sys.stderr.write("usage: %s [URI-OF-MEDIA-FILE]\n" % args[0]) + return 1 + + w = PlayerWindow() + w.show() + + if len(args) == 1: + if not w.do_choose_file(): + return 1 + elif len(args) == 2: + if not gst.uri_is_valid(args[1]): + sys.stderr.write("Error: Invalid URI: %s\n" % args[1]) + return 1 + w.load_file(args[1]) + else: + return usage() + + gtk.main() + +if __name__ == '__main__': + sys.exit(main(sys.argv)) -- 2.7.4