Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/glive.py
diff options
context:
space:
mode:
authorDaniel Drake <dsd@laptop.org>2010-04-21 17:30:36 (GMT)
committer Daniel Drake <dan@reactivated.net>2010-04-29 14:49:22 (GMT)
commitf6de5db915f077f4d90e68d5ca65b6c986080784 (patch)
tree0cf8946860bc12446eb6a862322c412ae2043f3c /glive.py
parent5abd5b671874f9db596aac97a5e47ec31a9a5bd8 (diff)
Restore v60 rework
Restore the v60 rewrite to use a single pipeline, tweaking at runtime when necessary. This improves responsivity of the application, and results in the activity behaving reliably again.
Diffstat (limited to 'glive.py')
-rw-r--r--glive.py828
1 files changed, 470 insertions, 358 deletions
diff --git a/glive.py b/glive.py
index a1a4610..2480fc8 100644
--- a/glive.py
+++ b/glive.py
@@ -18,47 +18,36 @@
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
-import re
+
import os
import gtk
import pygtk
pygtk.require('2.0')
+import sys
import gst
+import gst.interfaces
import pygst
pygst.require('0.10')
import time
+import threading
import gobject
gobject.threads_init()
-import logging
-logger = logging.getLogger('record:glive.py')
-
-from sugar.activity.activity import get_activity_root, get_bundle_path
+from sugar.activity.activity import get_bundle_path
from instance import Instance
from constants import Constants
+import record
import utils
import ui
-TMP_OGV = os.path.join(get_activity_root(), 'instance', 'output.ogv')
-TMP_OGG = os.path.join(get_activity_root(), 'instance', 'output.ogg')
-TMP_WAV = os.path.join(get_activity_root(), 'instance', 'output.wav')
-
-PLAYBACK_WIDTH = 640
-PLAYBACK_HEIGHT = 480
-
OGG_TRAITS = {
0: { 'width': 160, 'height': 120, 'quality': 16 },
1: { 'width': 400, 'height': 300, 'quality': 16 },
2: { 'width': 640, 'height': 480, 'quality': 32 } }
THUMB_STUB = gtk.gdk.pixbuf_new_from_file(
- os.path.join(get_bundle_path(), 'gfx', 'stub.png'))
-
-if os.path.isdir('/ofw'):
- AUDIO_FILTER = '! volume volume=10'
-else:
- AUDIO_FILTER = ''
+ os.path.join(get_bundle_path(), 'gfx', 'stub.png'))
def _does_camera_present():
v4l2src = gst.element_factory_make('v4l2src')
@@ -67,335 +56,497 @@ def _does_camera_present():
camera_presents = _does_camera_present()
class Glive:
- def play(self):
- if not camera_presents:
- return
+ def __init__(self, pca):
+ self.window = None
+ self.ca = pca
+ self._eos_cb = None
- logger.debug('play')
-
- if not self.play_pipe:
- self.src_str = \
- 'v4l2src ' \
- '! video/x-raw-yuv,width=%s,height=%s ' \
- % (PLAYBACK_WIDTH, PLAYBACK_HEIGHT)
- self.play_str = \
- 'xvimagesink force-aspect-ratio=true name=xsink'
-
- self.play_pipe = gst.parse_launch(
- '%s ' \
- '! valve name=valve ' \
- '! queue name=queue ' \
- '! %s' \
- % (self.src_str, self.play_str))
- self.valve = self.play_pipe.get_by_name('valve')
-
- def message_cb(bus, message):
- if message.type == gst.MESSAGE_ERROR:
- err, debug = message.parse_error()
- logger.error('play_pipe: %s %s' % (err, debug))
-
- if not self.fallback:
- logger.warning('use fallback_bin')
- self.fallback = True
-
- self.play_str = \
- 'ffmpegcolorspace ' \
- '! ximagesink force-aspect-ratio=true ' \
- ' name=xsink'
-
- self.play_pipe.remove(
- self.play_pipe.get_by_name('xsink'))
-
- c = gst.element_factory_make('ffmpegcolorspace')
- s = gst.element_factory_make('ximagesink', 'xsink')
- s.props.force_aspect_ratio = True
-
- self.play_pipe.add(c, s)
- gst.element_link_many(
- self.play_pipe.get_by_name('queue'), c, s)
-
- if [i for i in self.pipeline.get_state() \
- if id(i) == id(gst.STATE_PLAYING)]:
- self.pipeline = None
- self._switch_pipe(self.play_pipe)
-
- bus = self.play_pipe.get_bus()
- bus.add_signal_watch()
- bus.connect('message', message_cb)
-
- self._switch_pipe(self.play_pipe)
-
- def thumb_play(self, use_fallback=False):
- if not camera_presents:
- return
+ self.playing = False
+ self.picExposureOpen = False
- if not self.fallback and not use_fallback:
- # use xv to scale video
- self.play()
- return
+ self.AUDIO_TRANSCODE_ID = 0
+ self.TRANSCODE_ID = 0
+ self.VIDEO_TRANSCODE_ID = 0
- logger.debug('thumb_play')
+ self.PHOTO_MODE_PHOTO = 0
+ self.PHOTO_MODE_AUDIO = 1
- if not self.fallback_pipe:
- self.fallback_pipe = gst.parse_launch(
- '%s ' \
- '! queue ' \
- '! videoscale ' \
- '! video/x-raw-yuv,width=%s,height=%s ' \
- '! ffmpegcolorspace ' \
- '! ximagesink force-aspect-ratio=true name=xsink' \
- % (self.src_str, ui.UI.dim_PIPW, ui.UI.dim_PIPH))
+ self.TRANSCODE_UPDATE_INTERVAL = 200
- def message_cb(bus, message):
- if message.type == gst.MESSAGE_ERROR:
- err, debug = message.parse_error()
- logger.error('fallback_pipe: %s %s' % (err, debug))
- bus = self.fallback_pipe.get_bus()
- bus.add_signal_watch()
- bus.connect('message', message_cb)
+ self.VIDEO_WIDTH_SMALL = 160
+ self.VIDEO_HEIGHT_SMALL = 120
+ self.VIDEO_FRAMERATE_SMALL = 10
- self._switch_pipe(self.fallback_pipe)
+ self.VIDEO_WIDTH_LARGE = 200
+ self.VIDEO_HEIGHT_LARGE = 150
+ self.VIDEO_FRAMERATE_SMALL = 10
- def pause(self):
- logger.debug('pause')
- if self.pipeline:
- self.pipeline.set_state(gst.STATE_PAUSED)
+ self.pipeline = gst.Pipeline("my-pipeline")
+ self.createPhotoBin()
+ self.createAudioBin()
+ self.createVideoBin()
+ self.createPipeline()
- def stop(self):
- logger.debug('stop')
- if self.pipeline:
- self.pipeline.set_state(gst.STATE_NULL)
+ self.thumbPipes = []
+ self.muxPipes = []
- def takePhoto(self, after_photo_cb=None):
- if not camera_presents:
- return
+ bus = self.pipeline.get_bus()
+ bus.enable_sync_message_emission()
+ bus.add_signal_watch()
+ self.SYNC_ID = bus.connect('sync-message::element', self._onSyncMessageCb)
+ self.MESSAGE_ID = bus.connect('message', self._onMessageCb)
- logger.debug('takePhoto')
-
- if not self.photo:
- def sink_handoff(sink, buffer, pad, self):
- sink.props.signal_handoffs = False
-
- pixbuf = gtk.gdk.pixbuf_loader_new_with_mime_type('image/jpeg')
- pixbuf.write(buffer)
- pixbuf.close()
-
- structure = gst.Structure('record.photo')
- structure['pixbuf'] = pixbuf.get_pixbuf()
- msg = gst.message_new_custom(gst.MESSAGE_APPLICATION, sink,
- structure)
- self.play_pipe.get_bus().post(msg)
-
- self.photo = gst.element_factory_make('ffmpegcolorspace')
- self.photo_jpegenc = gst.element_factory_make('jpegenc')
- self.photo_sink = gst.element_factory_make('fakesink')
- self.photo_sink.connect('handoff', sink_handoff, self)
-
- def message_cb(bus, message, self):
- if message.type == gst.MESSAGE_APPLICATION \
- and message.structure.get_name() == 'record.photo':
- self.valve.props.drop = True
- self.play_pipe.remove(self.photo)
- self.play_pipe.remove(self.photo_jpegenc)
- self.play_pipe.remove(self.photo_sink)
- self.valve.link(self.play_pipe.get_by_name('queue'))
- self.valve.props.drop = False
- self.after_photo_cb(self, message.structure['pixbuf'])
-
- bus = self.play_pipe.get_bus()
- bus.add_signal_watch()
- bus.connect('message', message_cb, self)
-
- def process_cb(self, pixbuf):
- self.ca.m.savePhoto(pixbuf)
- self._switch_pipe(self.play_pipe)
+ def createPhotoBin ( self ):
+ queue = gst.element_factory_make("queue", "pbqueue")
+ queue.set_property("leaky", True)
+ queue.set_property("max-size-buffers", 1)
- self.after_photo_cb = after_photo_cb and after_photo_cb or process_cb
+ colorspace = gst.element_factory_make("ffmpegcolorspace", "pbcolorspace")
+ jpeg = gst.element_factory_make("jpegenc", "pbjpeg")
- self.valve.props.drop = True
- self.valve.unlink(self.play_pipe.get_by_name('queue'))
- self.play_pipe.add(self.photo, self.photo_jpegenc, self.photo_sink)
- gst.element_link_many(self.valve, self.photo, self.photo_jpegenc,
- self.photo_sink)
- self.photo_sink.props.signal_handoffs = True
- self.valve.props.drop = False
+ sink = gst.element_factory_make("fakesink", "pbsink")
+ self.HANDOFF_ID = sink.connect("handoff", self.copyPic)
+ sink.set_property("signal-handoffs", True)
- self._switch_pipe(self.play_pipe)
+ self.photobin = gst.Bin("photobin")
+ self.photobin.add(queue, colorspace, jpeg, sink)
- def startRecordingVideo(self, quality):
- if not camera_presents:
- return
+ gst.element_link_many(queue, colorspace, jpeg, sink)
- logger.debug('startRecordingVideo quality=%s' % quality)
-
- if not self.video_pipe or quality != self.ogg_quality:
- self.video_pipe = gst.parse_launch( \
- '%s ' \
- '! tee name=tee ' \
- 'tee.! queue ! %s ' \
- 'tee.! queue ' \
- '! ffmpegcolorspace ' \
- '! videorate skip_to_first=true ' \
- '! video/x-raw-yuv,framerate=10/1 ' \
- '! videoscale ' \
- '! video/x-raw-yuv,width=%s,height=%s ' \
- '! theoraenc quality=%s ' \
- '! oggmux ' \
- '! filesink location=%s ' \
- 'alsasrc ' \
- '! queue ' \
- '! audio/x-raw-int,rate=16000,channels=1,depth=16 ' \
- '! wavenc ' \
- '! filesink location=%s ' \
- % (self.src_str, self.play_str,
- OGG_TRAITS[quality]['width'],
- OGG_TRAITS[quality]['height'],
- OGG_TRAITS[quality]['quality'],
- TMP_OGV, TMP_WAV))
-
- def message_cb(bus, message, self):
- if message.type == gst.MESSAGE_ERROR:
- err, debug = message.parse_error()
- logger.error('video_pipe: %s %s' % (err, debug))
-
- bus = self.video_pipe.get_bus()
- bus.add_signal_watch()
- bus.connect('message', message_cb, self)
-
- def process_cb(self, pixbuf):
- self.pixbuf = pixbuf
- self._switch_pipe(self.video_pipe)
-
- self.ogg_quality = quality
- # take photo first
- self.takePhoto(process_cb)
+ pad = queue.get_static_pad("sink")
+ self.photobin.add_pad(gst.GhostPad("sink", pad))
- def stopRecordingVideo(self):
+ def createAudioBin ( self ):
+ src = gst.element_factory_make("alsasrc", "absrc")
+ srccaps = gst.Caps("audio/x-raw-int,rate=16000,channels=1,depth=16")
+
+ enc = gst.element_factory_make("wavenc", "abenc")
+
+ sink = gst.element_factory_make("filesink", "absink")
+ sink.set_property("location", os.path.join(Instance.instancePath, "output.wav"))
+
+ self.audiobin = gst.Bin("audiobin")
+ self.audiobin.add(src, enc, sink)
+
+ src.link(enc, srccaps)
+ enc.link(sink)
+
+ def createVideoBin ( self ):
+ queue = gst.element_factory_make("queue", "vbqueue")
+
+ scale = gst.element_factory_make("videoscale", "vbscale")
+
+ scalecapsfilter = gst.element_factory_make("capsfilter", "scalecaps")
+
+ scalecaps = gst.Caps('video/x-raw-yuv,width='+str(self.VIDEO_WIDTH_SMALL)+',height='+str(self.VIDEO_HEIGHT_SMALL))
+ scalecapsfilter.set_property("caps", scalecaps)
+
+ colorspace = gst.element_factory_make("ffmpegcolorspace", "vbcolorspace")
+
+ enc = gst.element_factory_make("theoraenc", "vbenc")
+ enc.set_property("quality", 16)
+
+ mux = gst.element_factory_make("oggmux", "vbmux")
+
+ sink = gst.element_factory_make("filesink", "vbfile")
+ sink.set_property("location", os.path.join(Instance.instancePath, "output.ogg"))
+
+ self.videobin = gst.Bin("videobin")
+ self.videobin.add(queue, scale, scalecapsfilter, colorspace, enc, mux, sink)
+
+ queue.link(scale)
+ scale.link_pads(None, scalecapsfilter, "sink")
+ scalecapsfilter.link_pads("src", colorspace, None)
+ gst.element_link_many(colorspace, enc, mux, sink)
+
+ pad = queue.get_static_pad("sink")
+ self.videobin.add_pad(gst.GhostPad("sink", pad))
+
+ def cfgVideoBin (self, quality, width, height):
+ vbenc = self.videobin.get_by_name("vbenc")
+ vbenc.set_property("quality", 16)
+ scaps = self.videobin.get_by_name("scalecaps")
+ scaps.set_property("caps", gst.Caps("video/x-raw-yuv,width=%d,height=%d" % (width, height)))
+
+ def createPipeline ( self ):
+ src = gst.element_factory_make("v4l2src", "camsrc")
+ try:
+ # old gst-plugins-good does not have this property
+ src.set_property("queue-size", 2)
+ except:
+ pass
+
+ rate = gst.element_factory_make("videorate", "vbrate")
+ ratecaps = gst.Caps('video/x-raw-yuv,framerate='+str(self.VIDEO_FRAMERATE_SMALL)+'/1')
+
+ tee = gst.element_factory_make("tee", "tee")
+ queue = gst.element_factory_make("queue", "dispqueue")
+ xvsink = gst.element_factory_make("xvimagesink", "xvsink")
+ self.pipeline.add(src, rate, tee, queue, xvsink)
+ src.link(rate)
+ rate.link(tee, ratecaps)
+ gst.element_link_many(tee, queue, xvsink)
+
+ def thumbPipe(self):
+ return self.thumbPipes[ len(self.thumbPipes)-1 ]
+
+
+ def thumbEl(self, name):
+ return self.thumbPipe().get_by_name(name)
+
+
+ def muxPipe(self):
+ return self.muxPipes[ len(self.muxPipes)-1 ]
+
+
+ def muxEl(self, name):
+ return self.muxPipe().get_by_name(name)
+
+
+ def play(self):
if not camera_presents:
return
- logger.debug('stopRecordingVideo')
+ self.pipeline.set_state(gst.STATE_PLAYING)
+ self.playing = True
- self._switch_pipe(self.play_pipe)
+ def pause(self):
+ self.pipeline.set_state(gst.STATE_PAUSED)
+ self.playing = False
- if not os.path.exists(TMP_OGV) \
- or not os.path.exists(TMP_WAV):
+
+ def stop(self):
+ self.pipeline.set_state(gst.STATE_NULL)
+ self.playing = False
+
+ def is_playing(self):
+ return self.playing
+
+ def idlePlayElement(self, element):
+ element.set_state(gst.STATE_PLAYING)
+ return False
+
+ def stopRecordingAudio( self ):
+ self.audiobin.set_state(gst.STATE_NULL)
+ self.pipeline.remove(self.audiobin)
+ gobject.idle_add( self.stoppedRecordingAudio )
+
+
+ def stoppedRecordingVideo(self):
+ if ( len(self.thumbPipes) > 0 ):
+ thumbline = self.thumbPipes[len(self.thumbPipes)-1]
+ thumbline.get_by_name('thumbFakesink').disconnect(self.THUMB_HANDOFF_ID)
+
+ oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv
+ if (not os.path.exists(oggFilepath)):
+ self.record = False
self.ca.m.cannotSaveVideo()
self.ca.m.stoppedRecordingVideo()
return
-
- if os.path.getsize(TMP_OGV) <= 0 \
- or os.path.getsize(TMP_WAV) <= 0:
+ oggSize = os.path.getsize(oggFilepath)
+ if (oggSize <= 0):
+ self.record = False
self.ca.m.cannotSaveVideo()
self.ca.m.stoppedRecordingVideo()
return
- if self.mux_pipe:
- self.mux_pipe.set_state(gst.STATE_NULL)
- del self.mux_pipe
-
- self.mux_pipe = gst.parse_launch( \
- 'filesrc location=%s ' \
- '! oggdemux ' \
- '! theoraparse ' \
- '! oggmux name=mux ' \
- '! filesink location=%s ' \
- 'filesrc location=%s ' \
- '! wavparse ' \
- '! audioconvert ' \
- '%s ' \
- '! vorbisenc name=vorbisenc ' \
- '! mux.' \
- % (TMP_OGV, TMP_OGG, TMP_WAV, AUDIO_FILTER))
-
- taglist = self.getTags(Constants.TYPE_VIDEO)
- vorbisenc = self.mux_pipe.get_by_name('vorbisenc')
- vorbisenc.merge_tags(taglist, gst.TAG_MERGE_REPLACE_ALL)
-
- def done(bus, message, self):
- if message.type == gst.MESSAGE_ERROR:
- err, debug = message.parse_error()
- logger.error('audio_pipe: %s %s' % (err, debug))
+ line = 'filesrc location=' + str(oggFilepath) + ' name=thumbFilesrc ! oggdemux name=thumbOggdemux ! theoradec name=thumbTheoradec ! tee name=thumbTee ! queue name=thumbQueue ! ffmpegcolorspace name=thumbFfmpegcolorspace ! jpegenc name=thumbJPegenc ! fakesink name=thumbFakesink'
+ thumbline = gst.parse_launch(line)
+ thumbQueue = thumbline.get_by_name('thumbQueue')
+ thumbQueue.set_property("leaky", True)
+ thumbQueue.set_property("max-size-buffers", 1)
+ thumbTee = thumbline.get_by_name('thumbTee')
+ thumbFakesink = thumbline.get_by_name('thumbFakesink')
+ self.THUMB_HANDOFF_ID = thumbFakesink.connect("handoff", self.copyThumbPic)
+ thumbFakesink.set_property("signal-handoffs", True)
+ self.thumbPipes.append(thumbline)
+ self.thumbExposureOpen = True
+ gobject.idle_add( self.idlePlayElement, thumbline )
+
+
+ def stoppedRecordingAudio( self ):
+ record.Record.log.debug("stoppedRecordingAudio")
+ if (self.audioPixbuf != None):
+ audioFilepath = os.path.join(Instance.instancePath, "output.wav")#self.el("audioFilesink").get_property("location")
+ if (not os.path.exists(audioFilepath)):
+ self.record = False
+ self.audio = False
+ self.ca.m.cannotSaveVideo()
return
- elif message.type != gst.MESSAGE_EOS:
+ wavSize = os.path.getsize(audioFilepath)
+ if (wavSize <= 0):
+ self.record = False
+ self.ca.m.cannotSaveVideo()
return
- logger.debug('stopRecordingVideo.done')
- self.mux_pipe.set_state(gst.STATE_NULL)
+ self.ca.ui.setPostProcessPixBuf(self.audioPixbuf)
- os.remove(TMP_OGV)
- os.remove(TMP_WAV)
+ line = 'filesrc location=' + str(audioFilepath) + ' name=audioFilesrc ! wavparse name=audioWavparse ! audioconvert name=audioAudioconvert ! vorbisenc name=audioVorbisenc ! oggmux name=audioOggmux ! filesink name=audioFilesink'
+ audioline = gst.parse_launch(line)
- ogg_w = OGG_TRAITS[self.ogg_quality]['width']
- ogg_h = OGG_TRAITS[self.ogg_quality]['height']
+ taglist = self.getTags(Constants.TYPE_AUDIO)
+ base64AudioSnapshot = utils.getStringFromPixbuf(self.audioPixbuf)
+ taglist[gst.TAG_EXTENDED_COMMENT] = "coverart="+str(base64AudioSnapshot)
+ vorbisEnc = audioline.get_by_name('audioVorbisenc')
+ vorbisEnc.merge_tags(taglist, gst.TAG_MERGE_REPLACE_ALL)
- thumb = self.pixbuf.scale_simple(ogg_w, ogg_h,
- gtk.gdk.INTERP_HYPER)
+ audioFilesink = audioline.get_by_name('audioFilesink')
+ audioOggFilepath = os.path.join(Instance.instancePath, "output.ogg")
+ audioFilesink.set_property("location", audioOggFilepath )
- self.ca.ui.setPostProcessPixBuf(thumb)
- self.ca.m.saveVideo(thumb, TMP_OGG, ogg_w, ogg_h)
- self.ca.m.stoppedRecordingVideo()
- self.ca.ui.updateVideoComponents()
+ audioBus = audioline.get_bus()
+ audioBus.add_signal_watch()
+ self.AUDIO_TRANSCODE_ID = audioBus.connect('message', self._onMuxedAudioMessageCb, audioline)
+ self.TRANSCODE_ID = gobject.timeout_add(self.TRANSCODE_UPDATE_INTERVAL, self._transcodeUpdateCb, audioline)
+ gobject.idle_add( self.idlePlayElement, audioline )
+ else:
+ self.record = False
+ self.audio = False
+ self.ca.m.cannotSaveVideo()
- bus = self.mux_pipe.get_bus()
- bus.add_signal_watch()
- bus.connect('message', done, self)
- self.mux_pipe.set_state(gst.STATE_PLAYING)
+ def getTags( self, type ):
+ tl = gst.TagList()
+ tl[gst.TAG_ARTIST] = str(Instance.nickName)
+ tl[gst.TAG_COMMENT] = "olpc"
+ #this is unfortunately, unreliable
+ #record.Record.log.debug("self.ca.metadata['title']->" + str(self.ca.metadata['title']) )
+ tl[gst.TAG_ALBUM] = "olpc" #self.ca.metadata['title']
+ tl[gst.TAG_DATE] = utils.getDateString(int(time.time()))
+ stringType = Constants.mediaTypes[type][Constants.keyIstr]
+ tl[gst.TAG_TITLE] = Constants.istrBy % {"1":stringType, "2":str(Instance.nickName)}
+ return tl
- def startRecordingAudio(self):
- logger.debug('startRecordingAudio')
-
- audio_pipe = \
- 'alsasrc ' \
- '! audioconvert ' \
- '! audio/x-raw-int,rate=16000,channels=1,depth=16 ' \
- '! queue ' \
- '%s ' \
- '! speexenc quality=2 name=vorbisenc ' \
- '! oggmux ' \
- '! filesink location=%s ' \
- % (AUDIO_FILTER, TMP_OGG)
-
- self.audio_pipe = gst.parse_launch(audio_pipe)
-
- def process_cb(self, pixbuf):
- # XXX setting tags on fc9 crashes Record
- #taglist = self.getTags(Constants.TYPE_AUDIO)
- #cover = utils.getStringFromPixbuf(pixbuf)
- #taglist[gst.TAG_EXTENDED_COMMENT] = 'coverart=%s' % cover
- #vorbisenc = self.audio_pipe.get_by_name('vorbisenc')
- #orbisenc.merge_tags(taglist, gst.TAG_MERGE_REPLACE_ALL)
-
- self.pixbuf = pixbuf
- if camera_presents:
- self._switch_pipe(self.play_pipe)
- self.audio_pipe.set_state(gst.STATE_PLAYING)
-
- if camera_presents:
- # take photo first
- self.takePhoto(process_cb)
- else:
- process_cb(self, THUMB_STUB)
+ def blockedCb(self, x, y, z):
+ pass
- def stopRecordingAudio( self ):
- logger.debug('stopRecordingAudio')
+ def _takePhoto(self):
+ if self.picExposureOpen:
+ return
- self.audio_pipe.set_state(gst.STATE_NULL)
+ self.picExposureOpen = True
+ pad = self.photobin.get_static_pad("sink")
+ pad.set_blocked_async(True, self.blockedCb, None)
+ self.pipeline.add(self.photobin)
+ self.photobin.set_state(gst.STATE_PLAYING)
+ self.pipeline.get_by_name("tee").link(self.photobin)
+ pad.set_blocked_async(False, self.blockedCb, None)
- if (not os.path.exists(TMP_OGG)):
- self.ca.m.cannotSaveVideo()
+ def takePhoto(self):
+ if not camera_presents:
return
- if (os.path.getsize(TMP_OGG) <= 0):
- self.ca.m.cannotSaveVideo()
+
+ self.photoMode = self.PHOTO_MODE_PHOTO
+ self._takePhoto()
+
+ def copyPic(self, fsink, buffer, pad, user_data=None):
+ if not self.picExposureOpen:
+ return
+
+ pad = self.photobin.get_static_pad("sink")
+ pad.set_blocked_async(True, self.blockedCb, None)
+ self.pipeline.get_by_name("tee").unlink(self.photobin)
+ self.pipeline.remove(self.photobin)
+ pad.set_blocked_async(False, self.blockedCb, None)
+
+ self.picExposureOpen = False
+ pic = gtk.gdk.pixbuf_loader_new_with_mime_type("image/jpeg")
+ pic.write( buffer )
+ pic.close()
+ pixBuf = pic.get_pixbuf()
+ del pic
+
+ self.savePhoto( pixBuf )
+
+
+ def savePhoto(self, pixbuf):
+ if self.photoMode == self.PHOTO_MODE_AUDIO:
+ self.audioPixbuf = pixbuf
+ else:
+ self.ca.m.savePhoto(pixbuf)
+
+
+ def startRecordingVideo(self, quality):
+ if not camera_presents:
+ return
+
+ self.record = True
+ self.audio = True
+
+ self.cfgVideoBin (OGG_TRAITS[quality]['quality'],
+ OGG_TRAITS[quality]['width'],
+ OGG_TRAITS[quality]['height'])
+
+ pad = self.videobin.get_static_pad("sink")
+ pad.set_blocked_async(True, self.blockedCb, None)
+ self.pipeline.add(self.videobin)
+ self.videobin.set_state(gst.STATE_PLAYING)
+ self.pipeline.get_by_name("tee").link(self.videobin)
+ pad.set_blocked_async(False, self.blockedCb, None)
+ self.pipeline.add(self.audiobin)
+ self.audiobin.set_state(gst.STATE_PLAYING)
+
+ def startRecordingAudio(self):
+ self.audioPixbuf = None
+
+ self.photoMode = self.PHOTO_MODE_AUDIO
+ self._takePhoto()
+
+ self.record = True
+ self.pipeline.add(self.audiobin)
+ self.audiobin.set_state(gst.STATE_PLAYING)
+
+ def stopRecordingVideo(self):
+ if not camera_presents:
return
- self.ca.ui.setPostProcessPixBuf(self.pixbuf)
- self.ca.m.saveAudio(TMP_OGG, self.pixbuf)
+ # We stop the pipeline while we are adjusting the pipeline to stop
+ # recording because if we do it on-the-fly, the following video live
+ # feed to the screen becomes several seconds delayed. Weird!
+ # FIXME: retest on F11
+ self._eos_cb = self.stopRecordingVideoEOS
+ self.pipeline.get_by_name('camsrc').send_event(gst.event_new_eos())
+ self.audiobin.get_by_name('absrc').send_event(gst.event_new_eos())
+
+ def stopRecordingVideoEOS(self):
+ self.pipeline.set_state(gst.STATE_NULL)
+ self.pipeline.get_by_name("tee").unlink(self.videobin)
+ self.pipeline.remove(self.videobin)
+ self.pipeline.remove(self.audiobin)
+ self.pipeline.set_state(gst.STATE_PLAYING)
+ gobject.idle_add( self.stoppedRecordingVideo )
+
+
+ def copyThumbPic(self, fsink, buffer, pad, user_data=None):
+ if (self.thumbExposureOpen):
+ self.thumbExposureOpen = False
+ pic = gtk.gdk.pixbuf_loader_new_with_mime_type("image/jpeg")
+ pic.write(buffer)
+ pic.close()
+ self.thumbBuf = pic.get_pixbuf()
+ del pic
+ self.thumbEl('thumbTee').unlink(self.thumbEl('thumbQueue'))
+
+ oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv
+ if (self.audio):
+ self.ca.ui.setPostProcessPixBuf(self.thumbBuf)
+
+ wavFilepath = os.path.join(Instance.instancePath, "output.wav")
+ muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") #ogv
+
+ muxline = gst.parse_launch('filesrc location=' + str(oggFilepath) + ' name=muxVideoFilesrc ! oggdemux name=muxOggdemux ! theoradec name=muxTheoradec ! theoraenc name=muxTheoraenc ! oggmux name=muxOggmux ! filesink location=' + str(muxFilepath) + ' name=muxFilesink filesrc location=' + str(wavFilepath) + ' name=muxAudioFilesrc ! wavparse name=muxWavparse ! audioconvert name=muxAudioconvert ! vorbisenc name=muxVorbisenc ! muxOggmux.')
+ taglist = self.getTags(Constants.TYPE_VIDEO)
+ vorbisEnc = muxline.get_by_name('muxVorbisenc')
+ vorbisEnc.merge_tags(taglist, gst.TAG_MERGE_REPLACE_ALL)
+
+ muxBus = muxline.get_bus()
+ muxBus.add_signal_watch()
+ self.VIDEO_TRANSCODE_ID = muxBus.connect('message', self._onMuxedVideoMessageCb, muxline)
+ self.muxPipes.append(muxline)
+ #add a listener here to monitor % of transcoding...
+ self.TRANSCODE_ID = gobject.timeout_add(self.TRANSCODE_UPDATE_INTERVAL, self._transcodeUpdateCb, muxline)
+ muxline.set_state(gst.STATE_PLAYING)
+ else:
+ self.record = False
+ self.audio = False
+ self.ca.m.saveVideo(self.thumbBuf, str(oggFilepath), self.VIDEO_WIDTH_SMALL, self.VIDEO_HEIGHT_SMALL)
+ self.ca.m.stoppedRecordingVideo()
+
+
+ def _transcodeUpdateCb( self, pipe ):
+ position, duration = self.queryPosition( pipe )
+ if position != gst.CLOCK_TIME_NONE:
+ value = position * 100.0 / duration
+ value = value/100.0
+ self.ca.ui.progressWindow.updateProgress(value, Constants.istrSaving)
+ return True
+
+
+ def queryPosition( self, pipe ):
+ try:
+ position, format = pipe.query_position(gst.FORMAT_TIME)
+ except:
+ position = gst.CLOCK_TIME_NONE
+
+ try:
+ duration, format = pipe.query_duration(gst.FORMAT_TIME)
+ except:
+ duration = gst.CLOCK_TIME_NONE
+
+ return (position, duration)
+
+
+ def _onMuxedVideoMessageCb(self, bus, message, pipe):
+ t = message.type
+ if (t == gst.MESSAGE_EOS):
+ self.record = False
+ self.audio = False
+ gobject.source_remove(self.VIDEO_TRANSCODE_ID)
+ self.VIDEO_TRANSCODE_ID = 0
+ gobject.source_remove(self.TRANSCODE_ID)
+ self.TRANSCODE_ID = 0
+ pipe.set_state(gst.STATE_NULL)
+ pipe.get_bus().remove_signal_watch()
+ pipe.get_bus().disable_sync_message_emission()
+
+ wavFilepath = os.path.join(Instance.instancePath, "output.wav")
+ oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv
+ muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") #ogv
+ os.remove( wavFilepath )
+ os.remove( oggFilepath )
+ self.ca.m.saveVideo(self.thumbBuf, str(muxFilepath), self.VIDEO_WIDTH_SMALL, self.VIDEO_HEIGHT_SMALL)
+ self.ca.m.stoppedRecordingVideo()
+ return False
+ else:
+ return True
+
+
+ def _onMuxedAudioMessageCb(self, bus, message, pipe):
+ t = message.type
+ if (t == gst.MESSAGE_EOS):
+ record.Record.log.debug("audio gst.MESSAGE_EOS")
+ self.record = False
+ self.audio = False
+ gobject.source_remove(self.AUDIO_TRANSCODE_ID)
+ self.AUDIO_TRANSCODE_ID = 0
+ gobject.source_remove(self.TRANSCODE_ID)
+ self.TRANSCODE_ID = 0
+ pipe.set_state(gst.STATE_NULL)
+ pipe.get_bus().remove_signal_watch()
+ pipe.get_bus().disable_sync_message_emission()
+
+ wavFilepath = os.path.join(Instance.instancePath, "output.wav")
+ oggFilepath = os.path.join(Instance.instancePath, "output.ogg")
+ os.remove( wavFilepath )
+ self.ca.m.saveAudio(oggFilepath, self.audioPixbuf)
+ return False
+ else:
+ return True
+
+
+ def _onSyncMessageCb(self, bus, message):
+ if message.structure is None:
+ return
+ if message.structure.get_name() == 'prepare-xwindow-id':
+ self.window.set_sink(message.src)
+ message.src.set_property('force-aspect-ratio', True)
+
+
+ def _onMessageCb(self, bus, message):
+ t = message.type
+ if t == gst.MESSAGE_EOS:
+ if self._eos_cb:
+ cb = self._eos_cb
+ self._eos_cb = None
+ cb()
+ elif t == gst.MESSAGE_ERROR:
+ #todo: if we come out of suspend/resume with errors, then get us back up and running...
+ #todo: handle "No space left on the resource.gstfilesink.c"
+ #err, debug = message.parse_error()
+ pass
def abandonMedia(self):
- logger.debug('abandonMedia')
self.stop()
if (self.AUDIO_TRANSCODE_ID != 0):
@@ -408,70 +559,22 @@ class Glive:
gobject.source_remove(self.VIDEO_TRANSCODE_ID)
self.VIDEO_TRANSCODE_ID = 0
- if (os.path.exists(TMP_OGG)):
- os.remove(TMP_OGG)
-
- def __init__(self, pca):
- self.window = None
- self.ca = pca
+ wavFilepath = os.path.join(Instance.instancePath, "output.wav")
+ if (os.path.exists(wavFilepath)):
+ os.remove(wavFilepath)
+ oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv
+ if (os.path.exists(oggFilepath)):
+ os.remove(oggFilepath)
+ muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") #ogv
+ if (os.path.exists(muxFilepath)):
+ os.remove(muxFilepath)
- self.pipeline = None
- self.play_pipe = None
- self.fallback_pipe = None
- self.photo = None
- self.video_pipe = None
- self.mux_pipe = None
- self.audio_pipe = None
-
- self.src_str = 'fakesrc'
- self.play_str = 'fakesink'
-
- self.fallback = False
-
- # XXX since sugar doesn't control capture volumes (see #800)
- # we have to do it by ourselves
- alsasrc = gst.element_factory_make('alsasrc')
- alsasrc.set_state(gst.STATE_PAUSED)
- for i in alsasrc.list_tracks():
- if i.props.flags & gst.interfaces.MIXER_TRACK_INPUT \
- and re.search('capture', i.label, flags=re.IGNORECASE):
- alsasrc.set_record(i, True)
- volume = i.props.min_volume \
- + int((i.props.max_volume - i.props.min_volume) \
- / 100. * 90.)
- alsasrc.set_volume(i, tuple([volume] * i.props.num_channels))
- logger.debug('Set volume %s to %s' % (volume, i.label))
- alsasrc.set_state(gst.STATE_NULL)
- del alsasrc
-
- def _switch_pipe(self, new_pipe):
- if self.pipeline != new_pipe:
- if self.pipeline:
- self.pipeline.set_state(gst.STATE_NULL)
- self.pipeline = new_pipe
-
- if self.pipeline:
- xsink = new_pipe.get_by_name('xsink')
- if xsink:
- xsink.set_xwindow_id(self.window.window.xid)
- self.pipeline.set_state(gst.STATE_PLAYING)
-
- def getTags( self, type ):
- tl = gst.TagList()
- tl[gst.TAG_ARTIST] = str(Instance.nickName)
- tl[gst.TAG_COMMENT] = "sugar"
- #this is unfortunately, unreliable
- #record.Record.log.debug("self.ca.metadata['title']->" + str(self.ca.metadata['title']) )
- tl[gst.TAG_ALBUM] = "sugar" #self.ca.metadata['title']
- tl[gst.TAG_DATE] = utils.getDateString(int(time.time()))
- stringType = Constants.mediaTypes[type][Constants.keyIstr]
- tl[gst.TAG_TITLE] = Constants.istrBy % {"1":stringType, "2":str(Instance.nickName)}
- return tl
class LiveVideoWindow(gtk.Window):
def __init__(self, bgd ):
gtk.Window.__init__(self)
+ self.imagesink = None
self.glive = None
self.modify_bg( gtk.STATE_NORMAL, bgd )
@@ -482,3 +585,12 @@ class LiveVideoWindow(gtk.Window):
def set_glive(self, pglive):
self.glive = pglive
self.glive.window = self
+
+ def set_sink(self, sink):
+ if (self.imagesink != None):
+ assert self.window.xid
+ self.imagesink = None
+ del self.imagesink
+
+ self.imagesink = sink
+ self.imagesink.set_xwindow_id(self.window.xid)