~ubuntu-branches/ubuntu/precise/gst0.10-python/precise

« back to all changes in this revision

Viewing changes to examples/remuxer.py

  • Committer: Bazaar Package Importer
  • Author(s): Loic Minier
  • Date: 2006-06-25 19:37:45 UTC
  • Revision ID: james.westby@ubuntu.com-20060625193745-9yeg0wq56r24n57x
Tags: upstream-0.10.4
ImportĀ upstreamĀ versionĀ 0.10.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
        self.on_eos = False
 
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
            self.videowidget.set_sink(message.src)
 
37
            message.src.set_property('force-aspect-ratio', True)
 
38
            
 
39
    def on_message(self, bus, message):
 
40
        t = message.type
 
41
        if t == gst.MESSAGE_ERROR:
 
42
            err, debug = message.parse_error()
 
43
            print "Error: %s" % err, debug
 
44
            if self.on_eos:
 
45
                self.on_eos()
 
46
            self.playing = False
 
47
        elif t == gst.MESSAGE_EOS:
 
48
            if self.on_eos:
 
49
                self.on_eos()
 
50
            self.playing = False
 
51
 
 
52
    def set_location(self, location):
 
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
 
 
191
    def get_time(self):
 
192
        time = 0
 
193
        for w, multiplier in ((self.minutes, gst.SECOND*60),
 
194
                              (self.seconds, gst.SECOND),
 
195
                              (self.milliseconds, gst.MSECOND)):
 
196
            text = w.get_text()
 
197
            try:
 
198
                val = int(text)
 
199
            except ValueError:
 
200
                val = 0
 
201
            w.set_text(val and str(val) or '')
 
202
            time += val * multiplier
 
203
        return time
 
204
 
 
205
    def set_time(self, time):
 
206
        if time == gst.CLOCK_TIME_NONE:
 
207
            print "Can't set '%s' (invalid time)" % self.label
 
208
            return
 
209
        self.freeze_notify()
 
210
        for w, multiplier in ((self.minutes, gst.SECOND*60),
 
211
                              (self.seconds, gst.SECOND),
 
212
                              (self.milliseconds, gst.MSECOND)):
 
213
            val = time // multiplier
 
214
            w.set_text(str(val))
 
215
            time -= val * multiplier
 
216
        self.thaw_notify()
 
217
 
 
218
    def set_now(self):
 
219
        time, dur = self.pwindow.player.query_position()
 
220
        self.set_time(time)
 
221
 
 
222
    def activated(self):
 
223
        time = self.get_time()
 
224
        if self.pwindow.player.is_playing():
 
225
            self.pwindow.play_toggled()
 
226
        self.pwindow.player.seek(time)
 
227
        self.pwindow.player.get_state(timeout=gst.MSECOND * 200)
 
228
 
 
229
class ProgressDialog(gtk.Dialog):
 
230
    def __init__(self, title, description, task, parent, flags, buttons):
 
231
        gtk.Dialog.__init__(self, title, parent, flags, buttons)
 
232
        self._create_ui(title, description, task)
 
233
 
 
234
    def _create_ui(self, title, description, task):
 
235
        self.set_border_width(6)
 
236
        self.set_resizable(False)
 
237
        self.set_has_separator(False)
 
238
 
 
239
        vbox = gtk.VBox()
 
240
        vbox.set_border_width(6)
 
241
        vbox.show()
 
242
        self.vbox.pack_start(vbox, False)
 
243
 
 
244
        label = gtk.Label('<big><b>%s</b></big>' % title)
 
245
        label.set_use_markup(True)
 
246
        label.set_alignment(0.0, 0.0)
 
247
        label.show()
 
248
        vbox.pack_start(label, False)
 
249
        
 
250
        label = gtk.Label(description)
 
251
        label.set_line_wrap(True)
 
252
        label.set_padding(0, 12)
 
253
        label.show()
 
254
        vbox.pack_start(label, False)
 
255
 
 
256
        self.progress = progress = gtk.ProgressBar()
 
257
        progress.show()
 
258
        vbox.pack_start(progress, False)
 
259
 
 
260
        self.progresstext = label = gtk.Label('')
 
261
        label.set_use_markup(True)
 
262
        label.set_alignment(0.0, 0.0)
 
263
        label.show()
 
264
        vbox.pack_start(label)
 
265
        self.set_task(task)
 
266
 
 
267
    def set_task(self, task):
 
