diff --git a/README.md b/README.md index dbdccf1..f8892bc 100644 --- a/README.md +++ b/README.md @@ -114,13 +114,15 @@ A brief description on how to set-up a Raspberry Pi to use this photobooth softw ``` and run `photobooth.py` -8. Optionally make the software run automatically on startup. To do that, you must simply add a corresponding line in the autostart file of LXDE, which can be found at `~/.config/lxsession/LXDE-pi/autostart`. Assuming you cloned the Photobooth repository into `/home/pi/photobooth`, add the following line into the autostart-file: +8. Optional but highly recommended, as it improves performance significantly: Download the gPhoto2 Python-bindings [Piggyphoto](https://github.com/alexdu/piggyphoto) and put the folder `piggyphoto` into the Photobooth-directory. + +9. Optionally make the software run automatically on startup. To do that, you must simply add a corresponding line in the autostart file of LXDE, which can be found at `~/.config/lxsession/LXDE-pi/autostart`. Assuming you cloned the Photobooth repository into `/home/pi/photobooth`, add the following line into the autostart-file: ``` lxterminal -e "/home/pi/photobooth/photobooth.sh set-time" ``` For this to work you must install `gnome-control-center` by running `sudo apt-get install gnome-control-center` (Unfortunately, this brings along a lot of dependencies - however, I haven't found any lightweight alternative that would allow to simply set date and time using the touch screen). -9. Alternatively, you can also add a Desktop shortcut. Create a file `/home/pi/Desktop/Photobooth.desktop` and enter the following: +10. Alternatively, you can also add a Desktop shortcut. Create a file `/home/pi/Desktop/Photobooth.desktop` and enter the following: ``` [Desktop Entry] Encoding=UTF-8 diff --git a/camera.py b/camera.py index a71d204..c448570 100644 --- a/camera.py +++ b/camera.py @@ -6,9 +6,17 @@ import subprocess try: import cv2 as cv cv_enabled = True + print('OpenCV available') except ImportError: cv_enabled = False +try: + import piggyphoto + piggyphoto_enabled = True + print('Piggyphoto available') +except ImportError: + piggyphoto_enabled = False + class CameraException(Exception): """Custom exception class to handle camera class errors""" def __init__(self, message, recoverable=False): @@ -23,6 +31,12 @@ class Camera_cv: self.cap.set(3, picture_size[0]) self.cap.set(4, picture_size[1]) + def has_preview(self): + return True + + def take_preview(self, filename="/tmp/preview.jpg"): + self.take_picture(filename) + def take_picture(self, filename="/tmp/picture.jpg"): if cv_enabled: r, frame = self.cap.read() @@ -39,9 +53,15 @@ class Camera_gPhoto: self.picture_size = picture_size # Print the capabilities of the connected camera try: - print(self.call_gphoto("-a", "/dev/null")) - except: - print("Warning: Can't list camera capabilities. Do you have gPhoto2 installed?") + if piggyphoto_enabled: + self.cap = piggyphoto.camera() + print(self.cap.abilities) + else: + print(self.call_gphoto("-a", "/dev/null")) + except CameraException as e: + print('Warning: Listing camera capabilities failed (' + e.message + ')') + except piggyphoto.libgphoto2error as e: + print('Warning: Listing camera capabilities failed (' + e.message + ')') def call_gphoto(self, action, filename): # Try to run the command @@ -61,6 +81,18 @@ class Camera_gPhoto: raise CameraException("Unknown error!\n" + '\n'.join(e.output.split('\n')[1:3]), False) return output + def has_preview(self): + return piggyphoto_enabled + + def take_preview(self, filename="/tmp/preview.jpg"): + if piggyphoto_enabled: + self.cap.capture_preview(filename) + else: + raise CameraException("No preview supported!") + def take_picture(self, filename="/tmp/picture.jpg"): - self.call_gphoto("--capture-image-and-download", filename) + if piggyphoto_enabled: + self.cap.capture_image(filename) + else: + self.call_gphoto("--capture-image-and-download", filename) return filename diff --git a/gui.py b/gui.py index cb518a3..2bdf277 100644 --- a/gui.py +++ b/gui.py @@ -53,7 +53,7 @@ class GUI_PyGame: def trigger_event(self, 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), flip=False): # Use window size if none given if size == (0,0): size = self.size @@ -74,6 +74,8 @@ class GUI_PyGame: # Create surface and blit the image to it surface = pygame.Surface(new_size) surface.blit(image, (0,0)) + if flip: + surface = pygame.transform.flip(surface, True, False) self.surface_list.append((surface, offset)) def show_message(self, msg, color=(245,245,245), bg=(0,0,0), transparency=True): diff --git a/photobooth.py b/photobooth.py index d91b6cf..b74baec 100755 --- a/photobooth.py +++ b/photobooth.py @@ -10,6 +10,7 @@ from time import sleep, clock from PIL import Image from gui import GUI_PyGame as GuiModule +#from camera import CameraException, Camera_cv as CameraModule from camera import CameraException, Camera_gPhoto as CameraModule from events import Rpi_GPIO as GPIO @@ -152,7 +153,8 @@ class Photobooth: # Do not catch KeyboardInterrupt and SystemExit except (KeyboardInterrupt, SystemExit): raise - except: + except Exception as e: + print('SERIOUS ERROR: ' + repr(e)) self.handle_exception("SERIOUS ERROR!") def handle_gpio(self, channel): @@ -277,6 +279,23 @@ class Photobooth: output_image.save(output_filename, "JPEG") return output_filename + def show_counter(self, seconds): + if self.camera.has_preview(): + tic, toc = clock(), 0 + while toc < self.pose_time: + self.display.clear() + self.camera.take_preview("/tmp/photobooth_preview.jpg") + self.display.show_picture("/tmp/photobooth_preview.jpg", flip=True) + self.display.show_message(str(seconds - int(clock() - tic))) + self.display.apply() + toc = clock() - tic + else: + for i in range(seconds): + self.display.clear() + self.display.show_message(str(seconds - i)) + self.display.apply() + sleep(1) + def take_picture(self): """Implements the picture taking routine""" # Disable lamp @@ -286,22 +305,18 @@ class Photobooth: self.display.clear() self.display.show_message("POSE!\n\nTaking four pictures..."); self.display.apply() - sleep(self.pose_time - 3) + sleep(2) # 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): + # Countdown + self.show_counter(self.pose_time) + # Try each picture up to 3 times remaining_attempts = 3 while remaining_attempts > 0: @@ -329,7 +344,7 @@ class Photobooth: else: raise e - # Sleep for a little bit if necessary + # Measure used time and sleep a second if too fast toc = clock() - tic if toc < 1.0: sleep(1.0 - toc) @@ -366,4 +381,4 @@ def main(): return 0 if __name__ == "__main__": - exit(main()) \ No newline at end of file + exit(main())