Last active
April 28, 2018 06:17
Revisions
-
jibbius revised this gist
Apr 28, 2018 . 1 changed file with 4 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -188,8 +188,10 @@ def taking_photo(photo_number, filename_prefix): #Take still camera.annotate_text = '' audio_stream = play_sound() camera.capture(filename) print("Photo (" + str(photo_number) + ") saved: " + filename) return audio_stream def playback_screen(filename_prefix): """ @@ -295,8 +297,8 @@ def main(): audio_streams = [] for photo_number in range(1, total_pics + 1): prep_for_photo_screen(photo_number) audio_stream = taking_photo(photo_number, filename_prefix) audio_streams.append(audio_stream) #thanks for playing playback_screen(filename_prefix) -
jibbius revised this gist
Apr 28, 2018 . 1 changed file with 7 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -32,6 +32,7 @@ def play_sound(): output=True, stream_callback=callback) stream.start_stream() return stream ############# ### Debug ### @@ -188,11 +189,8 @@ def taking_photo(photo_number, filename_prefix): #Take still camera.annotate_text = '' camera.capture(filename) print("Photo (" + str(photo_number) + ") saved: " + filename) def playback_screen(filename_prefix): """ Final screen before main loop restarts @@ -294,12 +292,18 @@ def main(): remove_overlay(overlay_2) remove_overlay(overlay_1) audio_streams = [] for photo_number in range(1, total_pics + 1): prep_for_photo_screen(photo_number) taking_photo(photo_number, filename_prefix) audio_streams.append(play_sound()) #thanks for playing playback_screen(filename_prefix) #close audio streams for stream in audio_streams: stream.close() # If we were doing a test run, exit here. if TESTMODE_AUTOPRESS_BUTTON: -
jibbius created this gist
Apr 6, 2018 .There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,329 @@ #!/usr/bin/env python #Imports import datetime import os from time import sleep from PIL import Image import RPi.GPIO as GPIO import picamera import pyaudio import wave import time # instantiate PyAudio (1) p = pyaudio.PyAudio() # define callback (2) def callback(in_data, frame_count, time_info, status): global wf data = wf.readframes(frame_count) return (data, pyaudio.paContinue) def play_sound(): # open stream using callback (3) global wf, stream wf = wave.open('assets/camera.wav', 'rb') stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), channels=wf.getnchannels(), rate=wf.getframerate(), output=True, stream_callback=callback) stream.start_stream() ############# ### Debug ### ############# # These options allow you to run a quick test of the app. # Both options must be set to 'False' when running as proper photobooth TESTMODE_AUTOPRESS_BUTTON = False # Button will be pressed automatically, and app will exit after 1 photo cycle TESTMODE_FAST = False # Reduced wait between photos and 2 photos only ######################## ### Variables Config ### ######################## pin_camera_btn = 21 # pin that the 'take photo' button is attached to pin_exit_btn = 13 # pin that the 'exit app' button is attached to (OPTIONAL BUTTON FOR EXITING THE APP) total_pics = 4 # number of pics to be taken prep_delay = 4 # number of seconds as users prepare to have photo taken photo_w = 1920 # take photos at this resolution photo_h = 1152 screen_w = 800 # resolution of the photo booth display screen_h = 480 if TESTMODE_FAST: total_pics = 2 # number of pics to be taken prep_delay = 2 # number of seconds at step 1 as users prep to have photo taken ############################## ### Setup Objects and Pins ### ############################## #Setup GPIO GPIO.setmode(GPIO.BCM) GPIO.setup(pin_camera_btn, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(pin_exit_btn , GPIO.IN, pull_up_down=GPIO.PUD_UP) #GPIO Debounce Duration #(This may help avoid "phantom presses" caused by electronic interference) debounce = 0.05 #Min duration (seconds) button is required to be "pressed in" for. #Setup Camera camera = picamera.PiCamera() camera.rotation = 270 camera.annotate_text_size = 80 camera.resolution = (photo_w, photo_h) camera.hflip = True # When preparing for photos, the preview will be flipped horizontally. #################### ### Other Config ### #################### REAL_PATH = os.path.dirname(os.path.realpath(__file__)) ######################## ### Helper Functions ### ######################## def print_overlay(string_to_print): """ Writes a string to both [i] the console, and [ii] camera.annotate_text """ print(string_to_print) camera.annotate_text = string_to_print def get_base_filename_for_images(): """ For each photo-capture cycle, a common base filename shall be used, based on the current timestamp. Example: ${ProjectRoot}/photos/2017-12-31_23-59-59 The example above, will later result in: ${ProjectRoot}/photos/2017-12-31_23-59-59_1of4.png, being used as a filename. """ base_filename = REAL_PATH + '/photos/' + str(datetime.datetime.now()).split('.')[0] base_filename = base_filename.replace(' ', '_') base_filename = base_filename.replace(':', '-') return base_filename def remove_overlay(overlay_id): """ If there is an overlay, remove it """ if overlay_id != -1: camera.remove_overlay(overlay_id) # overlay one image on screen def overlay_image(image_path, duration=0, layer=3): """ Add an overlay (and sleep for an optional duration). If sleep duration is not supplied, then overlay will need to be removed later. This function returns an overlay id, which can be used to remove_overlay(id). """ # "The camera`s block size is 32x16 so any image data # provided to a renderer must have a width which is a # multiple of 32, and a height which is a multiple of # 16." # Refer: http://picamera.readthedocs.io/en/release-1.10/recipes1.html#overlaying-images-on-the-preview # Load the arbitrarily sized image img = Image.open(image_path) # Create an image padded to the required size with # mode 'RGB' pad = Image.new('RGB', ( ((img.size[0] + 31) // 32) * 32, ((img.size[1] + 15) // 16) * 16, )) # Paste the original image into the padded one pad.paste(img, (0, 0)) #Get the padded image data try: padded_img_data = pad.tobytes() except AttributeError: padded_img_data = pad.tostring() # Note: tostring() is deprecated in PIL v3.x # Add the overlay with the padded image as the source, # but the original image's dimensions o_id = camera.add_overlay(padded_img_data, size=img.size) o_id.layer = layer if duration > 0: sleep(duration) camera.remove_overlay(o_id) return -1 # '-1' indicates there is no overlay else: return o_id # we have an overlay, and will need to remove it later ############### ### Screens ### ############### def prep_for_photo_screen(photo_number): """ Prompt the user to get ready for the next photo """ #Get ready for the next photo get_ready_image = REAL_PATH + "/assets/get_ready_"+str(photo_number)+".png" overlay_image(get_ready_image, prep_delay) def taking_photo(photo_number, filename_prefix): """ This function captures the photo """ #get filename to use filename = filename_prefix + '_' + str(photo_number) + 'of'+ str(total_pics)+'.jpg' #countdown from 3, and display countdown on screen for counter in range(3,0,-1): print_overlay(" ..." + str(counter)) sleep(1) #Take still camera.annotate_text = '' camera.capture(filename) play_sound() print("Photo (" + str(photo_number) + ") saved: " + filename) def playback_screen(filename_prefix): """ Final screen before main loop restarts """ #Processing print("Processing...") processing_image = REAL_PATH + "/assets/processing.png" overlay_image(processing_image, 2) #Playback prev_overlay = False for photo_number in range(1, total_pics + 1): filename = filename_prefix + '_' + str(photo_number) + 'of'+ str(total_pics)+'.jpg' this_overlay = overlay_image(filename, False, 3+total_pics) # The idea here, is only remove the previous overlay after a new overlay is added. if prev_overlay: remove_overlay(prev_overlay) sleep(2) prev_overlay = this_overlay remove_overlay(prev_overlay) #All done print("All done!") finished_image = REAL_PATH + "/assets/all_done_delayed_upload.png" overlay_image(finished_image, 5) def main(): """ Main program loop """ #Start Program print("Welcome to the photo booth!") print("Press the button to take a photo") #Start camera preview camera.start_preview(resolution=(screen_w, screen_h)) #Display intro screen intro_image_1 = REAL_PATH + "/assets/intro_1.png" intro_image_2 = REAL_PATH + "/assets/intro_2.png" overlay_1 = overlay_image(intro_image_1, 0, 3) overlay_2 = overlay_image(intro_image_2, 0, 4) #Wait for someone to push the button i = 0 blink_speed = 10 #Use falling edge detection to see if button is being pushed in GPIO.add_event_detect(pin_camera_btn, GPIO.FALLING) GPIO.add_event_detect(pin_exit_btn, GPIO.FALLING) while True: photo_button_is_pressed = None exit_button_is_pressed = None if GPIO.event_detected(pin_camera_btn): sleep(debounce) if GPIO.input(pin_camera_btn) == 0: photo_button_is_pressed = True if GPIO.event_detected(pin_exit_btn): sleep(debounce) if GPIO.input(pin_exit_btn) == 0: exit_button_is_pressed = True if exit_button_is_pressed is not None: return #Exit the photo booth if TESTMODE_AUTOPRESS_BUTTON: photo_button_is_pressed = True #Stay inside loop, until button is pressed if photo_button_is_pressed is None: #After every 10 cycles, alternate the overlay i = i+1 if i==blink_speed: overlay_2.alpha = 255 elif i==(2*blink_speed): overlay_2.alpha = 0 i=0 #Regardless, restart loop sleep(0.1) continue #Button has been pressed! #Silence GPIO detection GPIO.remove_event_detect(pin_camera_btn) GPIO.remove_event_detect(pin_exit_btn) #Get filenames for images filename_prefix = get_base_filename_for_images() print("Button pressed! You folks are in for a treat!") remove_overlay(overlay_2) remove_overlay(overlay_1) for photo_number in range(1, total_pics + 1): prep_for_photo_screen(photo_number) taking_photo(photo_number, filename_prefix) #thanks for playing playback_screen(filename_prefix) # If we were doing a test run, exit here. if TESTMODE_AUTOPRESS_BUTTON: break # Otherwise, display intro screen again overlay_1 = overlay_image(intro_image_1, 0, 3) overlay_2 = overlay_image(intro_image_2, 0, 4) GPIO.add_event_detect(pin_camera_btn, GPIO.FALLING) GPIO.add_event_detect(pin_exit_btn, GPIO.FALLING) print("Press the button to take a photo") if __name__ == "__main__": try: main() except KeyboardInterrupt: print("goodbye") except Exception as exception: print("unexpected error: ", str(exception)) finally: camera.stop_preview() camera.close() GPIO.cleanup() p.terminate()