~gordallott/ubuntupeople/ubuntuuploader

« back to all changes in this revision

Viewing changes to src/guiMediaEditing.py

  • Committer: Gordon Allott
  • Date: 2008-07-18 14:39:19 UTC
  • Revision ID: gordallott@gmail.com-20080718143919-3l5i0cjblmt8a42b
reverted media-joining code - gstreamer theora bug prevents it working, may come back to it soon

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
# -*- coding: utf-8 -*-
3
 
#       guiUploader.py
4
 
#
5
 
#       Copyright 2008 Gordon Allott <gordallott@gmail.com>
6
 
#
7
 
#       This program is free software; you can redistribute it and/or modify
8
 
#       it under the terms of the GNU General Public License as published by
9
 
#       the Free Software Foundation; either version 3 of the License, or
10
 
#       (at your option) any later version.
11
 
#
12
 
#       This program is distributed in the hope that it will be useful,
13
 
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
#       GNU General Public License for more details.
16
 
#
17
 
#       You should have received a copy of the GNU General Public License
18
 
#       along with this program; if not, write to the Free Software
19
 
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
 
#       MA 02110-1301, USA.
21
 
#
22
 
#
23
 
import sys, os
24
 
import pygtk
25
 
pygtk.require('2.0')
26
 
import gtk, gtk.glade, gobject
27
 
import resource, iconHandler, guiUploadConfig
28
 
from resource import extVideo, extAudio, mb_it
29
 
import datetime
30
 
from math import *
31
 
import pygst
32
 
pygst.require("0.10")
33
 
import gst
34
 
from gst.extend.discoverer import Discoverer
35
 
 
36
 
import re
37
 
from os import path as ospath
38
 
from resource import slugify
39
 
from time import sleep
40
 
import gui
41
 
import gstHelpers
42
 
from random import random
43
 
 
44
 
class AudioDec(gst.Bin):
45
 
    """Decodes audio file, outputs at specified caps"""
46
 
 
47
 
    def __init__(self, location, caps=None):
48
 
        gst.Bin.__init__(self)
49
 
 
50
 
        if not caps:
51
 
            caps = gst.caps_from_string('audio/x-raw-float, rate=44100, channels=2, endianness=1234, width=32')
52
 
 
53
 
 
54
 
        # Create elements
55
 
        src = gst.element_factory_make('filesrc')
56
 
        dec = gst.element_factory_make('decodebin')
57
 
        conv = gst.element_factory_make('audioconvert')
58
 
        rsmpl = gst.element_factory_make('audioresample')
59
 
        ident = gst.element_factory_make('identity')
60
 
 
61
 
        # Set 'location' property on filesrc
62
 
        src.set_property('location', location)
63
 
 
64
 
        # Connect handler for 'new-decoded-pad' signal
65
 
        dec.connect('new-decoded-pad', self.__on_new_decoded_pad)
66
 
 
67
 
        # Add elements to bin
68
 
        self.add(src, dec, conv, rsmpl, ident)
69
 
 
70
 
        # Link *some* elements
71
 
        # This is completed in self.__on_new_decoded_pad()
72
 
        src.link(dec)
73
 
        conv.link(rsmpl)
74
 
        rsmpl.link(ident, caps)
75
 
 
76
 
        # Reference used in self.__on_new_decoded_pad()
77
 
        self.__apad = conv.get_pad('sink')
78
 
 
79
 
        # Add ghost pad
80
 
        self.add_pad(gst.GhostPad('src', ident.get_pad('src')))
81
 
 
82
 
 
83
 
    def __on_new_decoded_pad(self, element, pad, last):
84
 
        caps = pad.get_caps()
85
 
        name = caps[0].get_name()
86
 
        #print '\n__on_new_decoded_pad:', name
87
 
        if 'audio' in name:
88
 
            if not self.__apad.is_linked(): # Only link once
89
 
                pad.link(self.__apad)
90
 
 
91
 
 
92
 
class VideoDefault(gst.Bin):
93
 
    """ we use this as our default video source, if nothing else is playing,
94
 
    this will
95
 
    """
96
 
 
97
 
    def __init__(self):
