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]
# 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

View File

@ -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:
capture_ok = camera_file.save(img_path)
except:
print('File access error.')
return False
# ~ camera.exit()
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,6 +997,7 @@ def main(args):
index = len(img_list)-1
playhead = index
if project_settings['apply_settings_on_startup']:
cam.apply_setting()
cam.frame = get_onionskin_frame(savepath)
@ -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)
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
# 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:
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