Add gphoto error handling, fix crash on fast triggering

This commit is contained in:
ABelliqueux 2025-03-06 15:12:23 +01:00
parent 0351c19eba
commit 9d5a49da21
2 changed files with 65 additions and 53 deletions

View File

@ -1,6 +1,7 @@
[DEFAULT] [DEFAULT]
# Camera type - can be : showmewebcam, picam, webcam, dslr # Camera type - can be : showmewebcam, picam, webcam, dslr
cam_type = 'webcam' cam_type = 'webcam'
apply_settings_on_startup = true
use_date_for_folder = false use_date_for_folder = false
file_extension = 'jpg' file_extension = 'jpg'
jpg_quality = 88 jpg_quality = 88

View File

@ -37,6 +37,7 @@ _ = gettext.translation('template', localedir='locales', languages=[LOCALE]).get
# defaults # defaults
project_settings_defaults = { project_settings_defaults = {
'cam_type': "webcam", 'cam_type': "webcam",
'apply_settings_on_startup' : True,
'use_date_for_folder': False, 'use_date_for_folder': False,
'file_extension':'png', 'file_extension':'png',
'jpg_quality':90, '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 '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 '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 '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 '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 # ~ 'manualfocusdrive' : dict(min=0, max=1, step=1, default=0, value=0), # Trigger autofocus # manualfocusdrive
} }
# Map generic config name to specific picamera setting name # Map generic config name to specific picamera setting name
self.cam_settings_map = { self.cam_settings_map = {
@ -446,17 +447,22 @@ class dslr():
self.onionskin_was_on = self.onionskin self.onionskin_was_on = self.onionskin
self.liveview_only = False self.liveview_only = False
self.lenspos = None self.lenspos = None
self.flip_img = False
self.cam_busy = 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: 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 # 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: except:
print(_("No camera found.")) print(_("No camera found."))
self.cam.exit() cam = None
self.camera = None
self.current_camera_config = None self.current_camera_config = None
return cam
def test_device(self, source): def test_device(self, source):
pass pass
@ -489,6 +495,8 @@ class dslr():
return cur_check_value return cur_check_value
def capture_frame(self, img_path): def capture_frame(self, img_path):
if self.cam is None:
self.cam = self.init_camera()
if not self.cam_busy: if not self.cam_busy:
# CHECK: Should we init and close dslr for each frame ? # CHECK: Should we init and close dslr for each frame ?
# Check battery level # 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. # 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) img_path = self.find_file_ext(file_path.name, img_path)
print('Copying image to', img_path) print('Copying image to', img_path)
except Exception as e:
print(e)
try:
camera_file = self.cam.file_get( camera_file = self.cam.file_get(
file_path.folder, file_path.folder,
file_path.name, file_path.name,
self.gp.GP_FILE_TYPE_NORMAL self.gp.GP_FILE_TYPE_NORMAL
) )
except:
print("Camera error. Check Battery and try restarting the camera.")
return False
try:
capture_ok = camera_file.save(img_path) capture_ok = camera_file.save(img_path)
except: except self.gp.GPhoto2Error as ex:
print('File access error.') print(ex.code)
return False # This is the way to find which error code is returned by gphoto
# ~ camera.exit() # See http://gphoto.org/doc/api/gphoto2-result_8h.html for error codes
self.cam_busy = False # 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 # Update frame
cam.frame = cv2.imread(img_path) self.frame = cv2.imread(img_path)
# ~ frame = cv2.resize(frame, (project_settings['screen_w'], project_settings['screen_h'])) self.cam_busy = False
# ~ if capture_ok is None:
# ~ return True
return True return True
else:
pass
def apply_gphoto_setting(self, setting:str): def apply_gphoto_setting(self, setting:str):
# Get corresponding setting name if possible # 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))) choices = list(self.gp.check_result(self.gp.gp_widget_get_choices(cur_setting)))
# Build dict with name/value equivalence # Build dict with name/value equivalence
choices_dict = {choices.index(i):i for i in list(choices)} 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 new_value exists in list, apply
if select_setting['value'] in choices_dict: 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'])) 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'] self.camera_current_settings[setting]['value'] = self.camera_current_settings[setting]['min']
def apply_setting(self, to_set:list=None, inc:int=0): 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)) self.camera_current_config = self.gp.check_result(self.gp.gp_camera_get_config(self.cam))
# iterate over the settings dictionary # iterate over the settings dictionary
if to_set is None: if to_set is None:
@ -592,12 +595,14 @@ class dslr():
return status return status
def flip_image(self): def flip_image(self):
self.frame = cv2.flip(self.frame, -1) self.flip_img = True
def focus(self, direction:str='-'): def focus(self, direction:str='-'):
self.apply_setting(['shutterspeed'], 1) self.apply_setting(['shutterspeed'], 1)
def reset_picture_settings(self): 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)) self.camera_current_config = self.gp.check_result(self.gp.gp_camera_get_config(self.cam))
for setting in self.camera_current_settings: for setting in self.camera_current_settings:
self.camera_current_settings[setting]['value'] = self.camera_current_settings[setting]['default'] self.camera_current_settings[setting]['value'] = self.camera_current_settings[setting]['default']
@ -992,7 +997,8 @@ def main(args):
index = len(img_list)-1 index = len(img_list)-1
playhead = index playhead = index
cam.apply_setting() if project_settings['apply_settings_on_startup']:
cam.apply_setting()
cam.frame = get_onionskin_frame(savepath) cam.frame = get_onionskin_frame(savepath)
cam.o_frame = cam.frame.copy() cam.o_frame = cam.frame.copy()
@ -1134,22 +1140,27 @@ def main(args):
img_name = return_next_frame_number(get_last_frame(savepath)) img_name = return_next_frame_number(get_last_frame(savepath))
img_path = os.path.join(savepath, img_name) img_path = os.path.join(savepath, img_name)
capture_ok = cam.capture_frame(img_path) capture_ok = cam.capture_frame(img_path)
print(_("File {} written.").format(img_path)) if capture_ok:
# Special case when we've no frame yet print(_("File {} written.").format(img_path))
if len(img_list) and (img_list[index] == '{letter}.-001.{ext}'.format(letter=project_letter, ext=project_settings['file_extension'])): # Special case when we've no frame yet
img_list[index] = img_name if len(img_list) and (img_list[index] == '{letter}.-001.{ext}'.format(letter=project_letter, ext=project_settings['file_extension'])):
else: img_list[index] = img_name
index += 1 else:
# Display a message if capture was not successfull index += 1
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:
cam.frame = get_onionskin_frame(savepath) cam.frame = get_onionskin_frame(savepath)
cam.o_frame = cam.frame.copy() 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 # Quit app
elif k%256 == 27: elif k%256 == 27:
# ESC pressed # ESC pressed