268
        self.progresstext.set_markup('<i>%s</i>' % task)
 
269
 
 
270
UNKNOWN = 0
 
271
SUCCESS = 1
 
272
FAILURE = 2
 
273
CANCELLED = 3
 
274
 
 
275
class RemuxProgressDialog(ProgressDialog):
 
276
    def __init__(self, parent, start, stop):
 
277
        ProgressDialog.__init__(self,
 
278
                                "Writing to disk",
 
279
                                ('Writing the selected segment of your '
 
280
                                 'media file to disk. This may take some '
 
281
                                 'time depending on the file size.'),
 
282
                                'Starting media pipeline',
 
283
                                parent,
 
284
                                gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
 
285
                                (gtk.STOCK_CANCEL, CANCELLED,
 
286
                                 gtk.STOCK_CLOSE, SUCCESS))
 
287
        self.start = start
 
288
        self.stop = stop
 
289
        self.update_position(start)
 
290
        self.set_completed(False)
 
291
        
 
292
    def update_position(self, pos):
 
293
        print pos
 
294
        pos = min(max(pos, self.start), self.stop)
 
295
        remaining = self.stop - pos
 
296
        minutes = remaining // (gst.SECOND * 60)
 
297
        seconds = (remaining - minutes * gst.SECOND * 60) // gst.SECOND
 
298
        self.progress.set_text('%d:%02d of video remaining' % (minutes, seconds))
 
299
        self.progress.set_fraction(1.0 - float(remaining) / (self.stop - self.start))
 
300
 
 
301
    def set_completed(self, completed):
 
302
        self.set_response_sensitive(CANCELLED, not completed)
 
303
        self.set_response_sensitive(SUCCESS, completed)
 
304
 
 
305
def set_connection_blocked_async_marshalled(pads, proc, *args, **kwargs):
 
306
    def clear_list(l):
 
307
        while l:
 
308
            l.pop()
 
309
 
 
310
    to_block = list(pads)
 
311
    to_relink = [(x, x.get_peer()) for x in pads]
 
312
 
 
313
    def on_pad_blocked_sync(pad, is_blocked):
 
314
        if pad not in to_block:
 
315
            # can happen after the seek and before unblocking -- racy,
 
316
            # but no prob, bob.
 
317
            return
 
318
        to_block.remove(pad)
 
319
        if not to_block:
 
320
            # marshal to main thread
 
321
            gobject.idle_add(on_pads_blocked)
 
322
 
 
323
    def on_pads_blocked():
 
324
        for src, sink in to_relink:
 
325
            src.link(sink)
 
326
        proc(*args, **kwargs)
 
327
        for src, sink in to_relink:
 
328
            src.set_blocked_async(False, lambda *x: None)
 
329
        clear_list(to_relink)
 
330
 
 
331
    for src, sink in to_relink:
 
332
        src.unlink(sink)
 
333
        src.set_blocked_async(True, on_pad_blocked_sync)
 
334
 
 
335
class Remuxer(gst.Pipeline):
 
336
 
 
337
    __gsignals__ = {'done': (gobject.SIGNAL_RUN_LAST, None, (int,))}
 
338
 
 
339
    def __init__(self, fromuri, touri, start, stop):
 
340
        # HACK: should do Pipeline.__init__, but that doesn't do what we
 
341
        # want; there's a bug open aboooot that
 
342
        self.__gobject_init__()
 
343
 
 
344
        assert start >= 0
 
345
        assert stop > start
 
346
 
 
347
        self.src = gst.element_make_from_uri(gst.URI_SRC, fromuri)
 
348
        self.remuxbin = RemuxBin(start, stop)
 
349
        self.sink = gst.element_make_from_uri(gst.URI_SINK, touri)
 
350
        self.resolution = UNKNOWN
 
351
 
 
352
        if gobject.signal_lookup('allow-overwrite', self.sink.__class__):
 
353
            self.sink.connect('allow-overwrite', self._allow_overwrite)
 
354
 
 
355
        self.add(self.src, self.remuxbin, self.sink)
 
356
 
 
357
        self.src.link(self.remuxbin)
 
358
        self.remuxbin.link(self.sink)
 
359
 
 
360
        self.window = None
 
361
        self.pdialog = None
 
362
 
 
363
        self.start_time = start
 
364
        self.stop_time = stop
 
365
 
 
366
        self._query_id = -1
 
367
 
 
368
    def _allow_overwrite(self, sink, uri):
 
369
        name = self.sink.get_uri()
 
