Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 627a337
Showing
13 changed files
with
870 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*.pyc | ||
photobooth.conf | ||
static/photos/raw/*.jpg | ||
static/photos/strips/*.jpg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)" | ||
|
Oops, something went wrong.