Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jcarbaugh committed Oct 4, 2010
0 parents commit 627a337
Show file tree
Hide file tree
Showing 13 changed files with 870 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
@@ -0,0 +1,4 @@
*.pyc
photobooth.conf
static/photos/raw/*.jpg
static/photos/strips/*.jpg
62 changes: 62 additions & 0 deletions README.md
@@ -0,0 +1,62 @@
# Photo Booth

## Installation

* Copy photobooth.example.conf to photobooth.conf
* Install the Python packages found in requirements.txt
* Install [PIL](http://www.pythonware.com/products/pil/)

## Settings

count
The number of photos to take for a single strip.

upload
1 to upload to Flickr (see settings below) or 0 to keep photos locally.

lomo_darkness
Darkness setting of the lomo filter.

lomo_saturation
Saturation setting of the lomo filter.

## Flickr upload

* [Create an API](http://www.flickr.com/services/api/) key for your account
* Edit photobooth.conf and set *api_key* and *api_secret* with the values from your Flickr API key
* Get your API token by running the following command from the photobooth directory:

python flickr.py

* Copy the *auth_token* and paste into *auth_token* in photobooth.conf

Other settings Flickr settings:

is_public
A value of 1 will make the uploaded photo public while 0 will keep it private.

photoset
The ID of the Flickr photoset to which the photo will be added. Leave blank to not add to a photoset.

tags
Tags to be added to the photo.

## Dry run

From the photobooth directory, run:

python pb.py

If everything works, you should be prompted for each photo. The paths of the photos and the final strip will be printed to the screen. Photo strips will not be uploaded to Flickr when run with this command.

## Running it for real

Okay, so I don't know of a good way to server the static files from the same server as the WebSocket handler. The only way to get around this in the short time that I had was to run two separate servers: one for WebSocket and the other for static media. Open up two terminal sessions. In the first, run:

python pbserver.py static

And in the second, run:

python pbserver.py app

If everything worked, you can go to http://127.0.0.1:8000 and use the photobooth!
49 changes: 49 additions & 0 deletions flickr.py
@@ -0,0 +1,49 @@
from flickrapi import shorturl
import datetime
import flickrapi
import pbserver

def upload(path):

API_KEY = pbserver.config.get('flickr', 'api_key')
API_SECRET = pbserver.config.get('flickr', 'api_secret')
TOKEN = pbserver.config.get('flickr', 'auth_token')

if not TOKEN:
raise ValueError('invalid or missing token')

flickr = flickrapi.FlickrAPI(API_KEY, API_SECRET, token=TOKEN)

params = {
'filename': path,
'title': '%s' % datetime.datetime.now(),
'is_public': pbserver.config.get('flickr', 'is_public'),
'format': 'etree',
}

tags = pbserver.config.get('flickr', 'tags')
if tags:
params['tags'] = tags

resp = flickr.upload(**params)
photo_id = resp.find('photoid').text

photoset_id = pbserver.config.get('flickr', 'photoset')
if photoset_id:
flickr.photosets_addPhoto(photoset_id=photoset_id, photo_id=photo_id)

return shorturl.url(photo_id)


if __name__ == '__main__':

API_KEY = pbserver.config.get('flickr', 'api_key')
API_SECRET = pbserver.config.get('flickr', 'api_secret')

flickr = flickrapi.FlickrAPI(API_KEY, API_SECRET)

(token, frob) = flickr.get_token_part_one(perms='write')
if not token:
raw_input("Press ENTER after you authorized this program")

print "auth_token:", flickr.get_token_part_two((token, frob))
Binary file added mask.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
94 changes: 94 additions & 0 deletions pb.py
@@ -0,0 +1,94 @@
from PIL import Image, ImageEnhance, ImageColor, ImageDraw
import json
import os
import sqlite3
import subprocess
import uuid

PWD = os.path.abspath(os.path.dirname(__file__))

class PhotoBooth(object):

def __init__(self, size=None, padding=20):

if not size:
size = (320, 240)

self.size = size
self.padding = padding

mask = Image.open(os.path.join(PWD, 'mask.jpg'))
mask = mask.resize(self.size)
self.lomo_mask = mask

self.lomo_darkness = 0.8
self.lomo_saturation = 1.6

def new_set(self):
guid = uuid.uuid4().hex[:16]
return {'id': guid, 'photos': []}

def take_photo(self, photoset):

filename = "%s-%s.jpg" % (photoset['id'], len(photoset['photos']) + 1)
path = os.path.join(PWD, 'static', 'photos', 'raw', filename)
subprocess.call(['isightcapture', path])

photoset['photos'].append(path)

return path

def printout(self, photoset):

count = len(photoset['photos'])
width = self.size[0] + (self.padding * 2)
height = (self.size[1] * count) + (self.padding * (count + 1))

master = Image.new("RGB", (width, height))

draw = ImageDraw.Draw(master)
draw.rectangle(((0, 0), (width - 1, height - 1)), fill="#FFF", outline="#999")
del draw

for i, path in enumerate(photoset['photos']):
im = Image.open(path)
im = im.resize(self.size)
im = self.lomoize(im)
offset_x = self.padding
offset_y = self.padding + (self.size[1] * i) + (self.padding * i)
master.paste(im, (offset_x, offset_y))
del im

filename = "static/photos/strips/%s.jpg" % photoset['id']
path = os.path.abspath(filename)

master.save(path)
del master

return path

def lomoize(self, image, darkness=None, saturation=None):
darker = ImageEnhance.Brightness(image).enhance(darkness or self.lomo_darkness)
saturated = ImageEnhance.Color(image).enhance(saturation or self.lomo_saturation)
lomoized = Image.composite(saturated, darker, self.lomo_mask)
return lomoized

def cleanup(self, photoset):
for path in photoset['photos']:
os.unlink(path)


if __name__ == '__main__':

pb = PhotoBooth()
photoset = pb.new_set()

for i in xrange(4):
print "Pose for photo %s" % i
pb.take_photo(photoset)

# "print" photo strip and display filesystem path
print photoset
print pb.printout(photoset)

pb.cleanup(photoset)
171 changes: 171 additions & 0 deletions pbserver.py
@@ -0,0 +1,171 @@
from gevent import http, pywsgi
from geventwebsocket.handler import WebSocketHandler
from pb import PhotoBooth
import ConfigParser
import flickr
import json
import mimetypes
import os
import qrcode
import re
import sys
import time

config = ConfigParser.ConfigParser()
config.read('photobooth.conf')

pwd = os.path.abspath(os.path.dirname(__file__))
strips_path = os.path.join(pwd, 'static', 'photos', 'strips')
photos_path = os.path.join(pwd, 'static', 'photos', 'raw')
templates_path = os.path.join(pwd, 'templates')
templates = {}

# create photo booth

photo_booth = PhotoBooth()
photo_booth.lomo_darkness = config.getfloat('photobooth', 'lomo_darkness')
photo_booth.lomo_saturation = config.getfloat('photobooth', 'lomo_saturation')

# template stuff

def autoload_templates():
for filename in os.listdir(templates_path):
load_template(path, cache=True)

def load_template(filename, cache=False):
path = os.path.join(templates_path, filename)
template = templates.get(path, None)
if not template:
f = open(path)
template = f.read()
f.close()
if cache:
templates[filename] = template
return template

# websocket utils

def send(ws, params):
ws.send(json.dumps(params))

# apps

def websocket_app(environ, start_response):

path = environ["PATH_INFO"].rstrip('/')

if not path:

start_response("200 OK", [('Content-Type', 'text/html')])
return load_template('base.html')

elif path == '/init':

data = {'photos': []}

for filename in os.listdir(strips_path):
data['photos'].append(filename)

start_response("200 OK", [('Content-Type', 'application/json')])
return json.dumps(data)

elif path == '/pb':

ws = environ["wsgi.websocket"]
#message = ws.wait()

# create new photo set
photoset = photo_booth.new_set()

send(ws, {'action': 'preset'})

# take each photo
count = config.getint('photobooth', 'count')
for i in xrange(count):

# pre-photo message
send(ws, {
'action': 'prephoto',
'index': i,
'count': count,
})

# take photo
photo_booth.take_photo(photoset)

# post-photo message
send(ws, {
'action': 'postphoto',
'index': i,
'count': count,
'filename': path.split('/')[-1],
'localPath': path,
})

send(ws, {'action': 'postset'})

# combine into photo strip and delete originals
strip_path = photo_booth.printout(photoset)
photo_booth.cleanup(photoset)

# upload to Flickr if enabled
if config.get('photobooth', 'upload') == '1':
send(ws, {'action': 'processing'})
flickr_url = flickr.upload(strip_path)
time.sleep(2)

# return final response
ws.send(json.dumps({
'action': 'strip',
'photoId': photoset['id'],
'localPath': strip_path,
'flickrUrl': flickr_url,
'qrCodeUrl': qrcode.image_url(flickr_url),
}))

# close websocket connection
ws.send(json.dumps({'action': 'close'}))

start_response("404 NOT FOUND", [('Content-Type', 'text/plain')])
return []

def static_app(request):

path = os.path.join(pwd, 'static', request.uri.strip('/'))

if not os.path.exists(path):
request.add_output_header('Content-Type', 'text/plain')
request.send_reply(404, "NOT FOUND", '')

f = open(path)
data = f.read()
f.close()

ct = mimetypes.guess_type(path)[0]

request.add_output_header('Content-Type', ct or 'text/plain')
request.send_reply(200, "OK", data)



if __name__ == '__main__':

def serve_photobooth():
server = pywsgi.WSGIServer(('127.0.0.1', 8000), websocket_app, handler_class=WebSocketHandler)
server.serve_forever()

def serve_photos():
http.HTTPServer(('127.0.0.1', 8001), static_app).serve_forever()

args = sys.argv[1:]
command = args[0] if args else None

if command == 'app':
serve_photobooth()

elif command == 'static':
serve_photos()

else:
print "Usage: pbserver.py (app|static)"

0 comments on commit 627a337

Please sign in to comment.