79 lines
3.6 KiB
Python
79 lines
3.6 KiB
Python
"""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
|