Continued refactoring
This commit is contained in:
49
events.py
Normal file
49
events.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# Created by br@re-web.eu, 2015
|
||||||
|
|
||||||
|
try:
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
gpio_enabled = True
|
||||||
|
except ImportError:
|
||||||
|
gpio_enabled = False
|
||||||
|
|
||||||
|
|
||||||
|
class Event:
|
||||||
|
def __init__(self, type, value):
|
||||||
|
"""type 0: quit
|
||||||
|
1: keystroke
|
||||||
|
2: mouseclick
|
||||||
|
3: gpio
|
||||||
|
"""
|
||||||
|
self.type = type
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
class Rpi_GPIO:
|
||||||
|
def __init__(self, handle_function, input_channels = [], output_channels = []):
|
||||||
|
if gpio_enabled:
|
||||||
|
# Display initial information
|
||||||
|
print("Your Raspberry Pi is board revision " + str(GPIO.RPI_INFO['P1_REVISION']))
|
||||||
|
print("RPi.GPIO version is " + str(GPIO.VERSION))
|
||||||
|
|
||||||
|
# Choose BCM numbering system
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
# Setup the input channels
|
||||||
|
for input_channel in input_channels:
|
||||||
|
GPIO.setup(input_channel, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||||
|
GPIO.add_event_detect(input_channel, GPIO.RISING, callback=handle_function, bouncetime=200)
|
||||||
|
|
||||||
|
# Setup the output channels
|
||||||
|
for output_channel in output_channels:
|
||||||
|
GPIO.setup(output_channel, GPIO.OUT)
|
||||||
|
GPIO.output(output_channel, GPIO.LOW)
|
||||||
|
else:
|
||||||
|
print("Warning: RPi.GPIO could not be loaded. GPIO disabled.")
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
if gpio_enabled:
|
||||||
|
GPIO.cleanup()
|
||||||
|
|
||||||
|
def set_output(self, channel, value=0):
|
||||||
|
if gpio_enabled:
|
||||||
|
GPIO.output(channel, GPIO.HIGH if value==1 else GPIO.LOW)
|
||||||
28
gui.py
28
gui.py
@@ -13,6 +13,8 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
import pygame.event as EventModule
|
import pygame.event as EventModule
|
||||||
|
|
||||||
|
from events import Event
|
||||||
|
|
||||||
|
|
||||||
class TextRectException:
|
class TextRectException:
|
||||||
def __init__(self, message = None):
|
def __init__(self, message = None):
|
||||||
@@ -120,7 +122,7 @@ class GUI_PyGame:
|
|||||||
"""A GUI class using PyGame"""
|
"""A GUI class using PyGame"""
|
||||||
|
|
||||||
def __init__(self, name, size):
|
def __init__(self, name, size):
|
||||||
# Call init routines
|
# Call init routines
|
||||||
pygame.init()
|
pygame.init()
|
||||||
if hasattr(EventModule, 'init'):
|
if hasattr(EventModule, 'init'):
|
||||||
EventModule.init()
|
EventModule.init()
|
||||||
@@ -147,8 +149,8 @@ class GUI_PyGame:
|
|||||||
def get_size(self):
|
def get_size(self):
|
||||||
return self.size
|
return self.size
|
||||||
|
|
||||||
def trigger_event(self, event_id, event_channel):
|
def trigger_event(self, event_channel):
|
||||||
EventModule.post(EventModule.Event(event_id, channel=event_channel))
|
EventModule.post(EventModule.Event(pygame.USEREVENT, channel=event_channel))
|
||||||
|
|
||||||
def show_picture(self, filename, size=(0,0), offset=(0,0)):
|
def show_picture(self, filename, size=(0,0), offset=(0,0)):
|
||||||
# Use window size if none given
|
# Use window size if none given
|
||||||
@@ -176,6 +178,26 @@ class GUI_PyGame:
|
|||||||
text = render_textrect(msg, font, rect, color, bg, 1, 1)
|
text = render_textrect(msg, font, rect, color, bg, 1, 1)
|
||||||
self.screen.blit(text, rect.topleft)
|
self.screen.blit(text, rect.topleft)
|
||||||
|
|
||||||
|
def wait_for_event(self):
|
||||||
|
# Repeat until a relevant event happened
|
||||||
|
while True:
|
||||||
|
# Discard all input that happened before entering the loop
|
||||||
|
EventModule.get()
|
||||||
|
|
||||||
|
# Wait for event
|
||||||
|
event = EventModule.wait()
|
||||||
|
|
||||||
|
# Return Event-Object
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
return Event(0, 0)
|
||||||
|
elif event.type == pygame.KEYDOWN:
|
||||||
|
return Event(1, event.key)
|
||||||
|
elif event.type == pygame.MOUSEBUTTONUP:
|
||||||
|
return Event(2, (event.button, event.pos))
|
||||||
|
elif event.type >= pygame.USEREVENT:
|
||||||
|
return Event(3, event.channel)
|
||||||
|
|
||||||
|
|
||||||
def mainloop(self, filename, handle_keypress, handle_mousebutton, handle_gpio_event):
|
def mainloop(self, filename, handle_keypress, handle_mousebutton, handle_gpio_event):
|
||||||
while True:
|
while True:
|
||||||
# Ignore all input that happened before entering the loop
|
# Ignore all input that happened before entering the loop
|
||||||
|
|||||||
547
photobooth.py
547
photobooth.py
@@ -4,7 +4,6 @@
|
|||||||
from __future__ import division
|
from __future__ import division
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from sys import exit
|
from sys import exit
|
||||||
@@ -12,17 +11,12 @@ from time import sleep
|
|||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
import pygame
|
from gui import GUI_PyGame as GuiModule
|
||||||
try:
|
|
||||||
import pygame.fastevent as eventmodule
|
|
||||||
except ImportError:
|
|
||||||
import pygame.event as eventmodule
|
|
||||||
|
|
||||||
try:
|
from camera import Camera_gPhoto as CameraModule
|
||||||
import RPi.GPIO as GPIO
|
from camera import CameraException
|
||||||
gpio_enabled = True
|
|
||||||
except ImportError:
|
from events import Rpi_GPIO as GPIO
|
||||||
gpio_enabled = False
|
|
||||||
|
|
||||||
#####################
|
#####################
|
||||||
### Configuration ###
|
### Configuration ###
|
||||||
@@ -34,14 +28,8 @@ display_size = (1024, 600)
|
|||||||
# Maximum size of assembled image
|
# Maximum size of assembled image
|
||||||
image_size = (2352, 1568)
|
image_size = (2352, 1568)
|
||||||
|
|
||||||
# Idle image
|
|
||||||
image_idle = None
|
|
||||||
|
|
||||||
# Pose image
|
|
||||||
image_pose = None
|
|
||||||
|
|
||||||
# Image basename
|
# Image basename
|
||||||
image_basename = datetime.now().strftime("%Y-%m-%d/pic")
|
picture_basename = datetime.now().strftime("%Y-%m-%d/pic")
|
||||||
|
|
||||||
# GPIO channel of switch to shutdown the Pi
|
# GPIO channel of switch to shutdown the Pi
|
||||||
gpio_shutdown_channel = 24 # pin 18 in all Raspi-Versions
|
gpio_shutdown_channel = 24 # pin 18 in all Raspi-Versions
|
||||||
@@ -52,9 +40,6 @@ gpio_trigger_channel = 23 # pin 16 in all Raspi-Versions
|
|||||||
# GPIO output channel for (blinking) lamp
|
# GPIO output channel for (blinking) lamp
|
||||||
gpio_lamp_channel = 4 # pin 7 in all Raspi-Versions
|
gpio_lamp_channel = 4 # pin 7 in all Raspi-Versions
|
||||||
|
|
||||||
# PyGame event used to detect GPIO triggers
|
|
||||||
gpio_pygame_event = pygame.USEREVENT
|
|
||||||
|
|
||||||
# Waiting time in seconds for posing
|
# Waiting time in seconds for posing
|
||||||
pose_time = 5
|
pose_time = 5
|
||||||
|
|
||||||
@@ -65,7 +50,7 @@ display_time = 10
|
|||||||
### Classes ###
|
### Classes ###
|
||||||
###############
|
###############
|
||||||
|
|
||||||
class Images:
|
class PictureList:
|
||||||
"""Class to manage images and count them"""
|
"""Class to manage images and count them"""
|
||||||
def __init__(self, basename):
|
def __init__(self, basename):
|
||||||
# Set basename and suffix
|
# Set basename and suffix
|
||||||
@@ -99,393 +84,171 @@ class Images:
|
|||||||
self.counter += 1
|
self.counter += 1
|
||||||
return self.get(self.counter)
|
return self.get(self.counter)
|
||||||
|
|
||||||
class TextRectException:
|
|
||||||
def __init__(self, message = None):
|
|
||||||
self.message = message
|
|
||||||
def __str__(self):
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
def render_textrect(string, font, rect, text_color, background_color, justification=0, valign=0):
|
class Photobooth:
|
||||||
"""Returns a surface containing the passed text string, reformatted
|
def __init__(self, picture_basename, picture_size, trigger_channel, shutdown_channel, lamp_channel):
|
||||||
to fit within the given rect, word-wrapping as necessary. The text
|
self.display = GuiModule('Photobooth', display_size)
|
||||||
will be anti-aliased.
|
self.pictures = PictureList(picture_basename)
|
||||||
|
self.camera = CameraModule()
|
||||||
|
self.pic_size = picture_size
|
||||||
|
|
||||||
Source: http://www.pygame.org/pcr/text_rect/index.php
|
self.trigger_channel = trigger_channel
|
||||||
|
self.shutdown_channel = shutdown_channel
|
||||||
|
self.lamp_channel = lamp_channel
|
||||||
|
|
||||||
Takes the following arguments:
|
input_channels = [ trigger_channel, shutdown_channel ]
|
||||||
|
output_channels = [ lamp_channel ]
|
||||||
string - the text you wish to render. \n begins a new line.
|
self.gpio = GPIO(self.handle_gpio, input_channels, output_channels)
|
||||||
font - a Font object
|
|
||||||
rect - a rectstyle giving the size of the surface requested.
|
|
||||||
text_color - a three-byte tuple of the rgb value of the
|
|
||||||
text color. ex (0, 0, 0) = BLACK
|
|
||||||
background_color - a three-byte tuple of the rgb value of the surface.
|
|
||||||
justification - 0 (default) left-justified
|
|
||||||
1 horizontally centered
|
|
||||||
2 right-justified
|
|
||||||
valign - 0 (default) top aligned
|
|
||||||
1 vertically centered
|
|
||||||
2 bottom aligned
|
|
||||||
|
|
||||||
Returns the following values:
|
|
||||||
|
|
||||||
Success - a surface object with the text rendered onto it.
|
|
||||||
Failure - raises a TextRectException if the text won't fit onto the surface.
|
|
||||||
"""
|
|
||||||
|
|
||||||
final_lines = []
|
|
||||||
|
|
||||||
requested_lines = string.splitlines()
|
|
||||||
|
|
||||||
# Create a series of lines that will fit on the provided
|
|
||||||
# rectangle.
|
|
||||||
|
|
||||||
accumulated_height = 0
|
|
||||||
for requested_line in requested_lines:
|
|
||||||
if font.size(requested_line)[0] > rect.width:
|
|
||||||
words = requested_line.split(' ')
|
|
||||||
# if any of our words are too long to fit, return.
|
|
||||||
for word in words:
|
|
||||||
if font.size(word)[0] >= rect.width:
|
|
||||||
raise TextRectException, "The word " + word + " is too long to fit in the rect passed."
|
|
||||||
# Start a new line
|
|
||||||
accumulated_line = ""
|
|
||||||
for word in words:
|
|
||||||
test_line = accumulated_line + word + " "
|
|
||||||
# Build the line while the words fit.
|
|
||||||
if font.size(test_line)[0] < rect.width:
|
|
||||||
accumulated_line = test_line
|
|
||||||
else:
|
|
||||||
accumulated_height += font.size(test_line)[1]
|
|
||||||
final_lines.append(accumulated_line)
|
|
||||||
accumulated_line = word + " "
|
|
||||||
accumulated_height += font.size(accumulated_line)[1]
|
|
||||||
final_lines.append(accumulated_line)
|
|
||||||
else:
|
|
||||||
accumulated_height += font.size(requested_line)[1]
|
|
||||||
final_lines.append(requested_line)
|
|
||||||
|
|
||||||
# Check height of the text and align vertically
|
|
||||||
|
|
||||||
if accumulated_height >= rect.height:
|
|
||||||
raise TextRectException, "Once word-wrapped, the text string was too tall to fit in the rect."
|
|
||||||
|
|
||||||
if valign == 0:
|
|
||||||
voffset = 0
|
|
||||||
elif valign == 1:
|
|
||||||
voffset = int((rect.height - accumulated_height) / 2)
|
|
||||||
elif valign == 2:
|
|
||||||
voffset = rect.height - accumulated_height
|
|
||||||
else:
|
|
||||||
raise TextRectException, "Invalid valign argument: " + str(valign)
|
|
||||||
|
|
||||||
# Let's try to write the text out on the surface.
|
|
||||||
|
|
||||||
surface = pygame.Surface(rect.size)
|
|
||||||
surface.fill(background_color)
|
|
||||||
|
|
||||||
accumulated_height = 0
|
|
||||||
for line in final_lines:
|
|
||||||
if line != "":
|
|
||||||
tempsurface = font.render(line, 1, text_color)
|
|
||||||
if justification == 0:
|
|
||||||
surface.blit(tempsurface, (0, voffset + accumulated_height))
|
|
||||||
elif justification == 1:
|
|
||||||
surface.blit(tempsurface, ((rect.width - tempsurface.get_width()) / 2, voffset + accumulated_height))
|
|
||||||
elif justification == 2:
|
|
||||||
surface.blit(tempsurface, (rect.width - tempsurface.get_width(), voffset + accumulated_height))
|
|
||||||
else:
|
|
||||||
raise TextRectException, "Invalid justification argument: " + str(justification)
|
|
||||||
accumulated_height += font.size(line)[1]
|
|
||||||
|
|
||||||
return surface
|
|
||||||
|
|
||||||
class GUI_PyGame:
|
|
||||||
"""The GUI class using PyGame"""
|
|
||||||
def __init__(self, name, size):
|
|
||||||
pygame.init()
|
|
||||||
if hasattr(eventmodule, 'init'):
|
|
||||||
eventmodule.init()
|
|
||||||
# Window name
|
|
||||||
pygame.display.set_caption(name)
|
|
||||||
# Hide mouse cursor
|
|
||||||
pygame.mouse.set_cursor(*pygame.cursors.load_xbm('transparent.xbm','transparent.msk'))
|
|
||||||
# Store screen and size
|
|
||||||
self.size = size
|
|
||||||
self.screen = pygame.display.set_mode(self.size, pygame.FULLSCREEN)
|
|
||||||
# Clear screen
|
|
||||||
self.clear()
|
|
||||||
|
|
||||||
def clear(self, color=(0,0,0)):
|
|
||||||
self.screen.fill(color)
|
|
||||||
|
|
||||||
def apply(self):
|
|
||||||
pygame.display.update()
|
|
||||||
|
|
||||||
def get_size(self):
|
|
||||||
return self.size
|
|
||||||
|
|
||||||
def trigger_event(self, event_id, event_channel):
|
|
||||||
eventmodule.post(eventmodule.Event(event_id, channel=event_channel))
|
|
||||||
|
|
||||||
def show_picture(self, filename, size=(0,0), offset=(0,0)):
|
|
||||||
# Use window size if none given
|
|
||||||
if size == (0,0):
|
|
||||||
size = self.size
|
|
||||||
# Load image from file
|
|
||||||
image = pygame.image.load(filename)
|
|
||||||
# Extract image size and determine scaling
|
|
||||||
image_size = image.get_rect().size
|
|
||||||
image_scale = min([min(a,b)/b for a,b in zip(size, image_size)])
|
|
||||||
# New image size
|
|
||||||
new_size = [int(a*image_scale) for a in image_size]
|
|
||||||
# Update offset
|
|
||||||
offset = tuple(a+int((b-c)/2) for a,b,c in zip(offset, size, new_size))
|
|
||||||
# Apply scaling and display picture
|
|
||||||
image = pygame.transform.scale(image, new_size).convert()
|
|
||||||
self.screen.blit(image, offset)
|
|
||||||
|
|
||||||
def show_message(self, msg, color=(245,245,245), bg=(0,0,0)):
|
|
||||||
# Choose font
|
|
||||||
font = pygame.font.Font(None, 144)
|
|
||||||
# Create rectangle for text
|
|
||||||
rect = pygame.Rect((0, 0, self.size[0], self.size[1]))
|
|
||||||
# Render text
|
|
||||||
text = render_textrect(msg, font, rect, color, bg, 1, 1)
|
|
||||||
self.screen.blit(text, rect.topleft)
|
|
||||||
|
|
||||||
def mainloop(self, filename):
|
|
||||||
while True:
|
|
||||||
# Ignore all input that happened before entering the loop
|
|
||||||
eventmodule.get()
|
|
||||||
# Clear display
|
|
||||||
self.clear()
|
|
||||||
# Show idle-picture and message
|
|
||||||
if filename != None:
|
|
||||||
self.show_picture(filename)
|
|
||||||
self.show_message("Hit the button!")
|
|
||||||
# Render everything
|
|
||||||
self.apply()
|
|
||||||
# Wait for event
|
|
||||||
event = eventmodule.wait()
|
|
||||||
# Handle the event
|
|
||||||
if event.type == pygame.QUIT: return
|
|
||||||
elif event.type == pygame.KEYDOWN: handle_keypress(event.key)
|
|
||||||
elif event.type == pygame.MOUSEBUTTONUP: handle_mousebutton(event.button, event.pos)
|
|
||||||
elif event.type == gpio_pygame_event: handle_gpio_event(event.channel)
|
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
pygame.quit()
|
self.display.teardown()
|
||||||
|
self.gpio.teardown()
|
||||||
|
exit(0)
|
||||||
|
|
||||||
class CameraException(Exception):
|
def run(self):
|
||||||
"""Custom exception class to handle gPhoto errors"""
|
while True:
|
||||||
pass
|
try:
|
||||||
|
# Enable lamp
|
||||||
|
self.gpio.set_output(self.lamp_channel, 1)
|
||||||
|
|
||||||
class Camera:
|
while True:
|
||||||
"""Camera class providing functionality to take pictures"""
|
# Display default message
|
||||||
def __init__(self):
|
self.display.clear()
|
||||||
# Print the abilities of the connected camera
|
self.display.show_message("Hit the button!")
|
||||||
try:
|
self.display.apply()
|
||||||
print(self.call_gphoto("-a", "/dev/null"))
|
# Wait for an event and handle it
|
||||||
except CameraException as e:
|
event = self.display.wait_for_event()
|
||||||
handle_exception(e.message)
|
self.handle_event(event)
|
||||||
|
|
||||||
def call_gphoto(self, action, filename):
|
except CameraException as e:
|
||||||
# Try to run the command
|
self.handle_exception(e.message)
|
||||||
try:
|
|
||||||
cmd = "gphoto2 --force-overwrite --quiet " + action + " --filename " + filename
|
def handle_gpio(self, channel):
|
||||||
output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
|
if channel in [ self.trigger_channel, self.shutdown_channel ]:
|
||||||
if "ERROR" in output:
|
self.display.trigger_event(channel)
|
||||||
raise subprocess.CalledProcessError(returncode=0, cmd=cmd, output=output)
|
|
||||||
except subprocess.CalledProcessError as e:
|
def handle_event(self, event):
|
||||||
if "Canon EOS Capture failed: 2019" in e.output:
|
if event.type == 0:
|
||||||
raise CameraException("Can't focus! Move and try again!")
|
self.teardown()
|
||||||
elif "No camera found" in e.output:
|
elif event.type == 1:
|
||||||
raise CameraException("No (supported) camera detected!")
|
self.handle_keypress(event.value)
|
||||||
else:
|
elif event.type == 2:
|
||||||
raise CameraException("Unknown error!\n" + '\n'.join(e.output.split('\n')[1:3]))
|
self.handle_mousebutton(event.value[0], event.value[1])
|
||||||
return output
|
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 == gpio_trigger_channel:
|
||||||
|
self.take_picture()
|
||||||
|
elif channel == gpio_shutdown_channel:
|
||||||
|
self.display.clear()
|
||||||
|
|
||||||
|
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, output_filename):
|
||||||
|
"""Assembles four pictures into a 2x2 grid"""
|
||||||
|
|
||||||
|
# Thumbnail size of pictures
|
||||||
|
size = (int(image_size[0]/2),int(image_size[1]/2))
|
||||||
|
|
||||||
|
# Create output image
|
||||||
|
output_image = Image.new('RGB', image_size)
|
||||||
|
|
||||||
|
# Load images and resize them
|
||||||
|
for i in range(2):
|
||||||
|
for j in range(2):
|
||||||
|
k = i * 2 + j
|
||||||
|
img = Image.open(input_filenames[k])
|
||||||
|
img.thumbnail(size)
|
||||||
|
offset = (j * size[0], i * size[1])
|
||||||
|
output_image.paste(img, offset)
|
||||||
|
|
||||||
|
output_image.save(output_filename, "JPEG")
|
||||||
|
|
||||||
|
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(pose_time - 3)
|
||||||
|
|
||||||
|
# Countdown
|
||||||
|
for i in range(3):
|
||||||
|
self.display.clear()
|
||||||
|
self.display.show_message(str(3 - i))
|
||||||
|
self.display.apply()
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
# Show 'Cheese'
|
||||||
|
self.display.clear()
|
||||||
|
self.display.show_message("S M I L E !")
|
||||||
|
self.display.apply()
|
||||||
|
|
||||||
|
# Extract display and image sizes
|
||||||
|
size = self.display.get_size()
|
||||||
|
outsize = (int(size[0]/2), int(size[1]/2))
|
||||||
|
|
||||||
|
# Take pictures
|
||||||
|
filenames = [i for i in range(4)]
|
||||||
|
for x in range(4):
|
||||||
|
filenames[x] = self.camera.take_picture("/tmp/photobooth_%02d.jpg" % x)
|
||||||
|
|
||||||
|
# Show 'Wait'
|
||||||
|
self.display.clear()
|
||||||
|
self.display.show_message("Please wait!\n\nProcessing...")
|
||||||
|
self.display.apply()
|
||||||
|
|
||||||
|
# Assemble them
|
||||||
|
outfile = self.pictures.get_next()
|
||||||
|
self.assemble_pictures(filenames, outfile)
|
||||||
|
|
||||||
|
# Show pictures for 10 seconds
|
||||||
|
self.display.clear()
|
||||||
|
self.display.show_picture(outfile, size, (0,0))
|
||||||
|
self.display.apply()
|
||||||
|
sleep(display_time)
|
||||||
|
|
||||||
|
# Reenable lamp
|
||||||
|
self.gpio.set_output(self.lamp_channel, 1)
|
||||||
|
|
||||||
def preview(self, filename="/tmp/preview.jpg"):
|
|
||||||
while not self.call_gphoto("--capture-preview", filename):
|
|
||||||
continue
|
|
||||||
return filename
|
|
||||||
|
|
||||||
def take_picture(self, filename="/tmp/picture.jpg"):
|
|
||||||
self.call_gphoto("--capture-image-and-download", filename)
|
|
||||||
return filename
|
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
### Functions ###
|
### Functions ###
|
||||||
#################
|
#################
|
||||||
|
|
||||||
def assemble_pictures(input_filenames, output_filename):
|
|
||||||
"""Assembles four pictures into a 2x2 grid"""
|
|
||||||
|
|
||||||
# Thumbnail size of pictures
|
|
||||||
size = (int(image_size[0]/2),int(image_size[1]/2))
|
|
||||||
|
|
||||||
# Create output image
|
|
||||||
output_image = Image.new('RGB', image_size)
|
|
||||||
|
|
||||||
# Load images and resize them
|
|
||||||
for i in range(2):
|
|
||||||
for j in range(2):
|
|
||||||
k = i * 2 + j
|
|
||||||
img = Image.open(input_filenames[k])
|
|
||||||
img.thumbnail(size)
|
|
||||||
offset = (j * size[0], i * size[1])
|
|
||||||
output_image.paste(img, offset)
|
|
||||||
|
|
||||||
output_image.save(output_filename, "JPEG")
|
|
||||||
|
|
||||||
def take_picture():
|
|
||||||
"""Implements the picture taking routine"""
|
|
||||||
# Disable the lamp
|
|
||||||
set_lamp(0)
|
|
||||||
|
|
||||||
# Show pose message
|
|
||||||
display.clear()
|
|
||||||
if image_pose != None:
|
|
||||||
display.show_picture(image_pose)
|
|
||||||
display.show_message("POSE!\n\nTaking four pictures...");
|
|
||||||
display.apply()
|
|
||||||
sleep(pose_time - 3)
|
|
||||||
|
|
||||||
# Countdown
|
|
||||||
for i in range(3):
|
|
||||||
display.clear()
|
|
||||||
display.show_message(str(3 - i))
|
|
||||||
display.apply()
|
|
||||||
sleep(1)
|
|
||||||
|
|
||||||
# Show 'Cheese'
|
|
||||||
display.clear()
|
|
||||||
display.show_message("S M I L E !")
|
|
||||||
display.apply()
|
|
||||||
|
|
||||||
# Extract display and image sizes
|
|
||||||
size = display.get_size()
|
|
||||||
outsize = (int(size[0]/2), int(size[1]/2))
|
|
||||||
|
|
||||||
# Take pictures
|
|
||||||
filenames = [i for i in range(4)]
|
|
||||||
for x in range(4):
|
|
||||||
filenames[x] = camera.take_picture("/tmp/photobooth_%02d.jpg" % x)
|
|
||||||
|
|
||||||
# Show 'Wait'
|
|
||||||
display.clear()
|
|
||||||
display.show_message("Please wait!\n\nProcessing...")
|
|
||||||
display.apply()
|
|
||||||
|
|
||||||
# Assemble them
|
|
||||||
outfile = images.get_next()
|
|
||||||
assemble_pictures(filenames, outfile)
|
|
||||||
|
|
||||||
# Show pictures for 10 seconds
|
|
||||||
display.clear()
|
|
||||||
display.show_picture(outfile, size, (0,0))
|
|
||||||
display.apply()
|
|
||||||
sleep(display_time)
|
|
||||||
|
|
||||||
# Reenable lamp
|
|
||||||
set_lamp(1)
|
|
||||||
|
|
||||||
def handle_keypress(key):
|
|
||||||
"""Implements the actions for the different keypress events"""
|
|
||||||
|
|
||||||
# Exit the application
|
|
||||||
if key == ord('q'):
|
|
||||||
teardown()
|
|
||||||
|
|
||||||
# Take pictures
|
|
||||||
elif key == ord('c'):
|
|
||||||
take_picture()
|
|
||||||
|
|
||||||
def handle_mousebutton(key, pos):
|
|
||||||
"""Implements the actions for the different mousebutton events"""
|
|
||||||
# Take a picture
|
|
||||||
if key == 1:
|
|
||||||
take_picture()
|
|
||||||
|
|
||||||
def handle_gpio_event(channel):
|
|
||||||
"""Implements the actions taken for a GPIO event"""
|
|
||||||
|
|
||||||
if channel == gpio_trigger_channel:
|
|
||||||
take_picture()
|
|
||||||
|
|
||||||
elif channel == gpio_shutdown_channel:
|
|
||||||
display.clear()
|
|
||||||
print("Shutting down!")
|
|
||||||
display.show_message("Shutting down!")
|
|
||||||
display.apply()
|
|
||||||
sleep(1)
|
|
||||||
teardown()
|
|
||||||
|
|
||||||
def handle_exception(msg):
|
|
||||||
"""Displays an error message and returns"""
|
|
||||||
display.clear()
|
|
||||||
print("Error: " + msg)
|
|
||||||
display.show_message("ERROR:\n\n" + msg)
|
|
||||||
display.apply()
|
|
||||||
sleep(3)
|
|
||||||
|
|
||||||
def setup_gpio():
|
|
||||||
"""Enables GPIO in- and output and registers event handles"""
|
|
||||||
if gpio_enabled:
|
|
||||||
# Display initial information
|
|
||||||
print("Your Raspberry Pi is board revision " + str(GPIO.RPI_INFO['P1_REVISION']))
|
|
||||||
print("RPi.GPIO version is " + str(GPIO.VERSION))
|
|
||||||
|
|
||||||
# Choose BCM numbering system
|
|
||||||
GPIO.setmode(GPIO.BCM)
|
|
||||||
|
|
||||||
# Setup the trigger channel as input and listen for events
|
|
||||||
GPIO.setup(gpio_trigger_channel, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
|
||||||
GPIO.setup(gpio_shutdown_channel, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
|
||||||
GPIO.add_event_detect(gpio_trigger_channel, GPIO.RISING, callback=handle_gpio, bouncetime=200)
|
|
||||||
GPIO.add_event_detect(gpio_shutdown_channel, GPIO.RISING, callback=handle_gpio, bouncetime=200)
|
|
||||||
|
|
||||||
# Setup the lamp channel as output
|
|
||||||
GPIO.setup(gpio_lamp_channel, GPIO.OUT)
|
|
||||||
GPIO.output(gpio_lamp_channel, GPIO.LOW)
|
|
||||||
else:
|
|
||||||
print("Warning: RPi.GPIO could not be loaded. GPIO disabled.")
|
|
||||||
|
|
||||||
def handle_gpio(channel):
|
|
||||||
"""Interrupt handler for GPIO events"""
|
|
||||||
display.trigger_event(gpio_pygame_event, channel)
|
|
||||||
|
|
||||||
def set_lamp(status=0):
|
|
||||||
"""Switch the lamp on"""
|
|
||||||
if gpio_enabled:
|
|
||||||
GPIO.output(gpio_lamp_channel, GPIO.HIGH if status==1 else GPIO.LOW)
|
|
||||||
|
|
||||||
def teardown(exit_code=0):
|
|
||||||
display.teardown()
|
|
||||||
if gpio_enabled:
|
|
||||||
GPIO.cleanup()
|
|
||||||
exit(exit_code)
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
setup_gpio()
|
photobooth = Photobooth(picture_basename, image_size, gpio_trigger_channel, gpio_shutdown_channel, gpio_lamp_channel)
|
||||||
while True:
|
photobooth.run()
|
||||||
try:
|
return photobooth.teardown()
|
||||||
set_lamp(1)
|
|
||||||
display.mainloop(image_idle)
|
|
||||||
except CameraException as e:
|
|
||||||
handle_exception(e.message)
|
|
||||||
teardown()
|
|
||||||
return 0
|
|
||||||
|
|
||||||
########################
|
|
||||||
### Global variables ###
|
|
||||||
########################
|
|
||||||
|
|
||||||
display = GUI_PyGame('Photobooth', display_size)
|
|
||||||
images = Images(image_basename)
|
|
||||||
camera = Camera()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
exit(main())
|
exit(main())
|
||||||
Reference in New Issue
Block a user