Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/olpcgames/camera.py
diff options
context:
space:
mode:
authorManuel Kaufmann <humitos@gmail.com>2012-03-27 13:15:10 (GMT)
committer Rafael Ortiz <rafael@activitycentral.com>2012-03-28 18:05:22 (GMT)
commit335ad73456ba3ec8f56811abddcaca4650199db1 (patch)
treedb788baba57c7656c9bdb73eb9003a70dc87d59a /olpcgames/camera.py
parent6deeb3f569e6c9a1c02a32a011b7a96a58fa8443 (diff)
Save and restore state of the game
Ability to 'save' (when the user closes the Activity) and 'restore' (when the user launch it from the Journal or the Home without holding Alt) the state of the game. For this ability I had to upgrade 'olpcgames' to 1.6 because 'olpcgames.FILE_READ_REQUEST' and 'olpcgames.FILE_WRITE_REQUEST' events are added in that version and those events are needed for this. The data is saved (as JSON, with json module) in the 'event.metadata["state"]' and the timestamp state is saved in 'event.filename'. This commit solves ticket #2393: * http://bugs.sugarlabs.org/ticket/2393 Signed-off-by: Manuel Kaufmann <humitos@gmail.com> Signed-off-by: Rafael Ortiz <rafael@activitycentral.com>
Diffstat (limited to 'olpcgames/camera.py')
-rw-r--r--olpcgames/camera.py186
1 files changed, 86 insertions, 100 deletions
diff --git a/olpcgames/camera.py b/olpcgames/camera.py
index b51a394..249f295 100644
--- a/olpcgames/camera.py
+++ b/olpcgames/camera.py
@@ -2,38 +2,27 @@
Depends upon:
pygame
- python-gstreamer
+ gstreamer (particularly gst-launch)
+
+Activity demonstrating usage:
+
+ http://dev.laptop.org/git?p=projects/games-misc;a=tree;f=cameratest.activity;hb=HEAD
+
+
"""
-import threading
+import threading, subprocess
import logging
+import olpcgames
import time
import os
import pygame
-import gst
from olpcgames.util import get_activity_root
log = logging.getLogger( 'olpcgames.camera' )
#log.setLevel( logging.DEBUG )
-CAMERA_LOAD = 9917
-CAMERA_LOAD_FAIL = 9918
+CAMERA_LOAD, CAMERA_LOAD_FAIL = olpcgames.CAMERA_LOAD, olpcgames.CAMERA_LOAD
-class CameraSprite(object):
- """Create gstreamer surface for the camera."""
- def __init__(self, x, y):
- import olpcgames
- if olpcgames.WIDGET:
- self._init_video(olpcgames.WIDGET, x, y)
-
- def _init_video(self, widget, x, y):
- from olpcgames import video
- self._vid = video.VideoWidget()
- widget._fixed.put(self._vid, x, y)
- self._vid.show()
-
- self.player = video.Player(self._vid)
- self.player.play()
-
class Camera(object):
"""A class representing a still-picture camera
@@ -51,11 +40,18 @@ class Camera(object):
Note:
The Camera class is simply a convenience wrapper around a fairly
- straightforward gstreamer bus. If you have more involved
+ straightforward gst-launch bus. If you have more involved
requirements for your camera manipulations you will probably
find it easier to write your own camera implementation than to
use this one. Basically we provide here the "normal" use case of
snapping a picture into a pygame image.
+
+ Note:
+
+ With the current camera implementation taking a single photograph
+ requires about 6 seconds! Obviously we'll need to figure out what's
+ taking gstreamer so long to process the pipe and fix that.
+
"""
_aliases = {
'camera': 'v4l2src',
@@ -65,7 +61,7 @@ class Camera(object):
'jpeg': 'jpegenc',
'jpg': 'jpegenc',
}
- def __init__(self, source='camera', format='png', filename='snap.png', directory = None):
+ def __init__(self, source='camera', format='png', filename=None, directory = None):
"""Initialises the Camera's internal description
source -- the gstreamer source for the video to capture, useful values:
@@ -74,17 +70,22 @@ class Camera(object):
format -- the gstreamer encoder to use for the capture, useful values:
'pngenc','png' -- PNG format graphic
'jpegenc','jpg','jpeg' -- JPEG format graphic
- filename -- the filename to use for the capture
+ filename -- the filename to use for the capture, if not specified defaults
+ to a random UUID + '.' + format
directory -- the directory in which to create the temporary file, defaults
to get_activity_root() + 'tmp'
"""
+ log.info( 'Creating camera' )
+ if not filename:
+ import uuid
+ filename = '%s.%s'%( uuid.uuid4(), format )
self.source = self._aliases.get( source, source )
self.format = self._aliases.get( format, format )
self.filename = filename
self.directory = directory
- SNAP_PIPELINE = '%(source)s ! ffmpegcolorspace ! %(format)s ! filesink location="%(filename)s"'
- def _create_pipe( self ):
- """Method to create the cstreamer pipe from our settings"""
+ SNAP_PIPELINE = 'gst-launch','%(source)s','!','ffmpegcolorspace','!','%(format)s','!','filesink','location="%(filename)s"'
+ def _create_subprocess( self ):
+ """Method to create the gstreamer subprocess from our settings"""
if not self.directory:
path = os.path.join( get_activity_root(), 'tmp' )
try:
@@ -97,35 +98,27 @@ class Camera(object):
filename = os.path.join( path, self.filename )
format = self.format
source = self.source
- pipeline = self.SNAP_PIPELINE % locals()
- log.debug( 'Background thread processing: %s', pipeline )
- return filename, gst.parse_launch(pipeline)
-
+ pipeline = [s%locals() for s in self.SNAP_PIPELINE ]
+ return filename, subprocess.Popen(
+ pipeline,stderr = subprocess.PIPE
+ )
+
def snap(self):
"""Snap a picture via the camera by iterating gstreamer until finished
Note: this is an unsafe implementation, it will cause the whole
- activity to hang if the operation happens to fail! It is strongly
- recommended that you use snap_async instead of snap!
+ activity to hang until the capture finishes. Time to finish is often
+ measured in whole seconds (3-6s).
+
+ It is *strongly* recommended that you use snap_async instead of snap!
"""
log.debug( 'Starting snap' )
- filename, pipe = self._create_pipe()
- pipe.set_state(gst.STATE_PLAYING)
- bus = pipe.get_bus()
- tmp = False
- while True:
- event = self.bus.poll(gst.MESSAGE_STATE_CHANGED, 5)
- if event:
- old, new, pending = event.parse_state_changed()
- if pending == gst.STATE_VOID_PENDING:
- if tmp:
- break
- else:
- tmp = True
- else:
- break
- log.log( 'Ending snap, loading: %s', filename )
- return self._load_and_clean( filename )
+ filename, pipe = self._create_subprocess()
+ if not pipe.wait():
+ log.debug( 'Ending snap, loading: %s', filename )
+ return self._load_and_clean( filename )
+ else:
+ raise IOError( """Unable to complete snapshot: %s""", pipe.stderr.read() )
def _load_and_clean( self, filename ):
"""Use pygame to load given filename, delete after loading/attempt"""
try:
@@ -142,23 +135,33 @@ class Camera(object):
token -- passed back as attribute of the event which signals that capture
is finished
- We return two types of events CAMERA_LOAD and CAMERA_LOAD_FAIL,
+ We return events of type CAMERA_LOAD with an attribute "succeed"
depending on whether we succeed or not. Attributes of the events which
are returned:
+ success -- whether the loading process succeeded
token -- as passed to this method
- filename -- the filename in our temporary directory we used to store
- the file temporarily
image -- pygame image.load result if successful, None otherwise
+ filename -- the filename in our temporary directory we used to store
+ the file temporarily (this file will be deleted before the event
+ is sent, the name is for informational purposes only).
err -- Exception instance if failed, None otherwise
Basically identical to the snap method, save that it posts a message
- to the event bus in eventwrap instead of blocking and returning...
+ to the event bus in pygame.event instead of blocking and returning...
+
+ Example:
+ if event == pygame.MOUSEBUTTONDOWN:
+ camera = Camera( source='test', filename = 'picture32' )
+ camera.snap_async( myIdentifier )
+ ...
+ elif event.type == olpcgames.CAMERA_LOAD:
+ if event.token == myIdentifier:
+ doSomething( event.image )
"""
log.debug( 'beginning async snap')
- t = threading.Thread(target=self._background_snap, args=(token,))
+ t = threading.Thread(target=self._background_snap, args=[token])
t.start()
- log.debug( 'background thread started for gstreamer' )
return token
def _background_snap(
@@ -175,50 +178,33 @@ class Camera(object):
we begin playing, the second for when we finish.
"""
log.debug( 'Background thread kicking off gstreamer capture begun' )
- from olpcgames import eventwrap
- from pygame.event import Event
- filename, pipe = self._create_pipe()
- bus = pipe.get_bus()
- bus.add_signal_watch()
- def _background_snap_onmessage( bus, message ):
- """Handle messages from the picture-snapping bus"""
- log.debug( 'Message handler for gst messages: %s', message )
- t = message.type
- if t == gst.MESSAGE_EOS:
- pipe.set_state(gst.STATE_NULL)
- try:
- image = self._load_and_clean( filename )
- success = True
- except Exception, err:
- success = False
- image = None
- else:
- err = None
- log.debug( 'Success loading file %r', token )
- eventwrap.post(Event(
- CAMERA_LOAD,
- filename=filename,
- success = success,
- token = token,
- image=image,
- err=err
- ))
-
- elif t == gst.MESSAGE_ERROR:
- log.warn( 'Failure loading file %r: %s', token, message )
- pipe.set_state(gst.STATE_NULL)
- err, debug = message.parse_error()
- eventwrap.post(Event(
- CAMERA_LOAD_FAIL,
- filename=filename,
- success = False,
- token = token,
- image=None,
- err=err
- ))
- return False
- bus.connect('message', _background_snap_onmessage)
- pipe.set_state(gst.STATE_PLAYING)
+ from pygame.event import Event, post
+ filename, pipe = self._create_subprocess()
+ if not pipe.wait():
+ success = True
+ log.debug( 'Ending capture, loading: %s', filename )
+ try:
+ image = self._load_and_clean( filename )
+ except Exception, err:
+ image = None
+ success = False
+ else:
+ err = None
+ else:
+ success = False
+ err = pipe.stderr.read()
+ image = None
+ evt = Event(
+ CAMERA_LOAD,
+ dict(
+ filename=filename,
+ success = success,
+ token = token,
+ image=image,
+ err=err
+ )
+ )
+ post( evt )
def snap():
"""Dump a snapshot from the camera to a pygame surface in background thread