98
 
 
99
 
        gst.Bin.__init__(self)
100
 
 
101
 
        self._source = gst.element_factory_make('videotestsrc')
102
 
        self._source.set_property('pattern', 'snow')
103
 
        self._convert = gst.element_factory_make('ffmpegcolorspace')
104
 
        self._identity = gst.element_factory_make('identity')
105
 
 
106
 
        self.add(self._source, self._convert, self._identity)
107
 
        self._source.link(self._convert)
108
 
        self._convert.link(self._identity)
109
 
 
110
 
        self.add_pad(gst.GhostPad('src', self._identity.get_pad('src')))
111
 
 
112
 
class VideoDec(gst.Bin):
113
 
    """Decodes audio file, outputs at specified caps"""
114
 
 
115
 
 
116
 
    salt = 1.23456
117
 
 
118
 
    def __init__(self, location, caps=None, salt=0.0, callback=None):
119
 
        gst.Bin.__init__(self)
120
 
 
121
 
        self.salt = salt
122
 
        self.callback = callback
123
 
 
124
 
        if not caps:
125
 
            #print "redoing caps.."
126
 
            caps = gst.caps_from_string('video/x-raw-yuv, framerate=(fraction)25/1')
127
 
            #print caps
128
 
 
129
 
        #print type(caps), caps
130
 
 
131
 
        # Create elements
132
 
        self._source = gst.element_factory_make('filesrc')
133
 
        self._decode = gst.element_factory_make('decodebin')
134
 
        self._convert = gst.element_factory_make('ffmpegcolorspace')
135
 
        #resize = gst.element_factory_make('videoscale')
136
 
        #rate = gst.element_factory_make('videorate')
137
 
        self._identity = gst.element_factory_make('identity')
138
 
 
139
 
        # Set 'location' property on filesrc
140
 
        self._source.set_property('location', location)
141
 
 
142
 
        # Connect handler for 'new-decoded-pad' signal
143
 
        self._decode.connect('new-decoded-pad', self.__on_new_decoded_pad)
144
 
 
145
 
        # Add elements to bin
146
 
        self.add(self._source, self._decode, self._convert, self._identity)
147
 
 
148
 
        # Link *some* elements
149
 
        # This is completed in self.__on_new_decoded_pad()
150
 
        self._source.link(self._decode)
151
 
        self._convert.link(self._identity)
152
 
        #resize.link(rate, caps)
153
 
        #rate.link(identity, caps)
154
 
 
155
 
        # Reference used in self.__on_new_decoded_pad()
156
 
        self.__apad = self._convert.get_pad('sink')
157
 
 
158
 
        # Add ghost pad
159
 
        self.add_pad(gst.GhostPad('src', self._identity.get_pad('src')))
160
 
 
161
 
 
162
 
    def __on_new_decoded_pad(self, element, pad, last):
163
 
        caps = pad.get_caps()
164
 
        name = caps[0].get_name()
165
 
        #print '\n__on_new_decoded_pad:', name
166
 
        if 'video' in name:
167
 
            if not self.__apad.is_linked(): # Only link once
168
 
                pad.link(self.__apad)
169
 
                gobject.idle_add(self.callback, self, self.salt)
170
 
 
171
 
    def set_state(self, state):
172
 
        """ replacement for the missing set_state included in normal elements """
173
 
        self._source.set_state(state)
174
 
        self._decode.set_state(state)
175
 
        self._convert.set_state(state)
176
 
        self._identity.set_state(state)
177
 
 
178
 
gobject.type_register(VideoDec)
179
 
 
180
 
class Editor(gobject.GObject):
181
 
    __gsignals__ = {
182
 
            'ready' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
183
 
            'complete' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
184
 
            'error' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
185
 
            'paused' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
186
 
            'playing' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
187
 
            'eos' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
188
 
        }
189
 
 
190
 
 
191
 
 
192
 
    hookedup = False
193
 
 
194
 
    def __init__(self, output='/tmp/out.ogg'):
195
 
        gobject.GObject.__init__(self)
196
 
        self._pipeline = gst.Pipeline("mypipeline")
