2
# -*- coding: utf-8 -*-
5
# Copyright 2008 Gordon Allott <gordallott@gmail.com>
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.
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.
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,
26
import gtk, gtk.glade, gobject
27
import resource, iconHandler, guiUploadConfig
28
from resource import extVideo, extAudio, mb_it
34
from gst.extend.discoverer import Discoverer
37
from os import path as ospath
38
from resource import slugify
39
from time import sleep
42
from random import random
44
class AudioDec(gst.Bin):
45
"""Decodes audio file, outputs at specified caps"""
47
def __init__(self, location, caps=None):
48
gst.Bin.__init__(self)
51
caps = gst.caps_from_string('audio/x-raw-float, rate=44100, channels=2, endianness=1234, width=32')
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')
61
# Set 'location' property on filesrc
62
src.set_property('location', location)
64
# Connect handler for 'new-decoded-pad' signal
65
dec.connect('new-decoded-pad', self.__on_new_decoded_pad)
68
self.add(src, dec, conv, rsmpl, ident)
70
# Link *some* elements
71
# This is completed in self.__on_new_decoded_pad()
74
rsmpl.link(ident, caps)
76
# Reference used in self.__on_new_decoded_pad()
77
self.__apad = conv.get_pad('sink')
80
self.add_pad(gst.GhostPad('src', ident.get_pad('src')))
83
def __on_new_decoded_pad(self, element, pad, last):
85
name = caps[0].get_name()
86
#print '\n__on_new_decoded_pad:', name
88
if not self.__apad.is_linked(): # Only link once
92
class VideoDefault(gst.Bin):
93
""" we use this as our default video source, if nothing else is playing,
99
gst.Bin.__init__(self)
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')
106
self.add(self._source, self._convert, self._identity)
107
self._source.link(self._convert)
108
self._convert.link(self._identity)
110
self.add_pad(gst.GhostPad('src', self._identity.get_pad('src')))
112
class VideoDec(gst.Bin):
113
"""Decodes audio file, outputs at specified caps"""
118
def __init__(self, location, caps=None, salt=0.0, callback=None):
119
gst.Bin.__init__(self)
122
self.callback = callback
125
#print "redoing caps.."
126
caps = gst.caps_from_string('video/x-raw-yuv, framerate=(fraction)25/1')
129
#print type(caps), caps
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')
139
# Set 'location' property on filesrc
140
self._source.set_property('location', location)
142
# Connect handler for 'new-decoded-pad' signal
143
self._decode.connect('new-decoded-pad', self.__on_new_decoded_pad)
145
# Add elements to bin
146
self.add(self._source, self._decode, self._convert, self._identity)
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)
155
# Reference used in self.__on_new_decoded_pad()
156
self.__apad = self._convert.get_pad('sink')
159
self.add_pad(gst.GhostPad('src', self._identity.get_pad('src')))
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
167
if not self.__apad.is_linked(): # Only link once
168
pad.link(self.__apad)
169
gobject.idle_add(self.callback, self, self.salt)
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)
178
gobject.type_register(VideoDec)
180
class Editor(gobject.GObject):
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, [])
194
def __init__(self, output='/tmp/out.ogg'):
195
gobject.GObject.__init__(self)
196
self._pipeline = gst.Pipeline("mypipeline")
201
#self._videoencoder = gstHelpers.VideoEncoder(videorate=False)
202
#self._audioencoder = gstHelpers.AudioEncoder()
204
self._mux = gst.element_factory_make('oggmux', 'mux')
205
#self._pipeline.add(self._mux)
207
self._progreport = gst.element_factory_make('progressreport')
209
self._filesink = gst.element_factory_make('filesink', 'filesink')
210
self._filesink.set_property('location', output)
212
#self._pipeline.add(#self._videoencoder,
213
##self._audioencoder,
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)
224
#self._videorate = gst.element_factory_make("videorate", "videoEncoder-videorate")
225
#self._videocaps = gst.caps_from_string('video/x-raw-yuv,framerate=25/1')
227
self._videoencoder = gstHelpers.VideoEncoder(framerate=25)
228
#self._videoencoder.set_property("caps", self._videocaps)
229
#self._videoencoder.set_state(gst.STATE_PAUSED)
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)
237
#gst.element_link_many(self._videoencoder,
240
#gst.element_link_many(self._audioencoder,
243
#gst.element_link_many(self._mux,
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)
255
audioConnected = False
256
videoConnected = False
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
263
#setup our default video source
264
self._defaultVideosource = VideoDefault()
267
media = gst.element_factory_make("gnlsource", 'defaultVideoSource')
268
media.add(self._defaultVideosource)
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)
284
addmedia_currentpos = 0 # we use this to track the 'start' of each media as its added
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
291
id = "%s[%f]" % (filepath, salt)
293
duration = end - start
295
print "addMedia", start, end, duration
297
#make our decoder for video first
298
vid_decoder = VideoDec(filepath, salt=salt, callback=self.onmediaReady)
301
# create a gnlfilesource
302
media = gst.element_factory_make("gnlsource", id)
303
media.add(vid_decoder)
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)
315
self._sources.append((media, salt, False))
316
self._comp.add(media)
318
self.addmedia_currentpos += duration
320
def areweready(self):
321
""" simple function that can be called to check various things to make
322
sure we are ready to start
324
print "|----| testing readyness"
325
for media, salt, ready in self._sources:
327
print "salt %f is not ready" % salt
331
if not self.hookedup:
332
print "self.hookedup is not true"
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()
344
def sync(self, element=None):
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
351
element = self._pipeline
353
state = element.get_state(timeout=3*gst.SECOND)[0]
354
if state == gst.STATE_CHANGE_SUCCESS:
360
""" called to emit the ready signal """
366
print "<---- play requested"
367
#self._comp.set_state(gst.STATE_PLAYING)
368
self._pipeline.set_state(gst.STATE_PLAYING)
371
print "<---- pause requested"
372
self._pipeline.set_state(gst.STATE_PAUSED)
375
print "<---- stop requested"
376
self.bus.remove_signal_watch()
377
self._pipeline.set_state(gst.STATE_NULL)
379
def get_position(self):
381
return self._pipeline.query_position(gst.FORMAT_TIME)[0]
382
except gst.QueryError:
385
def get_duration(self):
387
return self._pipeline.query_duration(gst.FORMAT_TIME)[0]
388
except gst.QueryError:
391
# -------------------------------------------------------------------
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]
405
raise Exception, 'onmediaReady called but salt was not found, salt was:', salt
408
def OnPad(self, comp, pad):
410
caps = pad.get_caps()
411
pad_type = caps.to_string()[0:5]
412
#print pad, caps.to_string()
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)
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)
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)
439
print "Unknown pad type detected, %s" %pad_type
441
#convpad = self.compconvert.get_compatible_pad(pad, pad.get_caps())
444
def no_more_pads(self, element):
445
print "done adding pads"
446
print "self.hookedup is true :)"
451
def _on_state_changed(self, bus, message):
452
state = message.parse_state_changed()
454
if (message.src == self._pipeline):
456
if state[1] == gst.STATE_PAUSED:
457
print "----> Stream is Paused "
459
elif state[1] == gst.STATE_PLAYING:
460
print "----> Stream is Playing "
465
second = "\t" + str(message.src)
466
if state[1] == gst.STATE_PAUSED:
467
first = u'◼'.encode('utf-8')
469
if state[1] == gst.STATE_PLAYING:
470
first = u'▶'.encode('utf-8')
472
if state[1] == gst.STATE_READY:
473
first = u'□'.encode('utf-8')
476
if state[1] == gst.STATE_NULL:
477
first = u'✗'.encode('utf-8')
482
#if (message.src == self._pipeline):
483
#state = message.parse_state_changed()
485
##if state[1] == gst.STATE_PAUSED:
486
##self.emit('paused')
487
##elif state[1] == gst.STATE_PLAYING:
488
##self.emit('playing')
490
def on_message(self, bus, message):
493
if type == gst.MESSAGE_EOS:
495
self._pipeline.set_state(gst.STATE_NULL)
497
self.emit('complete')
499
elif type == gst.MESSAGE_ERROR:
501
self._pipeline.set_state(gst.STATE_NULL)
502
err, debug = message.parse_error()
503
print "Error: %s" % err, debug
509
class view(gui.view):
510
""" gtk/glade object to provide the view """
516
'audioQuality': 75.0,
517
'videoQuality': 100.0,
518
'saveLocation': "~/Videos/foo.ogg",
525
windowName = 'mediaAction'
529
timerProgPulse = None
531
def __init__(self, config=None, wTree=None, callback=None):
536
self.connectSignals = {
537
"on_mAction_ok_clicked" : self.okayButton,
538
"on_mAction_cancel_clicked" : self.cancelButton,
541
gui.view.__init__(self, config=config, wTree=wTree, callback=callback)
543
#print "given input files: ", self.config['inputFiles']
545
if not self.config['inputFiles']:
548
self.editor = Editor(self.config['saveLocation'])
549
self.editor.connect('ready', self.on_ready)
551
gobject.idle_add(self.discover)
553
# start a nice pulse for the progress bar
554
self.timerProgPulse = gobject.timeout_add(250, self.callbackProgPulse)
557
def callbackProgPulse(self):
558
""" callback that just dings the progress bar in its active state """
559
self.wTree.get_widget('mAction_progress').pulse()
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
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')
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)
585
#self.start += discoverer.audiolength
586
#print "length: ", float(discoverer.audiolength) / gst.SECOND
587
self.editor.addMedia(infile, 0, float(discoverer.videolength) / gst.SECOND)
589
self.currentfile += 1
590
if self.currentfile < len(self.config['inputFiles']):
591
gobject.idle_add(self.discover)
594
#self.pipeline.set_state(gst.STATE_PLAYING)
600
def on_ready(self, editor):
605
#print "Playing...", editor.sync()