From b4ec4dbf3f56444a6bb4d86cbf9f221f7e1d28c5 Mon Sep 17 00:00:00 2001 From: Jules Date: Thu, 9 Apr 2026 07:28:33 +0200 Subject: [PATCH] Fix race condition gphoto2 : lock non-bloquant preview cede la place a la capture --- backend/camera.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/backend/camera.py b/backend/camera.py index 1cad4e3..8e651e5 100644 --- a/backend/camera.py +++ b/backend/camera.py @@ -1,6 +1,7 @@ import io import logging import subprocess +import threading import time from datetime import datetime @@ -66,6 +67,7 @@ class Camera: self.webcam = None self.webcam_index = -1 self.preview_dslr_ok = True # True si le dernier preview DSLR a reussi + self._gp_lock = threading.Lock() # Un seul appel gphoto2 a la fois def connecter(self, source=None) -> bool: """Connecte la camera. source: 'gphoto2', 'webcam:0', 'webcam:2', etc.""" @@ -147,21 +149,20 @@ class Camera: for attempt in range(3): try: - chemin_camera = self.camera.capture(gp.GP_CAPTURE_IMAGE) - fichier_camera = gp.CameraFile() - self.camera.file_get( - chemin_camera.folder, chemin_camera.name, gp.GP_FILE_TYPE_NORMAL, fichier_camera - ) - tmp_path = str(chemin_dest) + ".tmp" - fichier_camera.save(tmp_path) - + with self._gp_lock: + chemin_camera = self.camera.capture(gp.GP_CAPTURE_IMAGE) + fichier_camera = gp.CameraFile() + self.camera.file_get( + chemin_camera.folder, chemin_camera.name, gp.GP_FILE_TYPE_NORMAL, fichier_camera + ) + tmp_path = str(chemin_dest) + ".tmp" + fichier_camera.save(tmp_path) img = PILImage.open(tmp_path) if img.width > 4000: ratio = 4000 / img.width img = img.resize((4000, int(img.height * ratio)), PILImage.LANCZOS) img.save(str(chemin_dest), "JPEG", quality=92) Path(tmp_path).unlink(missing_ok=True) - log.info(f"Photo capturee (DSLR) : {chemin_dest} ({img.width}x{img.height})") return chemin_dest except gp.GPhoto2Error as e: @@ -224,6 +225,8 @@ class Camera: log.warning(f"Viewfinder non supporté : {e}") def _preview_gphoto2(self) -> bytes | None: + if not self._gp_lock.acquire(blocking=False): + return None # Capture en cours, on saute ce frame try: fichier = self.camera.capture_preview() donnees = bytes(fichier.get_data_and_size()) @@ -238,6 +241,8 @@ class Camera: except gp.GPhoto2Error as e: log.error(f"Erreur preview DSLR : {e}") return None + finally: + self._gp_lock.release() def _preview_webcam(self) -> bytes | None: if not self.webcam or not self.webcam.isOpened():