Files
Alfredv2/alfred/app/services/dona_face.py
T
2026-10-30 22:10:03 +01:00

79 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Reconnaissance faciale sur action utilisateur, via MCP Hermes existant.
Aucun visage enregistré; capture temporaire effacée automatiquement.
"""
from __future__ import annotations
from pathlib import Path
import os
import re
import tempfile
import threading
from urllib.parse import urlsplit
from flask import jsonify, request
_FACE_BUSY = threading.Lock()
_FACE_COMMAND = re.compile(
r"(?:qui\s+est\s+(?:devant|sur)\s+(?:la\s+)?cam[eé]ra|"
r"(?:reconnais|reconna[iî]tre|identifie|identifier)\s+(?:la|les|une|des)?\s*personne[s]?\s+(?:devant|sur)\s+(?:la\s+)?cam[eé]ra|"
r"(?:reconnaissance|identification)\s+faciale)", re.I)
def is_face_request(text: str) -> bool:
return bool(_FACE_COMMAND.search(str(text or '')))
def recognize_once(ask_hermes):
"""Appelle le MCP via Hermes dans le même compte; ne suppose pas le résultat."""
from services.camera import snapshot
if not _FACE_BUSY.acquire(blocking=False):
return "Une reconnaissance faciale est déjà en cours."
try:
# Lecture à la demande; autre flux webcam => message clair.
with tempfile.TemporaryDirectory(prefix='dona-face-') as work:
path = Path(work) / 'capture.jpg'
snapshot(str(path), index=int(os.getenv('CAMERA_INDEX', '0')))
if not path.is_file() or not path.stat().st_size:
raise RuntimeError('Aucune image exploitable de la caméra.')
prompt = (
'Utilise le MCP face_recognition, outil identify_people, pour analyser '
f'le fichier image local {path}. Cette image vient de la caméra de Dona. '
'Donne uniquement les identités effectivement reconnues par le résultat de l’outil. '
'Si aucune correspondance fiable ou si l’outil n’est pas accessible, dis-le explicitement. '
'N’invente jamais d’identité et ne crée pas de nouvelle fiche de personne.'
)
response = ask_hermes(prompt)
if isinstance(response, dict):
response = response.get('speech') or response.get('text') or ''
return str(response or 'La reconnaissance faciale n’a renvoyé aucun résultat.').strip()
finally:
_FACE_BUSY.release()
def register_face(app, socketio, ask_hermes, conversation_lock):
@app.post('/api/dona/face/identify')
def dona_face_identify():
# Protège la capture contre un déclenchement intersite involontaire.
if request.headers.get('X-Alfred-Request') != '1':
return jsonify(error='Requête non autorisée.'), 403
origin = request.headers.get('Origin')
if origin and urlsplit(origin).netloc != request.host:
return jsonify(error='Origine non autorisée.'), 403
if not conversation_lock.acquire(blocking=False):
return jsonify(error='Dona traite déjà une autre demande.'), 409
def job():
try:
socketio.emit('dona_face_status', {'state': 'analyzing', 'message': 'Analyse du visage en cours…'})
text = recognize_once(ask_hermes)
socketio.emit('dona_face_result', {'ok': True, 'text': text})
except Exception as exc:
app.logger.warning('Reconnaissance faciale indisponible: %s', exc)
socketio.emit('dona_face_result', {'ok': False,
'text': 'Impossible d’analyser la caméra. Ferme son aperçu si elle est déjà utilisée, puis réessaie.'})
finally:
conversation_lock.release()
threading.Thread(target=job, daemon=True, name='dona-face-manual').start()
return jsonify(ok=True, started=True), 202