This commit is contained in:
pporcheret
2026-10-26 00:06:24 +01:00
parent c9c3d5f223
commit cd37ac3c0a
207 changed files with 19205 additions and 54 deletions
@@ -0,0 +1,71 @@
"""Accès simple à la caméra d'Alfred, avec aperçu MJPEG activé à la demande.
Une seule lecture par instance Alfred. La vidéo ne démarre que lorsque
l'utilisateur clique sur Activer, pour éviter de monopoliser la caméra.
"""
import os
import threading
import time
_CAMERA_LOCK = threading.Lock()
def snapshot(path, index=0):
import cv2
if not _CAMERA_LOCK.acquire(blocking=False):
raise RuntimeError('Caméra déjà utilisée par la prévisualisation Alfred')
try:
cap = cv2.VideoCapture(index)
try:
ok, frame = cap.read()
finally:
cap.release()
if not ok:
raise RuntimeError('Impossible de lire la caméra locale')
cv2.imwrite(path, frame)
return path
finally:
_CAMERA_LOCK.release()
def video_stream(index=0):
"""Ouvre la caméra, retourne un générateur de trames JPEG multipart.
L'appelant doit retourner ce générateur dans une réponse Flask en streaming.
L'absence de cv2 ou de webcam déclenche une exception avant HTTP 200.
"""
import cv2
if not _CAMERA_LOCK.acquire(blocking=False):
raise RuntimeError('Prévisualisation vidéo déjà active')
cap = None
try:
cap = cv2.VideoCapture(index)
if not cap.isOpened():
cap.release()
raise RuntimeError(f'Caméra {index} indisponible')
cap.set(cv2.CAP_PROP_FRAME_WIDTH, int(os.getenv('CAMERA_WIDTH', '640')))
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, int(os.getenv('CAMERA_HEIGHT', '480')))
fps = min(15.0, max(1.0, float(os.getenv('CAMERA_FPS', '8'))))
quality = min(95, max(35, int(os.getenv('CAMERA_JPEG_QUALITY', '75'))))
except Exception:
if cap is not None:
cap.release()
_CAMERA_LOCK.release()
raise
def frames():
try:
while True:
ok, frame = cap.read()
if not ok:
break
ok_jpeg, encoded = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, quality])
if ok_jpeg:
yield b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + encoded.tobytes() + b'\r\n'
time.sleep(1.0 / fps)
finally:
cap.release()
_CAMERA_LOCK.release()
return frames()