370
        name = (gst.uri_has_protocol(name, 'file')
 
371
                and gst.uri_get_location(name)
 
372
                or name)
 
373
        m = gtk.MessageDialog(self.window,
 
374
                              gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
 
375
                              gtk.MESSAGE_QUESTION,
 
376
                              gtk.BUTTONS_NONE,
 
377
                              ("The file %s already exists. Would you "
 
378
                               "like to replace it?") % name)
 
379
        b = gtk.Button(stock=gtk.STOCK_CANCEL)
 
380
        b.show()
 
381
        m.add_action_widget(b, CANCELLED)
 
382
        b = gtk.Button('Replace')
 
383
        b.show()
 
384
        m.add_action_widget(b, SUCCESS)
 
385
        txt = ('If you replace an existing file, its contents will be '
 
386
               'overwritten.')
 
387
        m.format_secondary_text(txt)
 
388
        resp = m.run()
 
389
        m.destroy()
 
390
        return resp == SUCCESS
 
391
 
 
392
    def _start_queries(self):
 
393
        def do_query():
 
394
            try:
 
395
                # HACK: self.remuxbin.query() should do the same
 
396
                # (requires implementing a vmethod, dunno how to do that
 
397
                # although i think it's possible)
 
398
                # HACK: why does self.query_position(..) not give useful
 
399
                # answers? 
 
400
                pad = self.remuxbin.get_pad('src')
 
401
                pos, duration = pad.query_position(gst.FORMAT_TIME)
 
402
                if pos != gst.CLOCK_TIME_NONE:
 
403
                    self.pdialog.update_position(pos)
 
404
            except gst.QueryError:
 
405
                print 'query failed'
 
406
                pass
 
407
            return True
 
408
        if self._query_id == -1:
 
409
            self._query_id = gobject.timeout_add(100, # 10 Hz
 
410
                                                 do_query)
 
411
 
 
412
    def _stop_queries(self):
 
413
        if self._query_id != -1:
 
414
            gobject.source_remove(self._query_id)
 
415
            self._query_id = -1
 
416
 
 
417
    def _bus_watch(self, bus, message):
 
418
        if message.type == gst.MESSAGE_ERROR:
 
419
            print 'error', message
 
420
            self._stop_queries()
 
421
            m = gtk.MessageDialog(self.window,
 
422
                                  gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
 
423
                                  gtk.MESSAGE_ERROR,
 
424
                                  gtk.BUTTONS_CLOSE,
 
425
                                  "Error processing file")
 
426
            gerror, debug = message.parse_error()
 
427
            txt = ('There was an error processing your file: %s\n\n'
 
428
                   'Debug information:\n%s' % (gerror, debug))
 
429
            m.format_secondary_text(txt)
 
430
            m.run()
 
431
            m.destroy()
 
432
            self.response(FAILURE)
 
433
        elif message.type == gst.MESSAGE_WARNING:
 
434
            print 'warning', message
 
435
        elif message.type == gst.MESSAGE_SEGMENT_DONE:
 
436
            print 'eos, woot', message.src
 
437
            self.pdialog.set_task('Finished')
 
438
            self.pdialog.update_position(self.stop_time)
 
439
            self._stop_queries()
 
440
            self.pdialog.set_completed(True)
 
441
        elif message.type == gst.MESSAGE_STATE_CHANGED:
 
442
            if message.src == self:
 
443
                print message
 
444
                old, new, pending = message.parse_state_changed()
 
445
                if ((old, new, pending) ==
 
446
                    (gst.STATE_READY, gst.STATE_PAUSED,
 
447
                     gst.STATE_VOID_PENDING)):
 
448
                    self.pdialog.set_task('Processing file')
 
449
                    self.pdialog.update_position(self.start_time)
 
450
                    self._start_queries()
 
451
                    self.set_state(gst.STATE_PLAYING)
 
452
 
 
453
    def response(self, response):
 
454
        assert self.resolution == UNKNOWN
 
455
        self.resolution = response
 
456
        self.set_state(gst.STATE_NULL)
 
457
        self.pdialog.destroy()
 
458
        self.pdialog = None
 
459
        self.window.set_sensitive(True)
 
460
        self.emit('done', response)
 
461
 
 
462
    def start(self, main_window):
 
463
        bus = self.get_bus()
 
464
        bus.add_signal_watch()
 
465
        bus.connect('message', self._bus_watch)
 
466
        self.window = main_window
 
