v3
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user