Follow Us

Sunlight Foundation is a nonpartisan nonprofit organization. We depend on the generosity of our supporters to continue our work. With your help, Sunlight can continue to provide you with the most comprehensive, up-to-the-minute tools and resources for tracking political influence.

Take a post-election stand for         accountability!

Support open government:
Donate to Sunlight today!

Donate

Sunlight's (Mostly) Web-based Photo Booth

by

Sunlight Labs recently held an open house to bring members of the technology and transparency communities together over videogames and beer. Our systems administrator, Tim Ball, volunteered to create a photo booth for the event. A few days before the event Tim destroyed his arm in a terrible, unfortunate accident, nearly dashing our hopes for a photo booth. We had to honor Tim's memory (he's still alive) so rather than using an off-the-shelf photo booth software package, I hacked it up from scratch using Python, CSS3, WebSockets, and an iMac.

User Interface

The UI is written in HTML5. That gets us a lot of street cred, right? Well then keep it a secret that I didn't use any of the new elements. This app is HTML5 in doctype only. There is, however, a lot of CSS3 used all over the place. border-radius adds pleasing rounded corners to photos. CSS transforms are used to randomly rotate the photos and text as they are displayed on the screen. These random rotations give the app a natural, messy look that says "Hey kids, this application is free-wheeling and fun!"

In addition to CSS3, I used Typekit to add some distinct fonts to the page. What fun would it be to have a photo booth write to you in Times New Roman? Typekit is a hosted solution for embedding various fonts in your web pages. It uses the long-desired and now near-ubiquitous @font-face CSS property to load a remote font and use it in the rendered page.

On first load of the page, previous photo strips are loaded from disk and displayed randomly in a pile. If someone accidentally reloads the page, you don't want it to appear as if no one has used the app.

The Server

After trying various methods of taking web cam photos, I settled on isightcapture, a command line application for Mac OS X. The UI instructs the server to take a photo by sending a WebSocket message. The server then uses subprocess to invoke the utility and save the individual photos to disk. Once each photo has been taken, PIL is used to apply a lomo-esque filter and combine the photos into a single strip.

The WebSocket server is gevent-websocket and the static media (HTML, JavaScript, images) are served on a separate port by a simple gevent HTTP server. My colleague Andrew pointed out that the WebSocket protocol allows smart switching of HTTP and WebSocket traffic over the same port, but nothing that I tried worked correctly. The normal HTTP traffic was incredibly slow; a 50k image would take about 10 seconds to load locally. It's a pain, but the dual server solution is the best I could come up with in the short time I had to develop the photo booth.

WebSockets

Defined as part of the HTML5 specification, WebSockets give developers long-lived TCP connections for bi-directional communication between client and server. It would have been possible to do a simple Ajax call to the server to initiate the web cam shots, but callbacks to the client would have required messy client side polling of the server. I wanted to have the UI tell the users when to pose for each photo and to show each individual shot as taken. WebSockets made it super easy by setting up listeners on either side to exchange status messages. The general message flow is as follows:

client --> open connection --> server
client <-- reset the screen <-- server  # clear photo strip and messages

# for each photo
client <-- pre-photo <-- server         # next count "get ready!"
client <-- post-photo <-- server        # path to photo
client <-- post-set <-- server

client <-- processing <-- server        # "Hang on, your photos are being processed"
client <-- finished strip <-- server    # display strip, QR code
client <-- done <-- server
client --> close connection --> server

Client-side WebSockets are super easy to work with. After creating a WebSocket instance using the URL of the server, you register event handlers for receiving messages and opening/closing the connection. A basic client would consist of:

var ws = new WebSocket("ws://127.0.0.1");
ws.onmessage = function(e) {
    // e.data is just a string
};

The messages passed between client and server are just simple strings. It is up to the developer to decide the format and content of the messages being passed. I use a basic JSON data structure for the photo booth with each message containing and action field. Luigi showed me an awesome method of registering callbacks based on message events, but it was a bit overkill for a simple project like this.

To the Cloud!

As if claiming to be an HTML5 application isn't cool enough, photobooth has support for the cloud too! If configured, the app will upload all photo strips to Flickr and even place them into a photo set of your choice. If you are worried that your photos may be less than wholesome, you can set the photos to be private by default so that only you have access to them.

Since the app doesn't physically print out photo strips, it displays a QR code that links to the Flickr photo page. Tech savvy users can scan the QR code with their phone to "pick up" their photo strip.

So where does photobooth go from here? Right now I use a Mac-only command line program for capturing images from the iSight camera. I would like to add support for Linux-based capturing of webcam images so that the code will automatically select the appropriate method based on operating system. I need to figure out the proper way of serving WebSocket applications to make it easier to run both the WebSocket server and the static server at the same time. I'd also like to work on a fully hosted version that would use Flash (shudder) to take photos from the webcam and send them to the server.

Grab the code for photobooth from our GitHub repository.