def delete_old_images(curr_image_num): for file in statedb['to_delete']: (seg_num, image_num) = extract_from_abs_fn(file) if seg_num is None or image_num is None: print("filename %s is invalid. Couldnt not delete" % file) continue # For an image to be "old" and no longer needed we need to possibly consider: # 1) its index is greater than a batch size away and thus it has already been added to a segment video. # 2) It is not contained in the list of files still to be backed up segment_size = Settings.get_value('encoder_video_frames_per_segment', type=int) old_enough = image_num < (curr_image_num - 2 * segment_size) backed_up = file not in statedb['to_backup'] ok_to_delete = True if Settings.get_value('backup_enable', type=bool): ok_to_delete = ok_to_delete and backed_up create_video = Settings.get_value('encoder_video_create', type=bool) create_gif = Settings.get_value('encoder_gif_create', type=bool) need_full_segment = create_video or create_gif if need_full_segment: ok_to_delete = ok_to_delete and old_enough if ok_to_delete: cmd = "rm -f %s" % file success = run_cmd(cmd) if success: statedb['to_delete'].pop(0) # Since the list is in ascending order, once we find a file that is too new, we are done. if need_full_segment: if not old_enough: break
def video_worker(seg_num, image_num): try: input_fns = "-i %s/%s/img%%07d.jpg" % (image_dir, form_segment_name(seg_num)) output_fn = "%s/%s.mp4" % (Settings.get_value('encoder_video_path'), form_segment_name(seg_num)) success = True frame_rate = Settings.get_value('encoder_video_frame_rate', type=int) profile = Settings.get_value('encoder_video_profile') preset = Settings.get_value('encoder_video_preset') success &= create_video(input_fns, output_fn, image_num, frame_rate=frame_rate, profile=profile, preset=preset) if success: success &= append_video_segment(seg_num) # If successful, delete old segment if success: video_cleanup(seg_num) return except (KeyboardInterrupt, SystemExit) as e: terminate(1)
def set_camera_options(camera): # Set camera resolution. resolution = Settings.get_value('capture_resolution', type=dict) if resolution: camera.resolution = (resolution['width'], resolution['height']) # Set ISO. iso = Settings.get_value('capture_iso', type=int) if iso: camera.iso = iso # Set shutter speed. shutter_speed = Settings.get_value('capture_shutter_speed', type=int) if shutter_speed: camera.shutter_speed = shutter_speed # Sleep to allow the shutter speed to take effect correctly. sleep(1) camera.exposure_mode = 'off' # Set white balance. white_balance = Settings.get_value('capture_white_balance', type=dict) if white_balance: camera.awb_mode = 'off' camera.awb_gains = (white_balance['red_gain'], white_balance['blue_gain']) # Set camera rotation rotation = Settings.get_value('capture_rotation', type=int) if rotation: camera.rotation = rotation return camera
def restore_power_options(): global camera # enable hdmi if Settings.get_value('power_disable_hdmi', type=bool): cmd = "/usr/bin/tvservice -p" run_cmd(cmd) # Disabling LEDs can save about 5mA per LED # https://www.jeffgeerling.com/blogs/jeff-geerling/raspberry-pi-zero-conserve-energy if Settings.get_value('power_disable_pi_leds', type=bool): # PiZero Only # Set the Pi Zero ACT LED trigger to 'none'. #cmd1 = "echo none | sudo tee /sys/class/leds/led0/trigger" #run_cmd(cmd) # Turn on the Pi Zero ACT LED. #cmd2 = "echo 0 | sudo tee /sys/class/leds/led0/brightness" #run_cmd(cmd2) #with open("/sys/class/leds/led0/brightness","w") as f: #f.write('0\n') cmd = "echo 'Pi Zero ACT LED turned on.'" run_cmd(cmd) if Settings.get_value('power_disable_camera_led', type=bool): # Turn the camera's LED on camera.led = True cmd = "echo 'Camera LED enabled.'" run_cmd(cmd)
def backup_image(fn): # Generate file to backup and store to list of pending files to backup. statedb['to_backup'].append(fn) # Determine if we have enough files to make it worth backing up. if len(statedb['to_backup']) < Settings.get_value('backup_size', type=int): return True # hostname destination server_details = Settings.get_value('backup_server', type=dict) ssh = SSHClient() ssh.load_system_host_keys() ssh.connect(server_details['hostname']) # SCPCLient takes a paramiko transport as an argument scp = SCPClient(ssh.get_transport()) # backup all files overall_success = True new_backup_list = [] for file in statedb['to_backup']: #cmd = "scp %s %s" % (file, dest) # make this run command silent so that in cases whhen the server is down we dont spam the log indefinitely #success = run_cmd(cmd, silent=(not overall_success)) try: success = True scp.put(file, remote_path=server_details['image_path']) except FileNotFoundError: print("Error, file '%s' not found and thus enable to delete." % file) # Set as successful so we dont keep trying to delete a file that isnt there. success = True except paramiko.ssh_exception.SSHException: print("paramiko.ssh_exception.SSHException: Channel closed?") success = False if success: # denote file as successfully backed up #statedb['to_backup'].remove(file) # Denote that image should be removed if Settings.get_value('backup_enable_image_cleanup', type=bool): statedb['to_delete'].append(file) else: new_backup_list.append(file) overall_success &= success statedb['to_backup'] = new_backup_list scp.close() if not overall_success: print("Failed copying one or more images to %s!" % server_details['hostname']) return overall_success
def batch_capture(path, image_num, batch_size, last_capture_time): """Capture up to batch_size images at interval seconds apart into path with filenames indexed starting at image_num""" cnt = image_num % Settings.get_value('encoder_video_frames_per_segment', type=int) # Init time markers interval = timedelta( seconds=Settings.get_value('capture_interval', type=float)) if last_capture_time is None: last_capture_time = datetime.now() next_capture_time = last_capture_time + interval # Capture images while cnt < batch_size: if not Settings.get_value('capture_enable', type=bool): raise AbortCapture now = datetime.now() #If not time yet, sleep and check again if now < next_capture_time: sleep(POLL_PERIOD) continue msg = "Capturing image #%d Time: %s Delta: %s" % \ (image_num, str(now), str(now-last_capture_time)) print(msg) sys.stdout.flush() # Capture a picture. image_fn = form_image_fn(image_num) image_abs_fn = path + '/' + image_fn try: camera.capture(image_abs_fn) except PiCameraRuntimeError: print("ERROR: Timed out waiting for capture to end!") continue # backup image to server if specified if Settings.get_value('backup_enable', type=bool): backup_image(image_abs_fn) # delete any old image(s) if Settings.get_value('backup_enable_image_cleanup', type=bool): delete_old_images(image_num) # Book keeping last_capture_time = now next_capture_time = last_capture_time + interval image_num += 1 cnt += 1 return (last_capture_time, image_num)
def create_gif_segment(seg_num, start_img): # Create an animated gif (Requires ImageMagick). # print('\nCreating animated gif.\n') seg_str = form_segment_name(seg_num) fn = '%s.gif' % seg_str cmd = 'convert -delay 10 -loop 0 %s/%s/img*.jpg %s/%s' % ( image_dir, seg_str, Settings.get_value('encoder_gif_path'), fn) success = run_cmd(cmd, verbose=True, msg="--> encoding_frames() begun!")
def create_video_segment(seg_num, start_img): #print("Creating video segment") seg_str = form_segment_name(seg_num) fn = '%s.mp4' % seg_str frame_rate = Settings.get_value('encoder_video_frame_rate', type=int) # Helpful Link: # # https://trac.ffmpeg.org/wiki/Encode/H.264#Listpresetsandtunes # # Use HW encoding with the h264_omx codec: -c:v h264_omx # Use Baseline profile to omit B frames and reduce cpu usage. -profile:v baseline cmd = 'avconv -y -framerate %s -start_number %s -i %s/%s/img%%07d.jpg -profile:v %s -preset %s -vf format=yuv420p %s/%s' % \ (str(frame_rate), str(start_img), image_dir, seg_str, Settings.get_value('encoder_video_profile'), Settings.get_value('encoder_video_preset'), Settings.get_value('encoder_video_path'), fn) #cmd = 'avconv -y -framerate %s -start_number %s -i %s/%s/img%%07d.jpg -c:v h264_omx -vf format=yuv420p %s/%s' % \ # (str(frame_rate), str(start_img), image_dir, seg_str, config['video_path'], fn) success = run_cmd(cmd, verbose=True, msg="--> encoding_frames() begun!") return success
def capture_loop(image_dir, seg_num, image_num): # Init global camera # Init camera set_camera_options(camera) last_capture_time = None while True: try: print( "========================================== Segment # %d ==========================================" % (seg_num)) seg_str = form_segment_name(seg_num) full_path = image_dir + '/' + seg_str create_dir(full_path) # Capture n images segment_size = Settings.get_value( 'encoder_video_frames_per_segment', type=int) (last_capture_time, next_image_num) = batch_capture(full_path, image_num, segment_size, last_capture_time) if Settings.get_value('encoder_gif_create', type=bool): # Start thread to run concurrently t = threading.Thread(target=gif_worker, args=(seg_num, image_num)).start() # Create video segment and append to prior segments. if Settings.get_value('encoder_video_create', type=bool): # Start thread to run concurrently t = threading.Thread(target=video_worker, args=(seg_num, image_num)).start() # Increment segment number seg_num += 1 image_num = next_image_num except (AbortCapture): camera.close() return except (KeyboardInterrupt, SystemExit) as e: camera.close() raise e
def append_video_segment(seg_num): video_path = Settings.get_value('encoder_video_path') #print("Appending segment") # Form absolute path for segment file seg_str = form_segment_name(seg_num) new_seg_fn = '%s.mp4' % seg_str abs_new_seg_fn = video_path + '/' + new_seg_fn # Form absolute path for input video ivideo_fn = Settings.get_value('encoder_video_output_filename') abs_ivideo_fn = video_path + '/' + ivideo_fn # Form absolute path for output video ovideo_fn = 'tmp.mp4' abs_ovideo_fn = video_path + '/' + ovideo_fn # Append new video to old if not file_exists(ivideo_fn): cmd = 'cp %s %s' % (abs_new_seg_fn, abs_ovideo_fn) else: # Create file with list of files to concat concat_fn = video_path + '/' + "concat.txt" with open(concat_fn, "w") as f: f.write("file %s\nfile %s\n" % (abs_ivideo_fn, abs_new_seg_fn)) f.close() #cmd = 'avconv -i \"concat:%s|%s\" -c copy %s' % (abs_ivideo_fn, abs_new_seg_fn, abs_ovideo_fn) cmd = 'avconv -f concat -safe 0 -i %s -c copy %s' % (concat_fn, abs_ovideo_fn) success = run_cmd(cmd) if not success: print("Encoding of %s failed!" % abs_new_seg_fn) return success # If timelapse mpeg exists, rename as a backup timelapse_mpeg = Path(abs_ivideo_fn) if timelapse_mpeg.is_file(): cmd = 'mv -f %s %s' % (abs_ivideo_fn, abs_ivideo_fn + ".backup") success = run_cmd(cmd) # Rename tmp as new full timelapse mpeg # If tmp doesnt exist, its possible the encoding failed or the user aborted if file_exists(abs_ovideo_fn): # file exists, so delete old copy and rename tmp to new copy cmd = 'mv %s %s' % (abs_ovideo_fn, abs_ivideo_fn) success = run_cmd(cmd) else: success = False return success
def gen_final_video(): concat_fn = '%s/filelist-%s.txt' % (Settings.get_value( 'capture_video_path'), datetime.now().strftime('%Y-%m-%d_%H-%M-%S')) all_images = get_all_images(Settings.get_value('capture_image_path'), descending=False) with open(concat_fn, 'w') as f: for i in all_images: f.write("file '%s'\n" % i) input_fns = "-f concat -safe 0 -i %s" % (concat_fn) output_fn = "%s/%s.mp4" % (Settings.get_value('capture_image_path'), "final") hq_profile = Settings.get_value('encoder_hq_video_profile') hq_preset = Settings.get_value('encoder_hq_video_preset') success = True success &= create_video(input_fns, output_fn, None, frame_rate=None, profile=hq_profile, preset=hq_preset)
def video_cleanup(seg_num): """Remove old artifacts of the encoding / capture process""" # Remove old segments seg_to_delete = seg_num - SEGMENT_DELETION_DELAY seg_str = form_segment_name(seg_num) old_seg_fn = '%s.mp4' % seg_str abs_old_seg_fn = Settings.get_value( 'encoder_video_path') + '/' + old_seg_fn success = False if seg_to_delete > 0: cmd = 'rm -f %s' % (abs_old_seg_fn) success = run_cmd(cmd) else: success = True return success
# Log that process was started cmd = 'echo Timelapse capture process started' run_cmd(cmd) #config = config.load_config() #load_statedb() create_tables() ################### # init ################### # Create directory based on current timestamp. create_dir(Settings.get_value('capture_image_path')) if Settings.get_value('encoder_video_create', type=bool): create_dir(Settings.get_value('encoder_video_path')) if Settings.get_value('encoder_gif_create', type=bool): create_dir(Settings.get_value('encoder_gif_path')) ############################### # Determine last image/segment ################################ seg_num = 0 image_num = 0 # Get descending sorted list of all images in images folder