From 9d5a49da214c021c3ba2d9f612927be968af4b65 Mon Sep 17 00:00:00 2001 From: ABelliqueux Date: Thu, 6 Mar 2025 15:12:23 +0100 Subject: [PATCH] Add gphoto error handling, fix crash on fast triggering --- config.toml | 1 + frame_opencv.py | 117 ++++++++++++++++++++++++++---------------------- 2 files changed, 65 insertions(+), 53 deletions(-) diff --git a/config.toml b/config.toml index 6acea19..f451e5e 100644 --- a/config.toml +++ b/config.toml @@ -1,6 +1,7 @@ [DEFAULT] # Camera type - can be : showmewebcam, picam, webcam, dslr cam_type = 'webcam' +apply_settings_on_startup = true use_date_for_folder = false file_extension = 'jpg' jpg_quality = 88 diff --git a/frame_opencv.py b/frame_opencv.py index 8e20035..94e802e 100644 --- a/frame_opencv.py +++ b/frame_opencv.py @@ -37,6 +37,7 @@ _ = gettext.translation('template', localedir='locales', languages=[LOCALE]).get # defaults project_settings_defaults = { 'cam_type': "webcam", + 'apply_settings_on_startup' : True, 'use_date_for_folder': False, 'file_extension':'png', 'jpg_quality':90, @@ -421,8 +422,8 @@ class dslr(): 'whitebalance' : dict(min=0, max=7, step=1, default=2, value=1), # 0 Automatic 1 Daylight 2 Fluorescent 3 Tungsten 4 Flash 5 Cloudy 6 Shade 7 Preset 'capturetarget' : dict(min=0, max=1, step=1, default=0, value=0), # Internal memory 'iso' : dict(min=0, max=5, default=0, step=1, value=0), # 0:100, 5:3200 - 'shutterspeed' : dict(min=0, max=51, step=1, default=0, value=20), # 0 : 1/4000, 51: 30s - 'manualfocusdrive' : dict(min=0, max=1, step=1, default=0, value=0), # Trigger autofocus # manualfocusdrive + 'shutterspeed' : dict(min=0, max=51, step=1, default=0, value=30), # 0 : 1/4000, 51: 30s + # ~ 'manualfocusdrive' : dict(min=0, max=1, step=1, default=0, value=0), # Trigger autofocus # manualfocusdrive } # Map generic config name to specific picamera setting name self.cam_settings_map = { @@ -446,17 +447,22 @@ class dslr(): self.onionskin_was_on = self.onionskin self.liveview_only = False self.lenspos = None + self.flip_img = False self.cam_busy = False - self.cam = self.gp.check_result(self.gp.gp_camera_new()) + self.camera_current_config = None + self.cam = self.init_camera() + + def init_camera(self): + cam = self.gp.check_result(self.gp.gp_camera_new()) try: - self.gp.check_result(self.gp.gp_camera_init(self.cam)) + self.gp.check_result(self.gp.gp_camera_init(cam)) # get configuration tree - self.camera_current_config = self.gp.check_result(self.gp.gp_camera_get_config(self.cam)) + self.camera_current_config = self.gp.check_result(self.gp.gp_camera_get_config(cam)) except: print(_("No camera found.")) - self.cam.exit() - self.camera = None + cam = None self.current_camera_config = None + return cam def test_device(self, source): pass @@ -489,6 +495,8 @@ class dslr(): return cur_check_value def capture_frame(self, img_path): + if self.cam is None: + self.cam = self.init_camera() if not self.cam_busy: # CHECK: Should we init and close dslr for each frame ? # Check battery level @@ -503,32 +511,33 @@ class dslr(): # We don't want to download a jpg or raw from the dslr and save it as a false *.png. img_path = self.find_file_ext(file_path.name, img_path) print('Copying image to', img_path) - except Exception as e: - print(e) - try: camera_file = self.cam.file_get( - file_path.folder, - file_path.name, - self.gp.GP_FILE_TYPE_NORMAL - ) - except: - print("Camera error. Check Battery and try restarting the camera.") - return False - try: + file_path.folder, + file_path.name, + self.gp.GP_FILE_TYPE_NORMAL + ) capture_ok = camera_file.save(img_path) - except: - print('File access error.') - return False - # ~ camera.exit() - self.cam_busy = False + except self.gp.GPhoto2Error as ex: + print(ex.code) + # This is the way to find which error code is returned by gphoto + # See http://gphoto.org/doc/api/gphoto2-result_8h.html for error codes + # Cam was turned off/on + if ex.code == self.gp.GP_ERROR_IO_USB_FIND or ex.code == self.gp.GP_ERROR_IO_USB_CLAIM: + self.cam.exit() + self.cam = self.init_camera() + # ~ if ex.code == self.gp.GP_ERROR_CAMERA_BUSY: + # ~ print("Camera Busy.") + self.cam_busy = False + return False + # Flip image if needed + if self.flip_img: + frm = cv2.imread(img_path) + frm = cv2.flip(frm, -1) + cv2.imwrite(img_path, frm) # Update frame - cam.frame = cv2.imread(img_path) - # ~ frame = cv2.resize(frame, (project_settings['screen_w'], project_settings['screen_h'])) - # ~ if capture_ok is None: - # ~ return True + self.frame = cv2.imread(img_path) + self.cam_busy = False return True - else: - pass def apply_gphoto_setting(self, setting:str): # Get corresponding setting name if possible @@ -546,14 +555,6 @@ class dslr(): choices = list(self.gp.check_result(self.gp.gp_widget_get_choices(cur_setting))) # Build dict with name/value equivalence choices_dict = {choices.index(i):i for i in list(choices)} - # Increment mode : current value is increased or looped - # ~ if inc: - # Get current setting value - # ~ new_value = gp.check_result(gp.gp_widget_get_value(cur_setting)) - # Check current value + 1 is in range - # ~ if choices.index(new_value) in range(0, self.camera_current_settings[setting]['max']+1): - # Apply or loop value accordingly - # ~ pass # If new_value exists in list, apply if select_setting['value'] in choices_dict: cur_setting_choice = self.gp.check_result(self.gp.gp_widget_get_choice(cur_setting, select_setting['value'])) @@ -570,6 +571,8 @@ class dslr(): self.camera_current_settings[setting]['value'] = self.camera_current_settings[setting]['min'] def apply_setting(self, to_set:list=None, inc:int=0): + if self.cam is None: + self.cam = self.init_camera() self.camera_current_config = self.gp.check_result(self.gp.gp_camera_get_config(self.cam)) # iterate over the settings dictionary if to_set is None: @@ -592,12 +595,14 @@ class dslr(): return status def flip_image(self): - self.frame = cv2.flip(self.frame, -1) + self.flip_img = True def focus(self, direction:str='-'): self.apply_setting(['shutterspeed'], 1) def reset_picture_settings(self): + if self.cam is None: + self.cam = self.init_camera() self.camera_current_config = self.gp.check_result(self.gp.gp_camera_get_config(self.cam)) for setting in self.camera_current_settings: self.camera_current_settings[setting]['value'] = self.camera_current_settings[setting]['default'] @@ -992,7 +997,8 @@ def main(args): index = len(img_list)-1 playhead = index - cam.apply_setting() + if project_settings['apply_settings_on_startup']: + cam.apply_setting() cam.frame = get_onionskin_frame(savepath) cam.o_frame = cam.frame.copy() @@ -1134,22 +1140,27 @@ def main(args): img_name = return_next_frame_number(get_last_frame(savepath)) img_path = os.path.join(savepath, img_name) capture_ok = cam.capture_frame(img_path) - print(_("File {} written.").format(img_path)) - # Special case when we've no frame yet - if len(img_list) and (img_list[index] == '{letter}.-001.{ext}'.format(letter=project_letter, ext=project_settings['file_extension'])): - img_list[index] = img_name - else: - index += 1 - # Display a message if capture was not successfull - if not capture_ok: - cam.frame = generate_text_image(_("Error during capture."), - project_settings['screen_w'], project_settings['screen_h'] - ) - cam.o_frame = cam.frame.copy() - else: + if capture_ok: + print(_("File {} written.").format(img_path)) + # Special case when we've no frame yet + if len(img_list) and (img_list[index] == '{letter}.-001.{ext}'.format(letter=project_letter, ext=project_settings['file_extension'])): + img_list[index] = img_name + else: + index += 1 cam.frame = get_onionskin_frame(savepath) cam.o_frame = cam.frame.copy() - # ~ frame = cam.frame + # Display a message if capture was not successfull + # This happens when you try to take too much pictures in a short span of time with the DSLR. + # With a long exposure, gphoto will sometimes throw a GP_ERROR_CAMERA_BUSY (-110) error. + # In this case, the user should try to take the picture again and it should work, so the need + # for a message in UI is not obvious, and can be confusing. + # Disabled for now + # ~ else: + # ~ cam.frame = generate_text_image(_("Error during capture."), + # ~ project_settings['screen_w'], project_settings['screen_h'] + # ~ ) + # ~ cam.o_frame = cam.frame.copy() + # Quit app elif k%256 == 27: # ESC pressed