197
 
 
198
 
 
199
 
 
200
 
 
201
 
        #self._videoencoder = gstHelpers.VideoEncoder(videorate=False)
202
 
        #self._audioencoder = gstHelpers.AudioEncoder()
203
 
 
204
 
        self._mux = gst.element_factory_make('oggmux', 'mux')
205
 
        #self._pipeline.add(self._mux)
206
 
 
207
 
        self._progreport = gst.element_factory_make('progressreport')
208
 
 
209
 
        self._filesink = gst.element_factory_make('filesink', 'filesink')
210
 
        self._filesink.set_property('location', output)
211
 
 
212
 
        #self._pipeline.add(#self._videoencoder,
213
 
                           ##self._audioencoder,
214
 
                           ##self._mux,
215
 
                           ##self._progreport,
216
 
                           #self._filesink)
217
 
 
218
 
 
219
 
        # creating a gnlcomposition
220
 
        self._comp = gst.element_factory_make("gnlcomposition", "mycomposition")
221
 
        self._comp.connect("pad-added", self.OnPad)
222
 
        self._comp.connect("no-more-pads", self.no_more_pads)
223
 
 
224
 
        #self._videorate = gst.element_factory_make("videorate", "videoEncoder-videorate")
225
 
        #self._videocaps = gst.caps_from_string('video/x-raw-yuv,framerate=25/1')
226
 
 
227
 
        self._videoencoder = gstHelpers.VideoEncoder(framerate=25)
228
 
        #self._videoencoder.set_property("caps", self._videocaps)
229
 
        #self._videoencoder.set_state(gst.STATE_PAUSED)
230
 
 
231
 
        self._pipeline.add(self._comp, self._mux, self._filesink, self._videoencoder)
232
 
        self._mux.link(self._filesink)
233
 
        #self._videorate.link(self._videoencoder)
234
 
        self._videoencoder.link(self._mux)
235
 
 
236
 
 
237
 
        #gst.element_link_many(self._videoencoder,
238
 
                              #self._mux)
239
 
 
240
 
        #gst.element_link_many(self._audioencoder,
241
 
                              #self._mux)
242
 
 
243
 
        #gst.element_link_many(self._mux,
244
 
                              ##self._progreport,
245
 
                              #self._filesink)
246
 
 
247
 
        # Connect up callbacks
248
 
        bus = self._pipeline.get_bus()
249
 
        bus.add_signal_watch()
250
 
        bus.connect("message", self.on_message)
251
 
        bus.connect("message::state-changed", self._on_state_changed)
252
 
 
253
 
        self._sources = []
254
 
 
255
 
    audioConnected = False
256
 
    videoConnected = False
257
 
 
258
 
 
259
 
    def defaultSources(self):
260
 
        """ called to setup the default sources, needs to have all the files
261
 
        added to our gnonlin thingy first though
262
 
        """
263
 
        #setup our default video source
264
 
        self._defaultVideosource = VideoDefault()
265
 
 
266
 
        # create a gnlsource
267
 
        media = gst.element_factory_make("gnlsource", 'defaultVideoSource')
268
 
        media.add(self._defaultVideosource)
269
 
 
270
 
        # set the gnlfilesource properties
271
 
        #media.set_property("location", filepath)
272
 
        media.set_property("start", 0)
273
 
        media.set_property("duration", self.addmedia_currentpos)
274
 
        media.set_property("priority", 9064)
275
 
        #media.set_property("media-start", 0)
276
 
        #media.set_property("media-duration", self.addmedia_currentpos)
277
 
        media.set_state(gst.STATE_PAUSED)
278
 
        self._comp.add(media)
279
 
 
280
 
        self.sync(media)
281
 
 
282
 
        #pass
283
 
 
284
 
    addmedia_currentpos = 0      # we use this to track the 'start' of each media as its added
285
 
 
286
 
    def addMedia(self, filepath, start, end):
287
 
        start = int(start * gst.SECOND)
288
 
        end = int(end * gst.SECOND)
289
 
        salt = random() #salt is used to identify this media again later
290
 
 
291
 
        id = "%s[%f]" % (filepath, salt)
292
 
 
293
 
        duration = end - start