467
        if self.window:
 
468
            # can be None if we are debugging...
 
469
            self.window.set_sensitive(False)
 
470
        self.pdialog = RemuxProgressDialog(main_window, self.start_time,
 
471
                                           self.stop_time)
 
472
        self.pdialog.show()
 
473
        self.pdialog.connect('response', lambda w, r: self.response(r))
 
474
 
 
475
        self.set_state(gst.STATE_PAUSED)
 
476
        
 
477
    def run(self, main_window):
 
478
        self.start(main_window)
 
479
        loop = gobject.MainLoop()
 
480
        self.connect('done', lambda *x: gobject.idle_add(loop.quit))
 
481
        loop.run()
 
482
        return self.resolution
 
483
        
 
484
class RemuxBin(gst.Bin):
 
485
    def __init__(self, start_time, stop_time):
 
486
        self.__gobject_init__()
 
487
 
 
488
        self.parsefactories = self._find_parsers()
 
489
        self.parsers = []
 
490
 
 
491
        self.demux = gst.element_factory_make('oggdemux')
 
492
        self.mux = gst.element_factory_make('oggmux')
 
493
 
 
494
        self.add(self.demux, self.mux)
 
495
 
 
496
        self.add_pad(gst.GhostPad('sink', self.demux.get_pad('sink')))
 
497
        self.add_pad(gst.GhostPad('src', self.mux.get_pad('src')))
 
498
 
 
499
        self.demux.connect('pad-added', self._new_demuxed_pad)
 
500
        self.demux.connect('no-more-pads', self._no_more_pads)
 
501
 
 
502
        self.start_time = start_time
 
503
        self.stop_time = stop_time
 
504
 
 
505
    def _find_parsers(self):
 
506
        registry = gst.registry_get_default()
 
507
        ret = {}
 
508
        for f in registry.get_feature_list(gst.ElementFactory):
 
509
            if f.get_klass().find('Parser') >= 0:
 
510
                for t in f.get_static_pad_templates():
 
511
                    if t.direction == gst.PAD_SINK:
 
512
                        for s in t.get_caps():
 
513
                            ret[s.get_name()] = f.get_name()
 
514
                        break
 
515
        return ret
 
516
 
 
517
    def _new_demuxed_pad(self, element, pad):
 
518
        format = pad.get_caps()[0].get_name()
 
519
 
 
520
        if format not in self.parsefactories:
 
521
            self.async_error("Unsupported media type: %s", format)
 
522
            return
 
523
 
 
524
        queue = gst.element_factory_make('queue', 'queue_' + format)
 
525
        parser = gst.element_factory_make(self.parsefactories[format])
 
526
        self.add(queue)
 
527
        self.add(parser)
 
528
        queue.set_state(gst.STATE_PAUSED)
 
529
        parser.set_state(gst.STATE_PAUSED)
 
530
        pad.link(queue.get_compatible_pad(pad))
 
531
        queue.link(parser)
 
532
        parser.link(self.mux)
 
533
        self.parsers.append(parser)
 
534
 
 
535
    def _do_segment_seek(self):
 
536
        flags = gst.SEEK_FLAG_SEGMENT | gst.SEEK_FLAG_FLUSH
 
537
        # HACK: self.seek should work, should try that at some point
 
538
        return self.demux.seek(1.0, gst.FORMAT_TIME, flags,
 
539
                               gst.SEEK_TYPE_SET, self.start_time,
 
540
                               gst.SEEK_TYPE_SET, self.stop_time)
 
541
 
 
542
    def _no_more_pads(self, element):
 
543
        pads = [x.get_pad('src') for x in self.parsers]
 
544
        set_connection_blocked_async_marshalled(pads,
 
545
                                                self._do_segment_seek)
 
546
 
 
547
 
 
548
class PlayerWindow(gtk.Window):
 
549
    UPDATE_INTERVAL = 500
 
550
    def __init__(self):
 
551
        gtk.Window.__init__(self)
 
552
        self.set_default_size(600, 425)
 
553
 
 
554
        self.create_ui()
 
555
 
 
556
        self.player = GstPlayer(self.videowidget)
 
557
 
 
558
        def on_eos():
 
559
            self.player.seek(0L)
 
560
            self.play_toggled()
 
561
        self.player.on_eos = lambda *x: on_eos()
 
562
        
 
563
        self.update_id = -1
 
564
        self.changed_id = -1
 
565
        self.seek_timeout_id = -1
 
