diff options
author | Pootle daemon <pootle@pootle.sugarlabs.org> | 2012-05-30 04:33:32 (GMT) |
---|---|---|
committer | Pootle daemon <pootle@pootle.sugarlabs.org> | 2012-05-30 04:33:32 (GMT) |
commit | 360ec25c74f93ee198d7c5bcd4cc3197ef204108 (patch) | |
tree | da9797248212d44d7946054b64feed4374c51a25 | |
parent | fa42f1b96bd618d1e704ea71c176559558143cd5 (diff) | |
parent | a77659ea4499e0e569aad716a9948aab2672d86a (diff) |
Merge branch 'master' of git.sugarlabs.org:/story/story
-rw-r--r-- | NEWS | 9 | ||||
-rw-r--r-- | StoryActivity.py | 47 | ||||
-rw-r--r-- | activity/activity.info | 2 | ||||
-rw-r--r-- | grecord.py | 83 |
4 files changed, 93 insertions, 48 deletions
@@ -1,3 +1,12 @@ +4 + +ENHANCEMENTS: +* New translations +* Save images to Journal whenever saving an audio recording + +BUG_FIX: +* More robust detection of EOS for audio recording + 3 BUG_FIX: diff --git a/StoryActivity.py b/StoryActivity.py index a876ff6..75a57d8 100644 --- a/StoryActivity.py +++ b/StoryActivity.py @@ -29,7 +29,7 @@ if _have_toolbox: from sugar.activity.widgets import ActivityToolbarButton from sugar.activity.widgets import StopButton -from sugar.graphics.alert import NotifyAlert +from sugar.graphics.alert import Alert from toolbar_utils import button_factory, label_factory, separator_factory from utils import json_load, json_dump, play_audio_from_file @@ -74,6 +74,7 @@ class StoryActivity(activity.Activity): self._recording = False self._grecord = None + self._alert = None self._setup_toolbars(_have_toolbox) self._setup_dispatch_table() @@ -130,7 +131,7 @@ class StoryActivity(activity.Activity): separator_factory(self.toolbar) self.save_as_image = button_factory( - 'image-saveoff', self.toolbar, self.do_save_as_image_cb, + 'image-saveoff', self.toolbar, self._do_save_as_image_cb, tooltip=_('Save as image')) separator_factory(self.toolbar) @@ -173,8 +174,9 @@ class StoryActivity(activity.Activity): dot_list.append(int(dot)) self._game.restore_game(dot_list) - def do_save_as_image_cb(self, button): + def _do_save_as_image_cb(self, button=None): """ Grab the current canvas and save it to the Journal. """ + self._notify_successful_save(title=_('Save as image')) file_path = os.path.join(activity.get_activity_root(), 'instance', 'story.png') png_surface = self._game.export() @@ -189,7 +191,9 @@ class StoryActivity(activity.Activity): datastore.write(dsobject) dsobject.destroy() os.remove(file_path) - self._notify_successful_save(title=_('Save as image')) + if self._alert is not None: + self.remove_alert(self._alert) + self._alert = None def _record_cb(self, button=None): ''' Start/stop audio recording ''' @@ -205,13 +209,7 @@ class StoryActivity(activity.Activity): self._playback_button.set_icon('media-playback-start') self._playback_button.set_tooltip(_('Play recording')) self._notify_successful_save(title=_('Save recording')) - # FIXME: Pause for conversion to ogg to complete - # file size should be not be 0 - gobject.timeout_add( - 3000, subprocess.call, - ['ls', '-l', os.path.join(activity.get_activity_root(), - 'instance')]) - gobject.timeout_add(5000, self._save_recording) + gobject.timeout_add(100, self._wait_for_transcoding_to_finish) else: # Wasn't recording, so start _logger.debug('recording...False. Start recording.') self._grecord.record_audio() @@ -219,6 +217,14 @@ class StoryActivity(activity.Activity): self._record_button.set_icon('media-recording') self._record_button.set_tooltip(_('Stop recording')) + def _wait_for_transcoding_to_finish(self, button=None): + while not self._grecord.transcoding_complete(): + time.sleep(1) + if self._alert is not None: + self.remove_alert(self._alert) + self._alert = None + self._save_recording() + def _playback_recording_cb(self, button=None): ''' Play back current recording ''' _logger.debug('Playback current recording from output.ogg...') @@ -226,7 +232,7 @@ class StoryActivity(activity.Activity): 'instance', 'output.ogg')) return - def _save_recording(self, button=None): + def _save_recording(self): if os.path.exists(os.path.join(activity.get_activity_root(), 'instance', 'output.ogg')): _logger.debug('Saving recording to Journal...') @@ -242,22 +248,19 @@ class StoryActivity(activity.Activity): 'instance', 'output.ogg')) datastore.write(dsobject) dsobject.destroy() + # Always save an image with the recording. + self._do_save_as_image_cb() else: _logger.debug('Nothing to save...') return def _notify_successful_save(self, title='', msg=''): ''' Notify user when saves are completed ''' - - def _notification_alert_response_cb(alert, response_id, self): - self.remove_alert(alert) - - alert = NotifyAlert() - alert.props.title = title - alert.connect('response', _notification_alert_response_cb, self) - alert.props.msg = msg - self.add_alert(alert) - alert.show() + self._alert = Alert() + self._alert.props.title = title + self._alert.props.msg = msg + self.add_alert(self._alert) + self._alert.show() # Collaboration-related methods diff --git a/activity/activity.info b/activity/activity.info index 74ac50e..824d831 100644 --- a/activity/activity.info +++ b/activity/activity.info @@ -1,6 +1,6 @@ [Activity] name = Story -activity_version = 3 +activity_version = 4 license = GPLv3 bundle_id = org.sugarlabs.StoryActivity exec = sugar-activity StoryActivity.StoryActivity @@ -1,5 +1,5 @@ #Copyright (c) 2008, Media Modifications Ltd. -#Copyright (c) 2011, Walter Bender +#Copyright (c) 2011-12, Walter Bender #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal @@ -25,6 +25,9 @@ import time import gtk import gst +import logging +_logger = logging.getLogger("portfolio-activity") + import gobject gobject.threads_init() @@ -62,16 +65,16 @@ class Grecord: srccaps = gst.Caps("audio/x-raw-int,rate=16000,channels=1,depth=16") - # guarantee perfect stream, important for A/V sync + # Guarantee perfect stream, important for A/V sync rate = gst.element_factory_make("audiorate") - # without a buffer here, gstreamer struggles at the start of the + # Without a buffer here, gstreamer struggles at the start of the # recording and then the A/V sync is bad for the whole video # (possibly a gstreamer/ALSA bug -- even if it gets caught up, it - # should be able to resync without problem) + # should be able to resync without problem). queue = gst.element_factory_make("queue", "audioqueue") - queue.set_property("leaky", True) # prefer fresh data - queue.set_property("max-size-time", 5000000000) # 5 seconds + queue.set_property("leaky", True) # prefer fresh data + queue.set_property("max-size-time", 5000000000) # 5 seconds queue.set_property("max-size-buffers", 500) queue.connect("overrun", self._log_queue_overrun) @@ -79,8 +82,8 @@ class Grecord: sink = gst.element_factory_make("filesink", "absink") sink.set_property("location", - os.path.join(self._activity.get_activity_root(), - 'instance', 'output.wav')) + os.path.join(self._activity.get_activity_root(), + 'instance', 'output.wav')) self._audiobin = gst.Bin("audiobin") self._audiobin.add(src, rate, queue, enc, sink) @@ -92,7 +95,7 @@ class Grecord: cbuffers = queue.get_property("current-level-buffers") cbytes = queue.get_property("current-level-bytes") ctime = queue.get_property("current-level-time") - + def play(self): if self._get_state() == gst.STATE_PLAYING: return @@ -127,28 +130,57 @@ class Grecord: 'instance', 'output.wav') if not os.path.exists(audio_path) or os.path.getsize(audio_path) <= 0: # FIXME: inform model of failure? + _logger.error('output.wav does not exist or is empty') return - 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) + line = 'filesrc location=' + audio_path + ' name=audioFilesrc ! \ +wavparse name=audioWavparse ! audioconvert name=audioAudioconvert ! \ +vorbisenc name=audioVorbisenc ! oggmux name=audioOggmux ! \ +filesink name=audioFilesink' + self._audioline = gst.parse_launch(line) - vorbis_enc = audioline.get_by_name('audioVorbisenc') + vorbis_enc = self._audioline.get_by_name('audioVorbisenc') - audioFilesink = audioline.get_by_name('audioFilesink') + audioFilesink = self._audioline.get_by_name('audioFilesink') audioOggFilepath = os.path.join(self._activity.get_activity_root(), - 'instance', 'output.ogg') + 'instance', 'output.ogg') audioFilesink.set_property("location", audioOggFilepath) - audioBus = audioline.get_bus() + audioBus = self._audioline.get_bus() audioBus.add_signal_watch() self._audio_transcode_handler = audioBus.connect( - 'message', self._onMuxedAudioMessageCb, audioline) + 'message', self._onMuxedAudioMessageCb, self._audioline) self._transcode_id = gobject.timeout_add(200, self._transcodeUpdateCb, - audioline) - audioline.set_state(gst.STATE_PLAYING) + self._audioline) + self._audiopos = 0 + self._audioline.set_state(gst.STATE_PLAYING) + + def transcoding_complete(self): + # The EOS message is sometimes either not sent or not received. + # So if the position in the stream is not advancing, assume EOS. + if self._transcode_id is None: + _logger.debug('EOS.... transcoding finished') + return True + else: + position, duration = self._query_position(self._audioline) + # _logger.debug('position: %s, duration: %s' % (str(position), + # str(duration))) + if position == duration: + _logger.debug('We are done, even though we did not see EOS') + self._clean_up_transcoding_pipeline(self._audioline) + return True + elif position == self._audiopos: + _logger.debug('No progess, so assume we are done') + self._clean_up_transcoding_pipeline(self._audioline) + return True + self._audiopos = position + return False + + def blockedCb(self, x, y, z): + pass def record_audio(self): - # we should be able to add the audiobin on the fly, but unfortunately + # We should be able to add the audiobin on the fly, but unfortunately # this results in several seconds of silence being added at the start # of the recording. So we stop the whole pipeline while adjusting it. # SL#2040 @@ -160,7 +192,7 @@ class Grecord: position, duration = self._query_position(pipe) if position != gst.CLOCK_TIME_NONE: value = position * 100.0 / duration - value = value/100.0 + value = value / 100.0 return True def _query_position(self, pipe): @@ -177,9 +209,13 @@ class Grecord: return (position, duration) def _onMuxedAudioMessageCb(self, bus, message, pipe): + # _logger.debug(message.type) if message.type != gst.MESSAGE_EOS: return True + self._clean_up_transcoding_pipeline(pipe) + return False + def _clean_up_transcoding_pipeline(self, pipe): gobject.source_remove(self._audio_transcode_handler) self._audio_transcode_handler = None gobject.source_remove(self._transcode_id) @@ -190,10 +226,8 @@ class Grecord: wavFilepath = os.path.join(self._activity.get_activity_root(), 'instance', 'output.wav') - oggFilepath = os.path.join(self._activity.get_activity_root(), - 'instance', 'output.ogg') - os.remove( wavFilepath ) - return False + os.remove(wavFilepath) + return def _bus_message_handler(self, bus, message): t = message.type @@ -208,4 +242,3 @@ class Grecord: # left on the resource.gstfilesink.c" err, debug = # message.parse_error() pass - |