294
 
 
295
 
        print "addMedia", start, end, duration
296
 
 
297
 
        #make our decoder for video first
298
 
        vid_decoder = VideoDec(filepath, salt=salt, callback=self.onmediaReady)
299
 
 
300
 
 
301
 
        # create a gnlfilesource
302
 
        media = gst.element_factory_make("gnlsource", id)
303
 
        media.add(vid_decoder)
304
 
 
305
 
        # set the gnlfilesource properties
306
 
        #media.set_property("location", filepath)
307
 
        media.set_property('start', self.addmedia_currentpos)
308
 
        media.set_property('duration', duration)
309
 
        media.set_property('media-start', start)
310
 
        media.set_property('media-duration', duration)
311
 
        #media.set_property('priority', 1)
312
 
        media.set_state(gst.STATE_PAUSED)
313
 
 
314
 
 
315
 
        self._sources.append((media, salt, False))
316
 
        self._comp.add(media)
317
 
 
318
 
        self.addmedia_currentpos += duration
319
 
 
320
 
    def areweready(self):
321
 
        """ simple function that can be called to check various things to make
322
 
        sure we are ready to start
323
 
        """
324
 
        print "|----| testing readyness"
325
 
        for media, salt, ready in self._sources:
326
 
            if not ready:
327
 
                print "salt %f is not ready" % salt
328
 
                print self._sources
329
 
                return False
330
 
 
331
 
        if not self.hookedup:
332
 
            print "self.hookedup is not true"
333
 
            #self.pause()
334
 
            return False
335
 
 
336
 
        print "|----| We are ready here apprently\n"
337
 
        #if we get here we are ready
338
 
        self._comp.set_property("duration", self.addmedia_currentpos)
339
 
        self.defaultSources()
340
 
        self.emit('ready')
341
 
        return True
342
 
 
343
 
 
344
 
    def sync(self, element=None):
345
 
        """
346
 
        Wait for the pipeline to change state
347
 
        We give it a 3 second timeout: returns True if the state change was
348
 
        successful in this time, False otherwise
349
 
        """
350
 
        if not element:
351
 
            element = self._pipeline
352
 
 
353
 
        state = element.get_state(timeout=3*gst.SECOND)[0]
354
 
        if state == gst.STATE_CHANGE_SUCCESS:
355
 
            return True
356
 
        else:
357
 
            return False
358
 
 
359
 
    def ready(self):
360
 
        """ called to emit the ready signal """
361
 
        self.sync()
362
 
        self.emit('ready')
363
 
        return False
364
 
 
365
 
    def play(self):
366
 
        print "<---- play requested"
367
 
        #self._comp.set_state(gst.STATE_PLAYING)
368
 
        self._pipeline.set_state(gst.STATE_PLAYING)
369
 
 
370
 
    def pause(self):
371
 
        print "<---- pause requested"
372
 
        self._pipeline.set_state(gst.STATE_PAUSED)
373
 
 
374
 
    def stop(self):
375
 
        print "<---- stop requested"
376
 
        self.bus.remove_signal_watch()
377
 
        self._pipeline.set_state(gst.STATE_NULL)
378
 
 
379
 
    def get_position(self):
380
 
        try:
381
 
            return self._pipeline.query_position(gst.FORMAT_TIME)[0]
382
 
        except gst.QueryError:
383
 
            return None
384
 
 
385
 
    def get_duration(self):
386
 
        try:
387
 
            return self._pipeline.query_duration(gst.FORMAT_TIME)[0]
388
 
        except gst.QueryError:
389
 
            return None
390
 
 
391
 
    # -------------------------------------------------------------------
392
 
 
393
 
    def onmediaReady(self, element, mediaSalt):
394
 
        """ handles the ready signals from our media """
395
 
        print "media %f is ready!" % mediaSalt, element
396
 
        for index in range(len(self._sources)):
397
 
            media, salt, ready = self._sources[index]
398
 
            if mediaSalt == salt:
399
 
                print "source test; ", self._sources[index]
400
 
                self._sources[index] = (media, salt, True)
401
 
                print "source test; ", self._sources[index]
