diff options
author | flavio <fdanesse@gmail.com> | 2012-12-18 23:58:57 (GMT) |
---|---|---|
committer | flavio <fdanesse@gmail.com> | 2012-12-18 23:58:57 (GMT) |
commit | 3fa345f645bd591db70b32b487d7bed87a2f3018 (patch) | |
tree | 9feb98042227a11c6efa823936be7c111c920984 | |
parent | e98506d07f590393717250ff0edbcc505517f939 (diff) |
Burn video, audio recording, audio and video processing, generate pixbuf and previews
-rw-r--r-- | glive.py | 173 | ||||
-rw-r--r-- | gplay.py | 1 | ||||
-rw-r--r-- | model.py | 37 | ||||
-rw-r--r-- | record.py | 41 | ||||
-rw-r--r-- | serialize.py | 4 | ||||
-rw-r--r-- | utils.py | 16 |
6 files changed, 182 insertions, 90 deletions
@@ -29,7 +29,6 @@ from gi.repository import GObject from gi.repository import Gst from gi.repository import GstVideo -from sugar3.activity.activity import get_bundle_path import logging from instance import Instance @@ -129,27 +128,29 @@ class Glive: queue = Gst.ElementFactory.make("queue", "pbqueue") queue.set_property("leaky", 1) queue.set_property("max-size-buffers", 1) - + colorspace = Gst.ElementFactory.make("videoconvert", "pbcolorspace") jpeg = Gst.ElementFactory.make("jpegenc", "pbjpeg") - + sink = Gst.ElementFactory.make("fakesink", "pbsink") sink.connect("handoff", self._photo_handoff) sink.set_property("signal-handoffs", True) - + self._photobin = Gst.Bin() + self._photobin.set_name('photobin') + self._photobin.add(queue) self._photobin.add(colorspace) self._photobin.add(jpeg) self._photobin.add(sink) - + queue.link(colorspace) colorspace.link(jpeg) jpeg.link(sink) - + pad = queue.get_static_pad("sink") self._photobin.add_pad(Gst.GhostPad.new("sink", pad)) - + def _create_audiobin(self): src = Gst.ElementFactory.make("alsasrc", "absrc") @@ -165,7 +166,7 @@ class Glive: # WARNING **: 0.10-style raw audio caps are being created. Should be audio/x-raw,format=(string) srccaps = Gst.Caps.from_string("audio/x-raw-int,rate=16000,channels=1,depth=16") - + # guarantee perfect stream, important for A/V sync rate = Gst.ElementFactory.make("audiorate", 'audiorate') @@ -191,9 +192,11 @@ class Glive: self._audiobin.add(queue) self._audiobin.add(enc) self._audiobin.add(sink) - - src.link(rate) - rate.link(queue) + + # FIXME: audiorate - If these elements connect the pipeline does not run. + # FIXME: srccaps - If these elements connect the pipeline does not run. + src.link(queue) + #rate.link(queue) queue.link(enc) enc.link(sink) @@ -225,15 +228,16 @@ class Glive: self._videobin.add(queue) self._videobin.add(scale) - self._videobin.add(scalecapsfilter) + #self._videobin.add(scalecapsfilter) self._videobin.add(colorspace) self._videobin.add(enc) self._videobin.add(mux) self._videobin.add(sink) queue.link(scale) - scale.link_pads(None, scalecapsfilter, "sink") - scalecapsfilter.link_pads("src", colorspace, None) + #scale.link_pads(None, scalecapsfilter, "sink") # FIXME: If these elements connect the pipeline does not run. + #scalecapsfilter.link_pads("src", colorspace, None) + scale.link(colorspace) colorspace.link(enc) enc.link(mux) mux.link(sink) @@ -267,8 +271,8 @@ class Glive: 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.from_string("video/x-raw-yuv,width=%d,height=%d" % (width, height))) + #scaps = self._videobin.get_by_name("scalecaps") # FIXME: If these elements connect the pipeline does not run. + #scaps.set_property("caps", Gst.Caps.from_string("video/x-raw-yuv,width=%d,height=%d" % (width, height))) def _create_pipeline(self): @@ -295,7 +299,7 @@ class Glive: # the FPS value. rate = Gst.ElementFactory.make("videorate", 'videorate') ratecaps = Gst.Caps.from_string('video/x-raw-yuv,framerate=10/1') - + tee = Gst.ElementFactory.make("tee", "tee") queue = Gst.ElementFactory.make("queue", "dispqueue") @@ -360,7 +364,7 @@ class Glive: queue.link(self._xvsink) return self._xvsink - + def _configure_x(self): if self._pipeline.get_by_name("xbin") == self._xbin: @@ -419,6 +423,8 @@ class Glive: return self._pipeline.get_state(0)[1] def stop_recording_audio(self): + """ Stops recording audio + process begins wav to ogg.""" # We should be able to simply pause and remove the audiobin, but # this seems to cause a gstreamer segfault. So we stop the whole @@ -435,17 +441,23 @@ class Glive: if self._audio_pixbuf: self.model.still_ready(self._audio_pixbuf) + + command = "filesrc location=%s name=audioFilesrc ! \ + wavparse name=audioWavparse ! \ + audioconvert name=audioAudioconvert ! \ + vorbisenc name=audioVorbisenc ! \ + oggmux name=audioOggmux ! \ + filesink name=audioFilesink" % (audio_path) + + audioline = Gst.parse_launch(command) - line = 'filesrc location=' + audio_path + ' name=audioFilesrc ! wavparse name=audioWavparse ! audioconvert name=audioAudioconvert ! vorbisenc name=audioVorbisenc ! oggmux name=audioOggmux ! filesink name=audioFilesink' - audioline = Gst.parse_launch(line) - - taglist = self._get_tags(constants.TYPE_AUDIO) + #taglist = self._get_tags(constants.TYPE_AUDIO) - if self._audio_pixbuf: - pixbuf_b64 = utils.getStringFromPixbuf(self._audio_pixbuf) + #if self._audio_pixbuf: + #pixbuf_b64 = utils.getStringFromPixbuf(self._audio_pixbuf) #taglist[Gst.TAG_EXTENDED_COMMENT] = "coverart=" + pixbuf_b64 - vorbis_enc = audioline.get_by_name('audioVorbisenc') + #vorbis_enc = audioline.get_by_name('audioVorbisenc') #vorbis_enc.merge_tags(taglist, Gst.TagMergeMode.REPLACE_ALL) audioFilesink = audioline.get_by_name('audioFilesink') @@ -454,7 +466,10 @@ class Glive: audioBus = audioline.get_bus() audioBus.add_signal_watch() - self._audio_transcode_handler = audioBus.connect('message', self._onMuxedAudioMessageCb, audioline) + + self._audio_transcode_handler = audioBus.connect( + 'message', self._onMuxedAudioMessageCb, audioline) + self._transcode_id = GObject.timeout_add(200, self._transcodeUpdateCb, audioline) audioline.set_state(Gst.State.PLAYING) @@ -483,10 +498,9 @@ class Glive: self._photo_mode = photo_mode self._pic_exposure_open = True - pad = self._photobin.get_static_pad("sink") self._pipeline.add(self._photobin) - self._photobin.set_state(Gst.State.PLAYING) self._pipeline.get_by_name("tee").link(self._photobin) + self._photobin.set_state(Gst.State.PLAYING) def take_photo(self): @@ -494,14 +508,14 @@ class Glive: self._take_photo(self.PHOTO_MODE_PHOTO) def _photo_handoff(self, fsink, buffer, pad, user_data=None): + """Generates the file for photography.""" if not self._pic_exposure_open: return - - pad = self._photobin.get_static_pad("sink") + self._pipeline.get_by_name("tee").unlink(self._photobin) self._pipeline.remove(self._photobin) - + self._pic_exposure_open = False #pic = GdkPixbuf.PixbufLoader.new_with_mime_type("image/jpeg") # FIXME: TypeError: Must be sequence, not Buffer @@ -509,8 +523,18 @@ class Glive: #pic.close() #pixBuf = pic.get_pixbuf() #del pic - - #self.save_photo(pixBuf) + + # FIXME: Must provide a pixbuf here. + # Attempt to modify the functions to get through + # gdkpixbufsink as in the pictures, but there was + # no success, so this pixbuf temporarily assigned + # to find a solution to this problem. + path = os.path.dirname(__file__) + pix_file = os.path.join(path, 'gfx', 'media-circle.png') + self.thumbBuf = GdkPixbuf.Pixbuf.new_from_file(pix_file) + self.model.still_ready(self.thumbBuf) + + self.save_photo(pixBuf) def save_photo(self, pixbuf): @@ -521,6 +545,7 @@ class Glive: self.model.save_photo(pixbuf) def record_video(self, quality): + """Starts recording video.""" if not self._has_camera: return @@ -543,6 +568,7 @@ class Glive: self.play() def record_audio(self): + """Starts recording audio.""" if self._has_camera: self._audio_pixbuf = None @@ -569,7 +595,7 @@ class Glive: self._eos_cb = self._video_eos 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 _video_eos(self): self._pipeline.set_state(Gst.State.NULL) @@ -588,27 +614,38 @@ class Glive: if not os.path.exists(ogg_path) or os.path.getsize(ogg_path) <= 0: # FIXME: inform model of failure? return - - line = 'filesrc location=' + ogg_path + ' name=thumbFilesrc ! oggdemux name=thumbOggdemux ! theoradec name=thumbTheoradec ! tee name=thumb_tee ! queue name=thumb_queue ! videoconvert name=thumbFfmpegcolorspace ! jpegenc name=thumbJPegenc ! fakesink name=thumb_fakesink' - thumbline = Gst.parse_launch(line) + command = "filesrc location=%s name=thumbFilesrc ! \ + oggdemux name=thumbOggdemux ! \ + theoradec name=thumbTheoradec ! \ + tee name=thumb_tee ! \ + queue name=thumb_queue ! \ + videoconvert name=thumbFfmpegcolorspace ! \ + jpegenc name=thumbJPegenc ! \ + fakesink name=thumb_fakesink" % (ogg_path) + + thumbline = Gst.parse_launch(command) thumb_queue = thumbline.get_by_name('thumb_queue') thumb_queue.set_property("leaky", 1) thumb_queue.set_property("max-size-buffers", 1) - thumb_tee = thumbline.get_by_name('thumb_tee') + thumb_fakesink = thumbline.get_by_name('thumb_fakesink') self._thumb_handoff_handler = thumb_fakesink.connect("handoff", self.copyThumbPic) thumb_fakesink.set_property("signal-handoffs", True) + self._thumb_pipes.append(thumbline) + self._thumb_exposure_open = True + thumbline.set_state(Gst.State.PLAYING) def copyThumbPic(self, fsink, buffer, pad, user_data=None): + """Generates a thumbnail for the video.""" if not self._thumb_exposure_open: return - + self._thumb_exposure_open = False #loader = GdkPixbuf.PixbufLoader.new_with_mime_type("image/jpeg") # FIXME: TypeError: Must be sequence, not Buffer @@ -616,28 +653,55 @@ class Glive: #loader.close() #self.thumbBuf = loader.get_pixbuf() #self.model.still_ready(self.thumbBuf) - + self._thumb_element('thumb_tee').unlink(self._thumb_element('thumb_queue')) + # FIXME: Must provide a pixbuf here. + # Attempt to modify the functions to get through + # gdkpixbufsink as in the pictures, but there was + # no success, so this pixbuf temporarily assigned + # to find a solution to this problem. + path = os.path.dirname(__file__) + pix_file = os.path.join(path, 'gfx', 'media-record.png') + self.thumbBuf = GdkPixbuf.Pixbuf.new_from_file(pix_file) + self.model.still_ready(self.thumbBuf) + + # Process audio and video to a single file. oggFilepath = os.path.join(Instance.instancePath, "output.ogg") wavFilepath = os.path.join(Instance.instancePath, "output.wav") muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") - - muxline = Gst.parse_launch('filesrc location=' + str(oggFilepath) + ' name=muxVideoFilesrc ! oggdemux name=muxOggdemux ! theoraparse ! 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._get_tags(constants.TYPE_VIDEO) - vorbis_enc = muxline.get_by_name('muxVorbisenc') + + command = "filesrc location=%s name=muxVideoFilesrc ! \ + oggdemux name=muxOggdemux ! \ + oggmux name=muxOggmux ! \ + filesink location=%s name=muxFilesink \ + filesrc location=%s name=muxAudioFilesrc ! \ + wavparse name=muxWavparse ! \ + audioconvert name=muxAudioconvert ! \ + vorbisenc name=muxVorbisenc ! \ + muxOggmux." % ( str(oggFilepath), str(muxFilepath), str(wavFilepath) ) + + muxline = Gst.parse_launch(command) + + #taglist = self._get_tags(constants.TYPE_VIDEO) + #vorbis_enc = muxline.get_by_name('muxVorbisenc') #vorbis_enc.merge_tags(taglist, Gst.TagMergeMode.REPLACE_ALL) muxBus = muxline.get_bus() muxBus.add_signal_watch() - self._video_transcode_handler = muxBus.connect('message', self._onMuxedVideoMessageCb, muxline) + + self._video_transcode_handler = muxBus.connect('message', + self._onMuxedVideoMessageCb, muxline) + self._mux_pipes.append(muxline) + #add a listener here to monitor % of transcoding... - self._transcode_id = GObject.timeout_add(200, self._transcodeUpdateCb, muxline) + self._transcode_id = GObject.timeout_add(200, + self._transcodeUpdateCb, muxline) + muxline.set_state(Gst.State.PLAYING) - - def _transcodeUpdateCb( self, pipe ): + + def _transcodeUpdateCb(self, pipe): position, duration = self._query_position( pipe ) @@ -671,17 +735,22 @@ class Glive: GObject.source_remove(self._video_transcode_handler) self._video_transcode_handler = None + GObject.source_remove(self._transcode_id) self._transcode_id = None + 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") muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") + os.remove( wavFilepath ) os.remove( oggFilepath ) + self.model.save_video(muxFilepath, self.thumbBuf) return False @@ -693,15 +762,20 @@ class Glive: GObject.source_remove(self._audio_transcode_handler) self._audio_transcode_handler = None + GObject.source_remove(self._transcode_id) self._transcode_id = None + 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.model.save_audio(oggFilepath, self._audio_pixbuf) return False @@ -752,3 +826,4 @@ class Glive: if os.path.exists(mux_path): os.remove(mux_path) +
\ No newline at end of file @@ -120,7 +120,6 @@ class Gplay(GObject.GObject): value = (float(position) / float(duration)) * 100.0 self._emit_playback_status(value) - return True def _emit_playback_status(self, position): @@ -76,17 +76,22 @@ class Model: def write_file(self, path): - ui_serialized = self.activity.serialize() - self.mediaHashs['ui'] = ui_serialized - dom = serialize.saveMediaHash(self.mediaHashs, self.activity) - ui_data = json.dumps(ui_serialized) - ui_el = dom.createElement('ui') - ui_el.appendChild(dom.createTextNode(ui_data)) - dom.documentElement.appendChild(ui_el) - - fd = open(path, "w") - dom.writexml(fd) - fd.close() + # FIXME: Error: invalid value (typically too big) for the size of the input (surface, pattern, etc.) + # File ". . ./sugar3/activity/activity.py", line 648, in get_preview + # dummy_cr = Gdk.cairo_create(window) + + #ui_serialized = self.activity.serialize() + #self.mediaHashs['ui'] = ui_serialized + #dom = serialize.saveMediaHash(self.mediaHashs, self.activity) + #ui_data = json.dumps(ui_serialized) + #ui_el = dom.createElement('ui') + #ui_el.appendChild(dom.createTextNode(ui_data)) + #dom.documentElement.appendChild(ui_el) + + #fd = open(path, "w") + #dom.writexml(fd) + #fd.close() + pass def read_file(self, path): @@ -312,10 +317,10 @@ class Model: recd = self.createNewRecorded(constants.TYPE_PHOTO) imgpath = os.path.join(Instance.instancePath, recd.mediaFilename) - pixbuf.save(imgpath, "jpeg") + pixbuf.savev(imgpath, "jpeg", [], []) pixbuf = utils.generate_thumbnail(pixbuf) - pixbuf.save(recd.make_thumb_path(), "png") + pixbuf.savev(recd.make_thumb_path(), "png", [], []) #now that we've saved both the image and its pixbuf, we get their md5s self.createNewRecordedMd5Sums( recd ) @@ -329,7 +334,7 @@ class Model: os.rename(path, os.path.join(Instance.instancePath, recd.mediaFilename)) still = utils.generate_thumbnail(still) - still.save(recd.make_thumb_path(), "png") + still.savev(recd.make_thumb_path(), "png", [], []) self.createNewRecordedMd5Sums( recd ) @@ -344,11 +349,11 @@ class Model: if still: image_path = os.path.join(Instance.instancePath, "audioPicture.png") image_path = utils.getUniqueFilepath(image_path, 0) - still.save(image_path, "png") + still.savev(image_path, "png", [], []) recd.audioImageFilename = os.path.basename(image_path) still = utils.generate_thumbnail(still) - still.save(recd.make_thumb_path(), "png") + still.savev(recd.make_thumb_path(), "png", [], []) self.createNewRecordedMd5Sums( recd ) @@ -123,12 +123,6 @@ class Record(activity.Activity): self.model.write_file(path) - def close(self): - - self.model.gplay.stop() - self.model.glive.stop() - super(Record, self).close() - def _visibility_changed(self, widget, event): self.model.set_visible(event.state != Gdk.VisibilityState.FULLY_OBSCURED) @@ -788,11 +782,25 @@ class CountdownImage(Gtk.Image): w = 55 h = w - # FIXME: No more Pixmap - """ - pixmap = GdK.Pixmap(self.get_window(), w, h, -1) - ctx = pixmap.cairo_create() - ctx.rectangle(0, 0, w, h) + x, y = self.get_property('window').get_position() + rect = self.get_allocation() + width, height = rect.width, rect.height + + thumb_surface = Gdk.Window.create_similar_surface( + self.get_property('window'), + cairo.CONTENT_COLOR, + width, height) + + ctx = cairo.Context(thumb_surface) + + Gdk.cairo_set_source_window( + cairo_context, + self.get_property('window'), + x, y) + + ctx.paint() + + ctx.rectangle(x, y, w, h) ctx.set_source_rgb(0, 0, 0) ctx.fill() @@ -819,14 +827,19 @@ class CountdownImage(Gtk.Image): ctx.translate(-3, 0) pctx.show_layout(play) - return pixmap""" - + pixbuf = Gdk.pixbuf_get_from_surface( + thumb_surface, 0, 0, + thumb_surface.get_width(), + thumb_surface.get_height()) + + return pixbuf + def set_value(self, num): if num not in self._countdown_images: self._countdown_images[num] = self._generate_image(num) - self.set_from_pixmap(self._countdown_images[num], None) + self.set_from_pixbuf(self._countdown_images[num], None) class ShutterButton(Gtk.Button): diff --git a/serialize.py b/serialize.py index a52c6ca..551170a 100644 --- a/serialize.py +++ b/serialize.py @@ -141,7 +141,7 @@ def fillRecdFromNode(recd, el): thumbPath = os.path.join(Instance.instancePath, "datastoreThumb.jpg") thumbPath = utils.getUniqueFilepath(thumbPath, 0) thumbImg = utils.getPixbufFromString(bt.nodeValue) - thumbImg.save(thumbPath, "jpeg", {"quality":"85"} ) + thumbImg.savev(thumbPath, "jpeg", {"quality":"85"} ) recd.thumbFilename = os.path.basename(thumbPath) logger.debug("saved thumbFilename") @@ -155,7 +155,7 @@ def fillRecdFromNode(recd, el): audioImagePath = os.path.join(Instance.instancePath, "audioImage.png") audioImagePath = utils.getUniqueFilepath( audioImagePath, 0 ) audioImage = utils.getPixbufFromString( ai.nodeValue ) - audioImage.save(audioImagePath, "png", {} ) + audioImage.savev(audioImagePath, "png", {} ) recd.audioImageFilename = os.path.basename(audioImagePath) logger.debug("loaded audio image and set audioImageFilename") @@ -10,17 +10,15 @@ from gi.repository import GdkPixbuf from gi.repository import Rsvg def getStringFromPixbuf(pixbuf): + """Converts a pixbuf in a string.""" - data = [""] - pixbuf.save_to_callback(_saveDataToBufferCb, "png", {}, data) - return base64.b64encode(str(data[0])) - -def _saveDataToBufferCb(buf, data): + # Save_to_bufferv return: (bool, string) + data = pixbuf.save_to_bufferv('png', [], []) - data[0] += buf - return True + return base64.b64encode(data[1]) def getPixbufFromString(str): + """Converts a string into a pixbuf.""" pbl = GdkPixbuf.PixbufLoader() data = base64.b64decode( str ) @@ -30,6 +28,8 @@ def getPixbufFromString(str): return pbl.get_pixbuf() def load_colored_svg(filename, stroke, fill): + """Loads an svg, will change the fill and + stroke colors and returns the pixbuf.""" path = os.path.join(constants.GFX_PATH, filename) data = open(path, 'r').read() @@ -40,7 +40,7 @@ def load_colored_svg(filename, stroke, fill): entity = '<!ENTITY stroke_color "%s">' % stroke data = re.sub('<!ENTITY stroke_color .*>', entity, data) - Rsvg.Handle.new_from_data(data.encode('utf-8')).get_pixbuf() + return Rsvg.Handle.new_from_data(data.encode('utf-8')).get_pixbuf() def getUniqueFilepath( path, i ): |