566
 
 
567
        self.p_position = gst.CLOCK_TIME_NONE
 
568
        self.p_duration = gst.CLOCK_TIME_NONE
 
569
 
 
570
        def on_delete_event():
 
571
            self.player.stop()
 
572
            gtk.main_quit()
 
573
        self.connect('delete-event', lambda *x: on_delete_event())
 
574
 
 
575
    def load_file(self, location):
 
576
        self.player.set_location(location)
 
577
                                  
 
578
    def create_ui(self):
 
579
        vbox = gtk.VBox()
 
580
        vbox.show()
 
581
        self.add(vbox)
 
582
 
 
583
        self.videowidget = VideoWidget()
 
584
        self.videowidget.show()
 
585
        vbox.pack_start(self.videowidget)
 
586
        
 
587
        hbox = gtk.HBox()
 
588
        hbox.show()
 
589
        vbox.pack_start(hbox, fill=False, expand=False)
 
590
        
 
591
        self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0)
 
592
        hscale = gtk.HScale(self.adjustment)
 
593
        hscale.set_digits(2)
 
594
        hscale.set_update_policy(gtk.UPDATE_CONTINUOUS)
 
595
        hscale.connect('button-press-event', self.scale_button_press_cb)
 
596
        hscale.connect('button-release-event', self.scale_button_release_cb)
 
597
        hscale.connect('format-value', self.scale_format_value_cb)
 
598
        hbox.pack_start(hscale)
 
599
        hscale.show()
 
600
        self.hscale = hscale
 
601
 
 
602
        self.videowidget.connect_after('realize',
 
603
                                       lambda *x: self.play_toggled())
 
604
 
 
605
        table = gtk.Table(2,3)
 
606
        table.show()
 
607
        vbox.pack_start(table, fill=False, expand=False, padding=6)
 
608
 
 
609
        self.pause_image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PAUSE,
 
610
                                                    gtk.ICON_SIZE_LARGE_TOOLBAR)
 
611
        self.pause_image.show()
 
612
        self.play_image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PLAY,
 
613
                                                   gtk.ICON_SIZE_LARGE_TOOLBAR)
 
614
        self.play_image.show()
 
615
        self.button = button = gtk.Button()
 
616
        button.add(self.play_image)
 
617
        button.set_property('can-default', True)
 
618
        button.set_focus_on_click(False)
 
619
        button.show()
 
620
        aspect = gtk.AspectFrame(obey_child=False, xalign=0.0)
 
621
        aspect.set_property('shadow_type', gtk.SHADOW_NONE)
 
622
        aspect.show()
 
623
        aspect.add(button)
 
624
        table.attach(aspect, 0, 1, 0, 2, gtk.EXPAND|gtk.FILL, gtk.EXPAND|gtk.FILL)
 
625
        button.set_property('has-default', True)
 
626
        button.connect('clicked', lambda *args: self.play_toggled())
 
627
        
 
628
        self.cutin = cut = TimeControl(self, "Cut in time")
 
629
        cut.show()
 
630
        table.attach(cut, 1, 2, 0, 1, gtk.EXPAND, 0, 12)
 
631
 
 
632
        self.cutout = cut = TimeControl(self, "Cut out time")
 
633
        cut.show()
 
634
        table.attach(cut, 1, 2, 1, 2, gtk.EXPAND, 0, 12)
 
635
 
 
636
        buttonbox = gtk.HButtonBox()
 
637
        buttonbox.set_layout(gtk.BUTTONBOX_END)
 
638
        buttonbox.show()
 
639
        table.attach(buttonbox, 2, 3, 1, 2, 0, 0)
 
640
 
 
641
        button = gtk.Button("_Write to disk")
 
642
        button.set_property('image',
 
643
                            gtk.image_new_from_stock(gtk.STOCK_SAVE_AS,
 
644
                                                     gtk.ICON_SIZE_BUTTON))
 
645
        button.show()
 
646
        buttonbox.pack_start(button, False)
 
647
 
 
648
        self.cutin.connect('notify::time', lambda *x: self.check_cutout())
 
649
        self.cutout.connect('notify::time', lambda *x: self.check_cutin())
 
650
        button.connect('clicked', lambda *x: self.do_remux())
 
651
 
 
652
    def do_remux(self):
 
653
        if self.player.is_playing():
 
654
            self.play_toggled()
 
655
        in_uri = self.player.get_location()
 
656
        out_uri = in_uri[:-4] + '-remuxed.ogg'
 
