class Photobooth: """The main class. It contains all the logic for the photobooth. """ def __init__(self, display_size, display_rotate, picture_basename, picture_size, pose_time, display_time, trigger_channel, shutdown_channel, lamp_channel, idle_slideshow, slideshow_display_time): self.display = GuiModule('Photobooth', display_size) if (display_size == (0, 0)): # Detect actual resolution display_size = self.display.get_size() self.display_rotate = display_rotate if (display_rotate): self.display.set_rotate(True) self.pictures = PictureList(picture_basename) self.camera = CameraModule((picture_size[0] / 2, picture_size[1] / 2), camera_rotate=camera_rotate) self.camera_rotate = camera_rotate 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 self.idle_slideshow = idle_slideshow if self.idle_slideshow: self.slideshow_display_time = slideshow_display_time self.slideshow = Slideshow( display_size, display_time, os.path.dirname(os.path.realpath(picture_basename))) if (display_rotate): self.slideshow.display.set_rotate(True) input_channels = [trigger_channel, shutdown_channel] output_channels = [lamp_channel] self.gpio = GPIO(self.handle_gpio, input_channels, output_channels) self.printer_module = PrinterModule() try: pygame.mixer.init(buffer=1024) self.shutter = pygame.mixer.Sound(shutter_sound) self.bip1 = pygame.mixer.Sound(bip1_sound) self.bip2 = pygame.mixer.Sound(bip2_sound) self.bip2.play() except pygame.error: self.shutter = None pass def teardown(self): self.display.msg("Shutting down...") self.gpio.set_output(self.lamp_channel, 0) sleep(0.5) self.display.teardown() self.gpio.teardown() self.remove_tempfiles() exit(0) def remove_tempfiles(self): for filename in glob(tmp_dir + "photobooth_*.jpg"): try: os.remove(filename) except OSError: pass def _run_plain(self): while True: self.camera.set_idle() # Display default message self.display.msg("Hit the button!") # Wait for an event and handle it event = self.display.wait_for_event() self.handle_event(event) def _run_slideshow(self): while True: self.camera.set_idle() self.slideshow.display_next("Hit the button!") tic = time() while time() - tic < self.slideshow_display_time: self.check_and_handle_events() def run(self): while True: try: # Enable lamp self.gpio.set_output(self.lamp_channel, 1) # Select idle screen type if self.idle_slideshow: self._run_slideshow() else: self._run_plain() # Catch exceptions and display message except CameraException as e: self.handle_exception(e.message) # Do not catch KeyboardInterrupt and SystemExit except (KeyboardInterrupt, SystemExit): raise except Exception as e: import sys print('SERIOUS ERROR' + repr(e)) sys.excepthook(*sys.exc_info()) self.handle_exception("SERIOUS ERROR!\n(see log file)") def check_and_handle_events(self): r, e = self.display.check_for_event() while r: self.handle_event(e) r, e = self.display.check_for_event() def clear_event_queue(self): r, e = self.display.check_for_event() while r: r, e = self.display.check_for_event() 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() elif key == ord('f'): self.display.toggle_fullscreen() elif key == ord('i'): # Re-initialize the camera for debugging self.camera.reinit() elif key == ord('p'): self.toggle_auto_print() elif key == ord('r'): self.toggle_rotate() elif key == ord('1'): # Just for debugging self.show_preview_fps_1(5) elif key == ord('2'): # Just for debugging self.show_preview_fps_2(5) elif key == ord('3'): # Just for debugging self.show_preview_fps_3(5) def toggle_auto_print(self): "Toggle auto print and show an error message if printing isn't possible." if self.printer_module.can_print(): global auto_print auto_print = not auto_print self.display.msg("Autoprinting %s" % ("enabled" if auto_print else "disabled")) else: self.display.msg("Printing not configured\n(see log file)") def toggle_rotate(self): "Toggle rotating the display and camera." self.toggle_display_rotate() self.toggle_camera_rotate() self.display.msg("Display and camera rotated") def toggle_display_rotate(self): "Toggle rotating the display 90 degrees counter clockwise." self.display_rotate = (not self.display_rotate) self.display.set_rotate(self.display_rotate) self.slideshow.display.set_rotate(self.display_rotate) def toggle_camera_rotate(self): "Toggle rotating the camera 90 degrees counter clockwise." self.camera_rotate = (not self.camera_rotate) self.camera.set_rotate(self.camera_rotate) 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""" print("Error: " + msg) try: self.display.msg("ERROR:\n\n" + msg) except GuiException: self.display.msg("ERROR") sleep(3) def assemble_pictures(self, input_filenames): """Assembles four pictures into a 2x2 grid of thumbnails. The total size (WxH) is assigned in the global variable assembled_size at the top of this file. (E.g., 2352x1568) The outer border (a) is 2% of W The inner border (b) is 1% of W Note that if the camera is on its side, H and W will be swapped to create a portrait, rather than landscape, montage. Thumbnail sizes are calculated like so: 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 [Note that extra padding will be added on the sides if the aspect ratio of the camera images do not match the aspect ratio of the final assembled image.] """ # If the display is in portrait orientation, # we should create an assembled image that fits it. if self.display.get_rotate(): (H, W) = self.pic_size else: (W, H) = self.pic_size # Thumbnail size of pictures outer_border = int(2 * max(W, H) / 100) # 2% of long edge inner_border = int(1 * max(W, H) / 100) # 1% of long edge thumb_box = (int(W / 2), int(H / 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', (W, H), (255, 255, 255)) # Image 0 img = Image.open(input_filenames[0]) img = img.resize(maxpect(img.size, thumb_size), Image.ANTIALIAS) 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 = img.resize(maxpect(img.size, thumb_size), Image.ANTIALIAS) 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 = img.resize(maxpect(img.size, thumb_size), Image.ANTIALIAS) 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 = img.resize(maxpect(img.size, thumb_size), Image.ANTIALIAS) 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 show_preview(self, message=""): """If camera allows previews, take a photo and show it so people can pose before the shot. For speed, previews are decimated to fit within the screen instead of being scaled. For even more speed, the previews are blitted directly to a subsurface of the display. (Converting to a pygame Surface is slower). """ self.display.clear() if self.camera.has_preview(): f = self.camera.get_preview_array(self.display.get_size()) self.display.blit_array(f) self.display.show_message(message) self.display.apply() def show_counter(self, seconds): """Loop over showing the preview (if possible), with a count down""" tic = time() toc = time() - tic old_t = None while toc < seconds: t = seconds - int(toc) if t != old_t and self.bip1: self.bip1.play() old_t = t self.show_preview(str(t)) # Limit progress to 1 "second" per preview (e.g., too slow on Raspi 1) toc = min(toc + 1, time() - tic) def show_preview_fps_1(self, seconds): """XXX Debugging code for benchmarking XXX This is the original show_countdown preview code. (~5fps) """ import cv2, pygame, numpy tic = time() toc = 0 frames = 0 while toc < seconds: frames = frames + 1 self.display.clear() if self.camera.has_preview(): self.camera.take_preview(tmp_dir + "photobooth_preview.jpg") self.display.show_picture(tmp_dir + "photobooth_preview.jpg", flip=True) self.display.show_message(str(seconds - int(toc))) self.display.apply() toc = time() - tic self.display.msg("FPS: %d/%.2f = %.2f" % (frames, toc, float(frames) / toc)) print("Method 1 FPS: %d/%.2f = %.2f" % (frames, toc, float(frames) / toc)) sleep(3) def show_preview_fps_2(self, seconds): """XXX Debugging code for benchmarking XXX As a test, I'm trying a direct conversion from OpenCV to a PyGame Surface in memory and it's much faster than the original code, but still slower than subsurface blitting. (~10fps) """ import cv2, pygame, numpy tic = time() toc = 0 frames = 0 while toc < seconds: frames = frames + 1 self.display.clear() # Capture a preview image from the camera as a pygame surface. s = self.camera.get_preview_pygame_surface() (w, h) = s.get_size() (dw, dh) = self.display.get_size() # Figure out maximum proportional scaling size = (dw, dh) image_size = (w, h) offset = (0, 0) # New image size new_size = maxpect(image_size, size) # Update offset offset = tuple(a + int((b - c) / 2) for a, b, c in zip(offset, size, new_size)) # Apply scaling s = pygame.transform.scale(s, new_size).convert() # Display it using undocumented interface to GUI_Pygame self.display.surface_list.append((s, offset)) self.display.show_message(str(seconds - int(toc))) self.display.apply() toc = time() - tic self.display.msg("FPS: %d/%.2f = %.2f" % (frames, toc, float(frames) / toc)) print("Method 2 FPS: %d/%.2f = %.2f" % (frames, toc, float(frames) / toc)) sleep(3) def show_preview_fps_3(self, seconds): """XXX Debugging code for benchmarking XXX This is the fastest method, which decimates the array and blits it directly to a subsurface of the display. (~14fps) """ import cv2, pygame, numpy tic = time() toc = 0 frames = 0 while toc < seconds: frames = frames + 1 self.display.clear() # Grab a preview, decimated to fit within the screen size f = self.camera.get_preview_array(self.display.get_size()) # Blit it to the center of the screen self.display.blit_array(f) self.display.show_message(str(seconds - int(toc))) self.display.apply() toc = time() - tic self.display.msg("FPS: %d/%.2f = %.2f" % (frames, toc, float(frames) / toc)) print "Method 3 FPS: %d/%.2f = %.2f" % (frames, toc, float(frames) / toc) sleep(3) def show_pose(self, seconds, message=""): """Loop over showing the preview (if possible), with a static message. Note that this is *necessary* for OpenCV webcams as V4L will ramp the brightness level only after a certain number of frames have been taken. """ tic = time() toc = time() - tic while toc < seconds: self.show_preview(message) # Limit progress to 1 "second" per preview (e.g., too slow on Raspi 1) toc = min(toc + 1, time() - tic) def take_picture(self): """Implements the picture taking routine""" # Disable lamp self.gpio.set_output(self.lamp_channel, 0) # Show pose message self.show_pose(2, "POSE!\n\nTaking 4 pictures ...") # 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): # Countdown self.show_counter(self.pose_time) # Try each picture up to 3 times remaining_attempts = 3 while remaining_attempts > 0: remaining_attempts = remaining_attempts - 1 self.display.clear((255, 230, 200)) self.display.show_message("S M I L E !!!\n\n" + str(x + 1) + " of 4") self.display.apply() tic = time() try: filenames[x] = self.camera.take_picture( tmp_dir + "photobooth_%02d.jpg" % x) remaining_attempts = 0 if self.shutter: self.shutter.play() except CameraException as e: # On recoverable errors: display message and retry if e.recoverable: if remaining_attempts > 0: self.display.msg(e.message) sleep(5) else: raise CameraException( "Giving up! Please start over!", False) else: raise e # Measure used time and sleep a second if too fast toc = time() - tic if toc < 1.0: sleep(1.0 - toc) # Show 'Wait' self.display.msg("Please wait!\n\nWorking\n...") # Assemble them outfile = self.assemble_pictures(filenames) if self.printer_module.can_print(): # Show picture for 10 seconds and then send it to the printer. # If auto_print is True, hitting the button cancels the print. # If auto_print is False, hitting the button sends the print tic = time() t = int(self.display_time - (time() - tic)) old_t = self.display_time + 1 button_pressed = False # Clear event queue (in case they hit the button twice accidentally) self.clear_event_queue() while t > 0: if t != old_t: self.display.clear() self.display.show_picture(outfile, size, (0, 0)) self.display.show_message( "%s%d" % ("Printing in " if auto_print else "Print photo?\n", t)) self.display.apply() old_t = t # Flash the lamp so they'll know they can hit the button self.gpio.set_output(self.lamp_channel, int(time() * 2) % 2) # Watch for button, gpio, mouse press to cancel/enable printing r, e = self.display.check_for_event() if r: # Caught a button press. self.display.clear() self.display.show_picture(outfile, size, (0, 0)) self.display.show_message( "Printing%s" % (" cancelled" if auto_print else "")) self.display.apply() self.gpio.set_output(self.lamp_channel, 0) sleep(1) # Discard extra events (e.g., they hit the button a bunch) self.clear_event_queue() button_pressed = True break t = int(self.display_time - (time() - tic)) # Either button pressed or countdown timed out self.gpio.set_output(self.lamp_channel, 0) if auto_print ^ button_pressed: self.display.msg("Printing") self.printer_module.enqueue(outfile) else: # No printer available, so just show montage 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)
class Photobooth: """The main class. It contains all the logic for the photobooth. """ def __init__(self, display_size, display_rotate, picture_size, pose_time, display_time, idle_slideshow, slideshow_display_time): self.display = GuiModule('Photobooth', display_size) if display_size == (0, 0): # Detect actual resolution display_size = self.display.get_size() self.display_rotate = display_rotate if display_rotate: self.display.set_rotate(True) # Image basename picture_basename = datetime.now().strftime("%Y-%m-%d/pic") assembled_picture_basename = datetime.now().strftime( "%Y-%m-%d/assembled/pic") self.pictures = PictureList(picture_basename) self.assembled_pictures = PictureList(assembled_picture_basename) self.taking_picture = Lock() self.arduino = ArduinoSerial() self.camera = CameraModule((picture_size[0] / 2, picture_size[1] / 2), camera_rotate=camera_rotate) self.camera_rotate = camera_rotate self.picture_size = picture_size self.pose_time = pose_time self.display_time = display_time self.idle_slideshow = idle_slideshow if self.idle_slideshow: self.slideshow_display_time = slideshow_display_time self.slideshow = Slideshow(display_size, display_time, os.path.dirname( os.path.realpath(picture_basename)), recursive=False) if display_rotate: self.slideshow.display.set_rotate(True) self.printer_module = PrinterModule() try: pygame.mixer.init(buffer=1024) self.shutter = pygame.mixer.Sound(shutter_sound) self.bip1 = pygame.mixer.Sound(bip1_sound) self.bip2 = pygame.mixer.Sound(bip2_sound) self.bip2.play() except pygame.error: self.shutter = None pass self.arduino.start() def teardown(self): self.arduino.stop() self.arduino.join() self.display.msg("Shutting down...") self.display.teardown() self.remove_tempfiles() sleep(0.5) exit(0) def remove_tempfiles(self): for filename in glob(tmp_dir + "photobooth_*.jpg"): try: os.remove(filename) except OSError: pass def _run_plain(self): while True: self.camera.set_idle() # Display default message self.display.msg("Hit the button!") # Wait for an event and handle it event = wait_for_event() self.handle_event(event) def _run_slideshow(self): while True: self.camera.set_idle() self.slideshow.display_next("Hit the button!") tic = time() while time() - tic < self.slideshow_display_time: self.check_and_handle_events() sleep(0.1) def run(self): while True: try: # Select idle screen type if self.idle_slideshow: self._run_slideshow() else: self._run_plain() # Catch exceptions and display message except CameraException as e: self.handle_exception(e.message) # Do not catch KeyboardInterrupt and SystemExit except (KeyboardInterrupt, SystemExit): raise except Exception as e: import sys print('SERIOUS ERROR' + repr(e)) sys.excepthook(*sys.exc_info()) self.handle_exception("SERIOUS ERROR!\n(see log file)") def check_and_handle_events(self): r, e = check_for_event() while r: self.handle_event(e) r, e = check_for_event() def handle_serial_command(self, event): with self.taking_picture: self.take_picture() def clear_event_queue(self): r, e = check_for_event() while r: r, e = check_for_event() def handle_event(self, event): if event.type == 0: self.teardown() if self.taking_picture.locked(): return if 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_serial_command(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'): with self.taking_picture: self.take_picture() elif key == ord('f'): print("ToggleScreen") self.display.toggle_fullscreen() elif key == ord('i'): # Re-initialize the camera for debugging self.camera.reinit() elif key == ord('p'): self.toggle_auto_print() elif key == ord('r'): self.toggle_rotate() def toggle_auto_print(self): "Toggle auto print and show an error message if printing isn't possible." if self.printer_module.can_print(): global auto_print auto_print = not auto_print self.display.msg("Autoprinting %s" % ("enabled" if auto_print else "disabled")) else: self.display.msg("Printing not configured\n(see log file)") def toggle_rotate(self): "Toggle rotating the display and camera." self.toggle_display_rotate() self.toggle_camera_rotate() self.display.msg("Display and camera rotated") def toggle_display_rotate(self): "Toggle rotating the display 90 degrees counter clockwise." self.display_rotate = (not self.display_rotate) self.display.set_rotate(self.display_rotate) self.slideshow.display.set_rotate(self.display_rotate) def toggle_camera_rotate(self): "Toggle rotating the camera 90 degrees counter clockwise." self.camera_rotate = (not self.camera_rotate) self.camera.set_rotate(self.camera_rotate) def handle_mousebutton(self, key, pos): """Implements the actions for the different mousebutton events""" # Take a picture if key == 1: with self.taking_picture: self.take_picture() def handle_exception(self, msg): """Displays an error message and returns""" print("Error: " + msg) try: self.display.msg("ERROR:\n\n" + msg) except GuiException: self.display.msg("ERROR") sleep(3) def assemble_picture(self, input_filename): (H, W) = self.picture_size # Thumbnail size of pictures inner_border = 0 thumb_box = (int(W), int(H / 2)) thumb_size = (thumb_box[0], thumb_box[1] - inner_border / 2) # Create output image with white background output_image = Image.new('RGB', (W, H), (255, 255, 255)) # Image 0 img = Image.open(input_filename) img = img.resize(maxpect(img.size, thumb_size), Image.ANTIALIAS) crop = int((img.size[1] - thumb_size[1]) / 2) img = img.crop((0, crop, img.size[0], img.size[1] - crop)) offset = (0, 0) output_image.paste(img, offset) # Image copy offset = (0, thumb_box[1] + inner_border) output_image.paste(img, offset) # Save assembled image ass_output_filename = self.assembled_pictures.get_next() output_image.save(ass_output_filename, "JPEG", quality=95) # Save Input Image output_filename = self.pictures.get_next() img.save(output_filename, "JPEG", quality=95) return ass_output_filename, output_filename def assemble_pictures(self, input_filenames): """Assembles four pictures into a 2x2 grid of thumbnails. The total size (WxH) is assigned in the global variable assembled_size at the top of this file. (E.g., 2352x1568) The outer border (a) is 2% of W The inner border (b) is 1% of W Note that if the camera is on its side, H and W will be swapped to create a portrait, rather than landscape, montage. Thumbnail sizes are calculated like so: 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 [Note that extra padding will be added on the sides if the aspect ratio of the camera images do not match the aspect ratio of the final assembled image.] """ # If the display is in portrait orientation, # we should create an assembled image that fits it. if self.display.get_rotate(): (H, W) = self.picture_size else: (W, H) = self.picture_size # Thumbnail size of pictures outer_border = int(2 * max(W, H) / 100) # 2% of long edge inner_border = int(1 * max(W, H) / 100) # 1% of long edge thumb_box = (int(W / 2), int(H / 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', (W, H), (255, 255, 255)) # Image 0 img = Image.open(input_filenames[0]) img = img.resize(maxpect(img.size, thumb_size), Image.ANTIALIAS) 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 = img.resize(maxpect(img.size, thumb_size), Image.ANTIALIAS) 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 = img.resize(maxpect(img.size, thumb_size), Image.ANTIALIAS) 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 = img.resize(maxpect(img.size, thumb_size), Image.ANTIALIAS) 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 show_preview(self, message=""): """If camera allows previews, take a photo and show it so people can pose before the shot. For speed, previews are decimated to fit within the screen instead of being scaled. For even more speed, the previews are blitted directly to a subsurface of the display. (Converting to a pygame Surface is slower). """ self.display.clear() if self.camera.has_preview(): f = self.camera.get_preview_array(self.display.get_size()) self.display.blit_array(f) self.display.show_message(message) self.display.apply() def show_counter(self, seconds): """Loop over showing the preview (if possible), with a count down""" tic = time() toc = time() - tic old_t = None while toc < seconds: t = seconds - int(toc) if t != old_t and self.bip1: self.display.msg(str(t)) self.bip1.play() self.say("{}".format(t)) old_t = t self.display.msg(str(t)) # Limit progress to 1 "second" per preview (e.g., too slow on Raspi 1) toc = min(toc + 1, time() - tic) def show_pose(self, seconds, message=""): """Loop over showing the preview (if possible), with a static message. Note that this is *necessary* for OpenCV webcams as V4L will ramp the brightness level only after a certain number of frames have been taken. """ tic = time() toc = time() - tic while toc < seconds: self.show_preview(message) # Limit progress to 1 "second" per preview (e.g., too slow on Raspi 1) toc = min(toc + 1, time() - tic) def say(self, text, voice="Alex"): os.system("say -v {} -r 200 '{}'".format(voice, text)) def say_with_delay(self, text, voice="Alex", delay=0.3): sleep(delay) self.say(text, voice) def take_picture(self): """Implements the picture taking routine""" # NUM PICs num_pics = 1 t = threading.Thread(target=self.say, args=("Pose for the photo!", )) t.start() activate_olympus = """ osascript -e 'tell application "OLYMPUS Capture" to activate' """ os.system(activate_olympus) self.display.msg( "\n\n\n\n\n\n\nTaking {} picture ...".format(num_pics)) sleep(3.0) t.join() # Extract display and image sizes display_size = self.display.get_size() focus_python() self.display.msg("Look at the camera...".format(num_pics)) self.say("Look at the camera") sleep(0.5) words = random.choice(word_list) #words.append(0.3) # Take pictures filenames = [i for i in range(num_pics)] for x in range(num_pics): # Countdown self.show_counter(self.pose_time) self.display.msg("") #self.display.msg("S M I L E !!!\n\n {} of {}".format(x + 1, num_pics)) t = threading.Thread(target=self.say_with_delay, args=tuple(words)) t.start() try: #t = threading.Thread(target=self.camera.take_picture, args=(tmp_dir + "photobooth_%02d.jpg" % x, )) #t.start() filenames[x] = self.camera.take_picture(tmp_dir + "photobooth_%02d.jpg" % x) #if self.shutter: # self.shutter.play() except CameraException as e: raise e t.join() # Show 'Wait' self.display.msg("Please wait!\n\nWorking\n...") # Assemble them ass_outfile, outfile = self.assemble_picture(filenames[0]) if self.printer_module.can_print(): # Show picture for 10 seconds and then send it to the printer. # If auto_print is True, hitting the button cancels the print. # If auto_print is False, hitting the button sends the print tic = time() t = int(self.display_time - (time() - tic)) old_t = self.display_time + 1 do_print = auto_print # Clear event queue (in case they hit the button twice accidentally) self.clear_event_queue() while t > 0: if t != old_t: self.display.clear() self.display.show_picture(outfile, display_size, (0, 0)) self.display.show_message( "%s%d" % ("Wait for print \n or press button to cancel!\n " if auto_print else "Press button to print photo!\n", t)) self.display.apply() old_t = t # Watch for button, gpio, mouse press to cancel/enable printing r, e = check_for_event() if r: # Caught a button press. self.display.clear() self.display.show_picture(outfile, display_size, (0, 0)) self.display.show_message( "Printing%s" % (" cancelled" if auto_print else "")) self.display.apply() self.clear_event_queue() # Discard extra events (e.g., they hit the button a bunch) sleep(2) self.clear_event_queue() do_print = not do_print break sleep(0.1) t = int(self.display_time - (time() - tic)) # Either button pressed or countdown timed out if do_print: self.display.msg("Printing") self.printer_module.enqueue(ass_outfile) else: # No printer available, so just show montage for 10 seconds self.display.clear() self.display.show_picture(outfile, display_size, (0, 0)) self.display.apply() sleep(self.display_time) self.clear_event_queue()