402
 
                self.areweready()
403
 
                return
404
 
 
405
 
        raise Exception, 'onmediaReady called but salt was not found, salt was:', salt
406
 
 
407
 
 
408
 
    def OnPad(self, comp, pad):
409
 
        #print "pad added!"
410
 
        caps = pad.get_caps()
411
 
        pad_type = caps.to_string()[0:5]
412
 
        #print pad, caps.to_string()
413
 
 
414
 
        print pad_type
415
 
 
416
 
        if pad_type == "video":
417
 
            #self._videoencoder = gstHelpers.VideoEncoder(videorate=False)
418
 
            #self._pipeline.add(self._videoencoder)
419
 
            #self._videoencoder.set_state(gst.STATE_PAUSED)
420
 
            #self._videoencoder.set_property("caps", self._videocaps)
421
 
 
422
 
            pad.link(self._videoencoder.get_compatible_pad(pad, pad.get_caps()))#get_pad("sink"))
423
 
            #pad.link(self._videorate.get_pad('sink'))
424
 
            #self._videorate.link(self._videoencoder)
425
 
            #self._pipeline.add(self._mux, self._filesink)
426
 
            #self._mux.link(self._filesink)
427
 
            #self._videoencoder.link(self._mux)
428
 
            #self.ready()
429
 
            pass
430
 
 
431
 
        elif pad_type == "audio":
432
 
            self._audioencoder = gstHelpers.AudioEncoder()
433
 
            self._audioencoder.set_state(gst.STATE_PAUSED)
434
 
            pad.link(self._audioencoder.get_pad("sink"))
435
 
            self._audioencoder.link(self._mux)
436
 
            #self.ready()
437
 
 
438
 
        else:
439
 
            print "Unknown pad type detected, %s" %pad_type
440
 
 
441
 
        #convpad = self.compconvert.get_compatible_pad(pad, pad.get_caps())
442
 
        #pad.link(convpad)
443
 
 
444
 
    def no_more_pads(self, element):
445
 
        print "done adding pads"
446
 
        print "self.hookedup is true :)"
447
 
        self.hookedup = True
448
 
        self.areweready()
449
 
        #self.emit('ready')
450
 
 
451
 
    def _on_state_changed(self, bus, message):
452
 
        state = message.parse_state_changed()
453
 
 
454
 
        if  (message.src == self._pipeline):
455
 
 
456
 
            if state[1] == gst.STATE_PAUSED:
457
 
                print "----> Stream is Paused "
458
 
                self.emit('paused')
459
 
            elif state[1] == gst.STATE_PLAYING:
460
 
                print "----> Stream is Playing "
461
 
                self.emit('playing')
462
 
 
463
 
        else:
464
 
            first = "-" #✗□◼▶
465
 
            second = "\t" + str(message.src)
466
 
            if state[1] == gst.STATE_PAUSED:
467
 
                first = u'◼'.encode('utf-8')
468
 
 
469
 
            if state[1] == gst.STATE_PLAYING:
470
 
                first = u'▶'.encode('utf-8')
471
 
 
472
 
            if state[1] == gst.STATE_READY:
473
 
                first = u'□'.encode('utf-8')
474
 
 
475
 
 
476
 
            if state[1] == gst.STATE_NULL:
477
 
                first = u'✗'.encode('utf-8')
478
 
 
479
 
            print first + second
480
 
 
481
 
 
482
 
        #if  (message.src == self._pipeline):
483
 
            #state = message.parse_state_changed()
484
 
 
485
 
            ##if state[1] == gst.STATE_PAUSED:
486
 
                ##self.emit('paused')
487
 
            ##elif state[1] == gst.STATE_PLAYING:
488
 
                ##self.emit('playing')
489
 
 
490
 
    def on_message(self, bus, message):
491
 
 
492
 
        type = message.type
493
 
        if type == gst.MESSAGE_EOS:
494
 
            print "eos"
495
 
            self._pipeline.set_state(gst.STATE_NULL)
496
 
            self.sync()
497
 
            self.emit('complete')
498
 
 
499
 
        elif type == gst.MESSAGE_ERROR:
500
 
            print "error"
