Robustesse DSLR + éclairage visage + fix pellicule cadre imposé
- camera.py: fix fuite FD USB sur échec reconnexion (gp.Camera non libéré), cleanup PTP session avant init, catch toutes exceptions preview/capture, deconnecter() avec timeout thread + force del pour libérer le port USB - main.py: thread preview anti-crash (outer try/except), reconnexion DSLR même si preview actif, notification camera_erreur après échec capture, intégration module éclairage (analyse luminosité visage via WS) - printer.py: mode pellicule (-2up) ignoré par le cadre imposé événement - eclairage.py: nouveau module analyse luminosité visage (Haar + HSV) - camera.js: indicateur éclairage temps réel (score/100 + visages) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@ from backend.evenements import (
|
||||
lister_cadres_event, set_cadre_event, cadre_actif_pour_impression,
|
||||
)
|
||||
from backend.evenements import DOSSIER_EVENEMENTS
|
||||
from backend.eclairage import analyser_frame as analyser_eclairage, dernier_resultat as dernier_eclairage
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(message)s")
|
||||
log = logging.getLogger("photobooth")
|
||||
@@ -63,17 +64,21 @@ def _thread_preview():
|
||||
Tourne TOUJOURS quand le DSLR est connecte pour maintenir le miroir leve."""
|
||||
global _derniere_frame_preview
|
||||
while True:
|
||||
if capture_en_cours or camera.mode != "gphoto2" or not camera.connectee:
|
||||
time.sleep(0.05)
|
||||
continue
|
||||
try:
|
||||
donnees = camera.preview()
|
||||
with _preview_lock:
|
||||
_derniere_frame_preview = donnees
|
||||
except Exception:
|
||||
with _preview_lock:
|
||||
_derniere_frame_preview = None
|
||||
time.sleep(0.033) # ~30 fps max
|
||||
if capture_en_cours or camera.mode != "gphoto2" or not camera.connectee:
|
||||
time.sleep(0.05)
|
||||
continue
|
||||
try:
|
||||
donnees = camera.preview()
|
||||
with _preview_lock:
|
||||
_derniere_frame_preview = donnees
|
||||
except Exception:
|
||||
with _preview_lock:
|
||||
_derniere_frame_preview = None
|
||||
time.sleep(0.033) # ~30 fps max
|
||||
except Exception as e:
|
||||
log.error(f"_thread_preview crash: {e}")
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
async def _pusher_preview():
|
||||
@@ -103,6 +108,10 @@ async def _pusher_preview():
|
||||
loop = asyncio.get_event_loop()
|
||||
b64 = await loop.run_in_executor(None, lambda d=donnees: base64.b64encode(d).decode("ascii"))
|
||||
await diffuser_ws({"type": "preview", "image": f"data:image/jpeg;base64,{b64}"})
|
||||
if not capture_en_cours:
|
||||
eclairage = await loop.run_in_executor(None, analyser_eclairage, donnees)
|
||||
if eclairage is not None:
|
||||
await diffuser_ws({"type": "eclairage", **eclairage})
|
||||
|
||||
def _usb_reset_canon():
|
||||
"""Reset USB ioctl du Canon EOS (vendor 04a9) pour débloquer un port stall."""
|
||||
@@ -206,7 +215,7 @@ async def surveiller_dslr():
|
||||
_echecs_connexion = 0
|
||||
else:
|
||||
_dslr_erreurs += 1
|
||||
if _dslr_erreurs >= 3 and not _preview_actif:
|
||||
if _dslr_erreurs >= 3:
|
||||
log.warning(f"camera_erreur : DSLR preview KO depuis {_dslr_erreurs * 5}s, reconnexion forcee...")
|
||||
_dslr_erreurs = 0
|
||||
_echecs_connexion += 1
|
||||
@@ -409,6 +418,9 @@ async def api_capturer():
|
||||
capture_en_cours = False
|
||||
log.info(f"Capture terminée : {chemin}")
|
||||
if chemin is None:
|
||||
if camera.mode == "gphoto2" and not camera.preview_dslr_ok:
|
||||
log.warning("Capture echouee + preview KO → reconnexion DSLR imminente")
|
||||
await diffuser_ws({"type": "camera_erreur", "message": "Erreur capture — reconnexion en cours"})
|
||||
return JSONResponse({"erreur": "Echec capture"}, status_code=500)
|
||||
nom = chemin.name
|
||||
|
||||
@@ -547,6 +559,14 @@ async def api_camera_statut():
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/eclairage")
|
||||
async def api_eclairage():
|
||||
resultat = dernier_eclairage()
|
||||
if resultat is None:
|
||||
return {"score": -1, "action": "no_data", "detail": "Aucune analyse disponible"}
|
||||
return resultat
|
||||
|
||||
|
||||
@app.get("/api/camera/debug")
|
||||
async def api_camera_debug():
|
||||
"""Diagnostic complet de la caméra (preview, viewfinder, erreurs gphoto2)."""
|
||||
|
||||
Reference in New Issue
Block a user