Retrait verrou camera et threading : revert approche simple sans deadlock

This commit is contained in:
2026-04-09 02:00:11 +02:00
parent 03102c803e
commit db751ca48c

View File

@@ -1,7 +1,6 @@
import io import io
import logging import logging
import subprocess import subprocess
import threading
import time import time
from datetime import datetime from datetime import datetime
@@ -67,7 +66,6 @@ class Camera:
self.webcam = None self.webcam = None
self.webcam_index = -1 self.webcam_index = -1
self.preview_dslr_ok = True # True si le dernier preview DSLR a reussi self.preview_dslr_ok = True # True si le dernier preview DSLR a reussi
self._lock = threading.Lock() # Verrou pour eviter acces gphoto2 concurrent
def connecter(self, source=None) -> bool: def connecter(self, source=None) -> bool:
"""Connecte la camera. source: 'gphoto2', 'webcam:0', 'webcam:2', etc.""" """Connecte la camera. source: 'gphoto2', 'webcam:0', 'webcam:2', etc."""
@@ -147,33 +145,32 @@ class Camera:
def _capture_gphoto2(self, chemin_dest: Path) -> Path | None: def _capture_gphoto2(self, chemin_dest: Path) -> Path | None:
from PIL import Image as PILImage from PIL import Image as PILImage
with self._lock: for attempt in range(3):
for attempt in range(3): try:
try: chemin_camera = self.camera.capture(gp.GP_CAPTURE_IMAGE)
chemin_camera = self.camera.capture(gp.GP_CAPTURE_IMAGE) fichier_camera = gp.CameraFile()
fichier_camera = gp.CameraFile() self.camera.file_get(
self.camera.file_get( chemin_camera.folder, chemin_camera.name, gp.GP_FILE_TYPE_NORMAL, fichier_camera
chemin_camera.folder, chemin_camera.name, gp.GP_FILE_TYPE_NORMAL, fichier_camera )
) tmp_path = str(chemin_dest) + ".tmp"
tmp_path = str(chemin_dest) + ".tmp" fichier_camera.save(tmp_path)
fichier_camera.save(tmp_path)
img = PILImage.open(tmp_path) img = PILImage.open(tmp_path)
if img.width > 4000: if img.width > 4000:
ratio = 4000 / img.width ratio = 4000 / img.width
img = img.resize((4000, int(img.height * ratio)), PILImage.LANCZOS) img = img.resize((4000, int(img.height * ratio)), PILImage.LANCZOS)
img.save(str(chemin_dest), "JPEG", quality=92) img.save(str(chemin_dest), "JPEG", quality=92)
Path(tmp_path).unlink(missing_ok=True) Path(tmp_path).unlink(missing_ok=True)
log.info(f"Photo capturee (DSLR) : {chemin_dest} ({img.width}x{img.height})") log.info(f"Photo capturee (DSLR) : {chemin_dest} ({img.width}x{img.height})")
return chemin_dest return chemin_dest
except gp.GPhoto2Error as e: except gp.GPhoto2Error as e:
if "I/O in progress" in str(e) and attempt < 2: if "I/O in progress" in str(e) and attempt < 2:
log.warning(f"DSLR occupe, retry {attempt + 1}/3...") log.warning(f"DSLR occupe, retry {attempt + 1}/3...")
time.sleep(0.5) time.sleep(0.5)
continue continue
log.error(f"Erreur capture DSLR : {e}") log.error(f"Erreur capture DSLR : {e}")
return None return None
def _capture_webcam(self, chemin_dest: Path) -> Path | None: def _capture_webcam(self, chemin_dest: Path) -> Path | None:
if not self.webcam or not self.webcam.isOpened(): if not self.webcam or not self.webcam.isOpened():
@@ -201,49 +198,46 @@ class Camera:
return None # Pas de simulation ni webcam en dehors du mode dedie return None # Pas de simulation ni webcam en dehors du mode dedie
def activer_viewfinder(self): def activer_viewfinder(self):
"""Active le LiveView sur les Canon (nécessaire avant capture_preview).""" """Active le LiveView sur les Canon."""
if self.mode != "gphoto2" or not self.connectee: if self.mode != "gphoto2" or not self.connectee:
return return
with self._lock: try:
try: cfg = self.camera.get_config()
cfg = self.camera.get_config() vf = cfg.get_child_by_name("viewfinder")
vf = cfg.get_child_by_name("viewfinder") vf.set_value(1)
vf.set_value(1) self.camera.set_config(cfg)
self.camera.set_config(cfg) log.info("Viewfinder activé")
log.info("Viewfinder activé") except Exception as e:
except Exception as e: log.warning(f"Viewfinder non supporté : {e}")
log.warning(f"Viewfinder non supporté : {e}")
def desactiver_viewfinder(self): def desactiver_viewfinder(self):
"""Désactive le LiveView.""" """Désactive le LiveView."""
if self.mode != "gphoto2" or not self.connectee: if self.mode != "gphoto2" or not self.connectee:
return return
with self._lock: try:
try: cfg = self.camera.get_config()
cfg = self.camera.get_config() vf = cfg.get_child_by_name("viewfinder")
vf = cfg.get_child_by_name("viewfinder") vf.set_value(0)
vf.set_value(0) self.camera.set_config(cfg)
self.camera.set_config(cfg) log.info("Viewfinder désactivé")
log.info("Viewfinder désactivé") except Exception as e:
except Exception as e: log.warning(f"Viewfinder non supporté : {e}")
log.warning(f"Viewfinder non supporté : {e}")
def _preview_gphoto2(self) -> bytes | None: def _preview_gphoto2(self) -> bytes | None:
with self._lock: try:
try: fichier = self.camera.capture_preview()
fichier = self.camera.capture_preview() donnees = bytes(fichier.get_data_and_size())
donnees = bytes(fichier.get_data_and_size()) img = cv2.imdecode(np.frombuffer(donnees, dtype=np.uint8), cv2.IMREAD_COLOR)
img = cv2.imdecode(np.frombuffer(donnees, dtype=np.uint8), cv2.IMREAD_COLOR) if img is not None:
if img is not None: h, w = img.shape[:2]
h, w = img.shape[:2] if w > 960:
if w > 960: img = cv2.resize(img, (960, int(h * 960 / w)), interpolation=cv2.INTER_LINEAR)
img = cv2.resize(img, (960, int(h * 960 / w)), interpolation=cv2.INTER_LINEAR) _, buf = cv2.imencode(".jpg", img, [cv2.IMWRITE_JPEG_QUALITY, 75])
_, buf = cv2.imencode(".jpg", img, [cv2.IMWRITE_JPEG_QUALITY, 75]) return buf.tobytes()
return buf.tobytes() return donnees
return donnees except gp.GPhoto2Error as e:
except gp.GPhoto2Error as e: log.error(f"Erreur preview DSLR : {e}")
log.error(f"Erreur preview DSLR : {e}") return None
return None
def _preview_webcam(self) -> bytes | None: def _preview_webcam(self) -> bytes | None:
if not self.webcam or not self.webcam.isOpened(): if not self.webcam or not self.webcam.isOpened():