501
 
            self._pipeline.set_state(gst.STATE_NULL)
502
 
            err, debug = message.parse_error()
503
 
            print "Error: %s" % err, debug
504
 
            self.emit('error')
505
 
 
506
 
 
507
 
 
508
 
 
509
 
class view(gui.view):
510
 
    """ gtk/glade object to provide the view """
511
 
 
512
 
    config = {
513
 
        'title': "",
514
 
        'description': "",
515
 
        'fps': 15,
516
 
        'audioQuality': 75.0,
517
 
        'videoQuality': 100.0,
518
 
        'saveLocation': "~/Videos/foo.ogg",
519
 
        'inputFiles': []
520
 
    }
521
 
 
522
 
    inputfiles = []
523
 
    currentfile = 0
524
 
 
525
 
    windowName = 'mediaAction'
526
 
 
527
 
    editor = None
528
 
 
529
 
    timerProgPulse = None
530
 
 
531
 
    def __init__(self, config=None, wTree=None, callback=None):
532
 
 
533
 
        if not config:
534
 
            config = self.config
535
 
 
536
 
        self.connectSignals = {
537
 
            "on_mAction_ok_clicked" : self.okayButton,
538
 
            "on_mAction_cancel_clicked" : self.cancelButton,
539
 
        }
540
 
 
541
 
        gui.view.__init__(self, config=config, wTree=wTree, callback=callback)
542
 
 
543
 
        #print "given input files: ", self.config['inputFiles']
544
 
 
545
 
        if not self.config['inputFiles']:
546
 
            self.destroy()
547
 
 
548
 
        self.editor = Editor(self.config['saveLocation'])
549
 
        self.editor.connect('ready', self.on_ready)
550
 
 
551
 
        gobject.idle_add(self.discover)
552
 
 
553
 
        # start a nice pulse for the progress bar
554
 
        self.timerProgPulse = gobject.timeout_add(250, self.callbackProgPulse)
555
 
 
556
 
 
557
 
    def callbackProgPulse(self):
558
 
        """ callback that just dings the progress bar in its active state """
559
 
        self.wTree.get_widget('mAction_progress').pulse()
560
 
        return True
561
 
 
562
 
 
563
 
 
564
 
    def discover(self):
565
 
        infile = self.config['inputFiles'][self.currentfile]
566
 
        #print "trying to descover: %s" , infile
567
 
        discoverer = Discoverer(infile)
568
 
        discoverer.connect('discovered', self.on_discovered, infile)
569
 
        discoverer.discover()
570
 
        return False # Don't repeat idle call
571
 
 
572
 
 
573
 
    def on_discovered(self, discoverer, ismedia, infile):
574
 
        #print '\non_discovered:', infile
575
 
        discoverer.print_info()
576
 
        #if discoverer.is_audio:
577
 
            #dec = AudioDec(infile, self.caps)
578
 
            #src = gst.element_factory_make('gnlsource')
579
 
            #src.add(dec)
580
 
            #src.set_property('media-start', 0L)
581
 
            #src.set_property('media-duration', discoverer.audiolength)
582
 
            #src.set_property('start', self.start)
583
 
            #src.set_property('duration', discoverer.audiolength)
584
 
            #self.comp.add(src)
585
 
            #self.start += discoverer.audiolength
586
 
        #print "length: ", float(discoverer.audiolength) / gst.SECOND
587
 
        self.editor.addMedia(infile, 0, float(discoverer.videolength) / gst.SECOND)
588
 
 
589
 
        self.currentfile += 1
590
 
        if self.currentfile < len(self.config['inputFiles']):
591
 
            gobject.idle_add(self.discover)
592
 
            print "continue..."
593
 
        else:
594
 
            #self.pipeline.set_state(gst.STATE_PLAYING)
595
 
            #print "start!"
596
 
            self.editor.pause()
597
 
            self.editor.sync()
598
 
            pass
599
 
 
600
 
    def on_ready(self, editor):
601
 
        print "On_ready"
602
 
        #editor.sync()
603
 
        editor.play()
604
 
 
605
 
        #print "Playing...", editor.sync()
606
 
        return True