Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPootle 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)
commit360ec25c74f93ee198d7c5bcd4cc3197ef204108 (patch)
treeda9797248212d44d7946054b64feed4374c51a25
parentfa42f1b96bd618d1e704ea71c176559558143cd5 (diff)
parenta77659ea4499e0e569aad716a9948aab2672d86a (diff)
Merge branch 'master' of git.sugarlabs.org:/story/story
-rw-r--r--NEWS9
-rw-r--r--StoryActivity.py47
-rw-r--r--activity/activity.info2
-rw-r--r--grecord.py83
4 files changed, 93 insertions, 48 deletions
diff --git a/NEWS b/NEWS
index 823f6de..2dd3336 100644
--- a/NEWS
+++ b/NEWS
@@ -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
diff --git a/grecord.py b/grecord.py
index d992463..02fcbd3 100644
--- a/grecord.py
+++ b/grecord.py
@@ -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
-