657
        r = Remuxer(in_uri, out_uri,
 
658
                    self.cutin.get_time(), self.cutout.get_time())
 
659
        r.run(self)
 
660
 
 
661
    def check_cutout(self):
 
662
        if self.cutout.get_time() <= self.cutin.get_time():
 
663
            pos, dur = self.player.query_position()
 
664
            self.cutout.set_time(dur)
 
665
 
 
666
    def check_cutin(self):
 
667
        if self.cutin.get_time() >= self.cutout.get_time():
 
668
            self.cutin.set_time(0)
 
669
 
 
670
    def play_toggled(self):
 
671
        self.button.remove(self.button.child)
 
672
        if self.player.is_playing():
 
673
            self.player.pause()
 
674
            self.button.add(self.play_image)
 
675
        else:
 
676
            self.player.play()
 
677
            if self.update_id == -1:
 
678
                self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
 
679
                                                     self.update_scale_cb)
 
680
            self.button.add(self.pause_image)
 
681
 
 
682
    def scale_format_value_cb(self, scale, value):
 
683
        if self.p_duration == -1:
 
684
            real = 0
 
685
        else:
 
686
            real = value * self.p_duration / 100
 
687
        
 
688
        seconds = real / gst.SECOND
 
689
 
 
690
        return "%02d:%02d" % (seconds / 60, seconds % 60)
 
691
 
 
692
    def scale_button_press_cb(self, widget, event):
 
693
        # see seek.c:start_seek
 
694
        gst.debug('starting seek')
 
695
        
 
696
        self.button.set_sensitive(False)
 
697
        self.was_playing = self.player.is_playing()
 
698
        if self.was_playing:
 
699
            self.player.pause()
 
700
 
 
701
        # don't timeout-update position during seek
 
702
        if self.update_id != -1:
 
703
            gobject.source_remove(self.update_id)
 
704
            self.update_id = -1
 
705
 
 
706
        # make sure we get changed notifies
 
707
        if self.changed_id == -1:
 
708
            self.changed_id = self.hscale.connect('value-changed',
 
709
                self.scale_value_changed_cb)
 
710
            
 
711
    def scale_value_changed_cb(self, scale):
 
712
        # see seek.c:seek_cb
 
713
        real = long(scale.get_value() * self.p_duration / 100) # in ns
 
714
        gst.debug('value changed, perform seek to %r' % real)
 
715
        self.player.seek(real)
 
716
        # allow for a preroll
 
717
        self.player.get_state(timeout=50*gst.MSECOND) # 50 ms
 
718
 
 
719
    def scale_button_release_cb(self, widget, event):
 
720
        # see seek.cstop_seek
 
721
        widget.disconnect(self.changed_id)
 
722
        self.changed_id = -1
 
723
 
 
724
        self.button.set_sensitive(True)
 
725
        if self.seek_timeout_id != -1:
 
726
            gobject.source_remove(self.seek_timeout_id)
 
727
            self.seek_timeout_id = -1
 
728
        else:
 
729
            gst.debug('released slider, setting back to playing')
 
730
            if self.was_playing:
 
731
                self.player.play()
 
732
 
 
733
        if self.update_id != -1:
 
734
            self.error('Had a previous update timeout id')
 
735
        else:
 
736
            self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
 
737
                self.update_scale_cb)
 
738
 
 
739
    def update_scale_cb(self):
 
740
        self.p_position, self.p_duration = self.player.query_position()
 
741
        if self.p_position != gst.CLOCK_TIME_NONE:
 
742
            value = self.p_position * 100.0 / self.p_duration
 
743
            self.adjustment.set_value(value)
 
744
 
 
745
        return True
 
746
 
 
747
def main(args):
 
748
    def usage():
 
749
        sys.stderr.write("usage: %s URI-OF-MEDIA-FILE\n" % args[0])
 
750
        sys.exit(1)
 
751
 
 
752
    w = PlayerWindow()
 
753
 
 
754
    if len(args) != 2:
 
755
        usage()
 
756
 
 
757
    if not gst.uri_is_valid(args[1]):
 
758
        sys.stderr.write("Error: Invalid URI: %s\n" % args[1])
 
759
        sys.exit(1)
 
760
 
 
761
    w.load_file(args[1])
 
762
    w.show()
 
763
 
 
764
    gtk.main()
 
765
 
 
766
if __name__ == '__main__':
 
767
    sys.exit(main(sys.argv))