Files
photobooth/photobooth.py
2015-06-28 17:41:35 +02:00

323 lines
10 KiB
Python
Executable File

#!/usr/bin/env python
# Created by br@re-web.eu, 2015
import os
from datetime import datetime
from glob import glob
from sys import exit
from time import sleep, clock
from PIL import Image
from gui import GUI_PyGame as GuiModule
from camera import CameraException, Camera_gPhoto as CameraModule
from events import Rpi_GPIO as GPIO
#####################
### Configuration ###
#####################
# Screen size
display_size = (1024, 600)
# Maximum size of assembled image
image_size = (2352, 1568)
# Size of pictures in the assembled image
thumb_size = (1176, 784)
# Image basename
picture_basename = datetime.now().strftime("%Y-%m-%d/pic")
# GPIO channel of switch to shutdown the Photobooth
gpio_shutdown_channel = 24 # pin 18 in all Raspi-Versions
# GPIO channel of switch to take pictures
gpio_trigger_channel = 23 # pin 16 in all Raspi-Versions
# GPIO output channel for (blinking) lamp
gpio_lamp_channel = 4 # pin 7 in all Raspi-Versions
# Waiting time in seconds for posing
pose_time = 5
# Display time for taken pictures
display_time = 10
###############
### Classes ###
###############
class PictureList:
"""Class to manage images and count them"""
def __init__(self, basename):
# Set basename and suffix
self.basename = basename
self.suffix = ".jpg"
self.count_width = 5
# Ensure directory exists
dirname = os.path.dirname(self.basename)
if not os.path.exists(dirname):
os.makedirs(dirname)
# Find existing files
count_pattern = "[0-9]" * self.count_width
pictures = glob(self.basename + count_pattern + self.suffix)
# Get number of latest file
if len(pictures) == 0:
self.counter = 0
else:
pictures.sort()
last_picture = pictures[-1]
self.counter = int(last_picture[-(self.count_width+len(self.suffix)):-len(self.suffix)])
print("Number of last existing file: " + str(self.counter) + "(" + str(len(pictures)) + ")")
print("Saving as: " + self.basename)
def get(self, count):
return self.basename + str(count).zfill(self.count_width) + self.suffix
def get_last(self):
return self.get(self.counter)
def get_next(self):
self.counter += 1
return self.get(self.counter)
class Photobooth:
def __init__(self, display_size, picture_basename, picture_size, pose_time, display_time,
trigger_channel, shutdown_channel, lamp_channel):
self.display = GuiModule('Photobooth', display_size)
self.pictures = PictureList(picture_basename)
self.camera = CameraModule(picture_size)
self.pic_size = picture_size
self.pose_time = pose_time
self.display_time = display_time
self.trigger_channel = trigger_channel
self.shutdown_channel = shutdown_channel
self.lamp_channel = lamp_channel
input_channels = [ trigger_channel, shutdown_channel ]
output_channels = [ lamp_channel ]
self.gpio = GPIO(self.handle_gpio, input_channels, output_channels)
def teardown(self):
self.display.clear()
self.display.show_message("Shutting down...")
self.display.apply()
self.gpio.set_output(self.lamp_channel, 0)
sleep(0.5)
self.display.teardown()
self.gpio.teardown()
exit(0)
def run(self):
while True:
try:
# Enable lamp
self.gpio.set_output(self.lamp_channel, 1)
while True:
# Display default message
self.display.clear()
self.display.show_message("Hit the button!")
self.display.apply()
# Wait for an event and handle it
event = self.display.wait_for_event()
self.handle_event(event)
except CameraException as e:
self.handle_exception(e.message)
def handle_gpio(self, channel):
if channel in [ self.trigger_channel, self.shutdown_channel ]:
self.display.trigger_event(channel)
def handle_event(self, event):
if event.type == 0:
self.teardown()
elif event.type == 1:
self.handle_keypress(event.value)
elif event.type == 2:
self.handle_mousebutton(event.value[0], event.value[1])
elif event.type == 3:
self.handle_gpio_event(event.value)
def handle_keypress(self, key):
"""Implements the actions for the different keypress events"""
# Exit the application
if key == ord('q'):
self.teardown()
# Take pictures
elif key == ord('c'):
self.take_picture()
def handle_mousebutton(self, key, pos):
"""Implements the actions for the different mousebutton events"""
# Take a picture
if key == 1:
self.take_picture()
def handle_gpio_event(self, channel):
"""Implements the actions taken for a GPIO event"""
if channel == self.trigger_channel:
self.take_picture()
elif channel == self.shutdown_channel:
self.teardown()
def handle_exception(self, msg):
"""Displays an error message and returns"""
self.display.clear()
print("Error: " + msg)
self.display.show_message("ERROR:\n\n" + msg)
self.display.apply()
sleep(3)
def assemble_pictures(self, input_filenames):
"""Assembles four pictures into a 2x2 grid
It assumes, all original pictures have the same aspect ratio as
the resulting image.
For the thumbnail sizes we have:
h = (H - 2 * a - 2 * b) / 2
w = (W - 2 * a - 2 * b) / 2
W
|---------------------------------------|
--- +---+-------------+---+-------------+---+ ---
| | | | a
| | +-------------+ +-------------+ | ---
| | | | | | | |
| | | 0 | | 1 | | | h
| | | | | | | |
| | +-------------+ +-------------+ | ---
H | | | | 2*b
| | +-------------+ +-------------+ | ---
| | | | | | | |
| | | 2 | | 3 | | | h
| | | | | | | |
| | +-------------+ +-------------+ | ---
| | | | a
--- +---+-------------+---+-------------+---+ ---
|---|-------------|---|-------------|---|
a w 2*b w a
"""
# Thumbnail size of pictures
outer_border = 50
inner_border = 20
thumb_box = ( int( self.pic_size[0] / 2 ) ,
int( self.pic_size[1] / 2 ) )
thumb_size = ( thumb_box[0] - outer_border - inner_border ,
thumb_box[1] - outer_border - inner_border )
# Create output image with white background
output_image = Image.new('RGB', self.pic_size, (255, 255, 255))
# Image 0
img = Image.open(input_filenames[0])
img.thumbnail(thumb_size)
offset = ( thumb_box[0] - inner_border - img.size[0] ,
thumb_box[1] - inner_border - img.size[1] )
output_image.paste(img, offset)
# Image 1
img = Image.open(input_filenames[1])
img.thumbnail(thumb_size)
offset = ( thumb_box[0] + inner_border,
thumb_box[1] - inner_border - img.size[1] )
output_image.paste(img, offset)
# Image 2
img = Image.open(input_filenames[2])
img.thumbnail(thumb_size)
offset = ( thumb_box[0] - inner_border - img.size[0] ,
thumb_box[1] + inner_border )
output_image.paste(img, offset)
# Image 3
img = Image.open(input_filenames[3])
img.thumbnail(thumb_size)
offset = ( thumb_box[0] + inner_border ,
thumb_box[1] + inner_border )
output_image.paste(img, offset)
# Save assembled image
output_filename = self.pictures.get_next()
output_image.save(output_filename, "JPEG")
return output_filename
def take_picture(self):
"""Implements the picture taking routine"""
# Disable lamp
self.gpio.set_output(self.lamp_channel, 0)
# Show pose message
self.display.clear()
self.display.show_message("POSE!\n\nTaking four pictures...");
self.display.apply()
sleep(self.pose_time - 3)
# Extract display and image sizes
size = self.display.get_size()
outsize = (int(size[0]/2), int(size[1]/2))
# Countdown
for i in range(3):
self.display.clear()
self.display.show_message(str(3 - i))
self.display.apply()
sleep(1)
# Take pictures
filenames = [i for i in range(4)]
for x in range(4):
self.display.clear()
self.display.show_message("S M I L E !!!\n\n" + str(x+1) + " of 4")
self.display.apply()
tic = clock()
filenames[x] = self.camera.take_picture("/tmp/photobooth_%02d.jpg" % x)
toc = clock() - tic
if toc < 1.0:
sleep(1.0 - toc)
# Show 'Wait'
self.display.clear()
self.display.show_message("Please wait!\n\nProcessing...")
self.display.apply()
# Assemble them
outfile = self.assemble_pictures(filenames)
# Show pictures for 10 seconds
self.display.clear()
self.display.show_picture(outfile, size, (0,0))
self.display.apply()
sleep(self.display_time)
# Reenable lamp
self.gpio.set_output(self.lamp_channel, 1)
#################
### Functions ###
#################
def main():
photobooth = Photobooth(display_size, picture_basename, image_size, pose_time, display_time,
gpio_trigger_channel, gpio_shutdown_channel, gpio_lamp_channel)
photobooth.run()
photobooth.teardown()
return 0
if __name__ == "__main__":
exit(main())