feat(v3): 人物模块重构 - 大模型结构化特征值替代 OpenCV 帧定位。prompt 增加 person_appearances(uid+7特征+action)并重写合并 prompt(稳定特征优先比对);gemini 透传特征字段;oracle_db events/people 加 person_appearances_json/features_json/display_uid + _merge_features;video_processor 去 cv2 改 ffprobe 校验、_store_result 聚合 uid 特征落 people、删缩略图/事件截图;person_service 聚合特征后基于特征文本 LLM 合并;api_gateway 删 3 个图接口;NAS 镜像+DDL 加新字段;fam-ui 特征卡替代头像、事件时间线人物特征块
This commit is contained in:
@@ -1,18 +1,23 @@
|
||||
"""
|
||||
API-Gateway - Flask 蓝图(新架构 v2)
|
||||
API-Gateway - Flask 蓝图(新架构 v3)
|
||||
|
||||
端点:
|
||||
GET /api/oracle/sync NAS 每 30 分钟拉取增量(since + token 校验)
|
||||
POST /api/oracle/people/correct NAS 推送手动命名校正(label -> canonical_name)
|
||||
POST /api/edge/chat/ask 智能问答编排(Gemini -> NVIDIA -> Ollama)
|
||||
GET /api/oracle/activity 实时服务状态 + 最近活动流
|
||||
GET /health 健康检查
|
||||
|
||||
已移除(v3 去除帧图/avatar 依赖,改用大模型特征值):
|
||||
/api/oracle/video/<id>/thumb, /api/oracle/event/<id>/thumb,
|
||||
/api/oracle/person/avatar —— 不再生成 jpg,UI 读 sync_people.features_json
|
||||
|
||||
已移除(旧推送/分块/队列模式): /video/push, /enqueue, /chunk, /assemble,
|
||||
/results, /queue/stats, /mark_frames
|
||||
"""
|
||||
import os
|
||||
|
||||
from flask import Blueprint, request, jsonify, send_file
|
||||
from flask import Blueprint, request, jsonify
|
||||
|
||||
from ..logger import setup_logger
|
||||
from .. import state
|
||||
@@ -121,61 +126,6 @@ def chat_ask():
|
||||
return jsonify({"answer": answer, "provider": provider}), 200
|
||||
|
||||
|
||||
@api_bp.route('/api/oracle/video/<int:video_id>/thumb', methods=['GET'])
|
||||
def video_thumb(video_id):
|
||||
"""返回视频首帧缩略图(token 校验,不公网裸奔)。
|
||||
|
||||
缩略图由 video_processor 处理成功后生成于 /opt/fam-edge/thumbs/{video_id}.jpg。
|
||||
"""
|
||||
if not _check_token():
|
||||
return jsonify({"error": "unauthorized"}), 401
|
||||
path = f"/opt/fam-edge/thumbs/{video_id}.jpg"
|
||||
if not os.path.isfile(path):
|
||||
return jsonify({"error": "thumb_not_found"}), 404
|
||||
return send_file(path, mimetype='image/jpeg')
|
||||
|
||||
|
||||
@api_bp.route('/api/oracle/event/<int:event_id>/thumb', methods=['GET'])
|
||||
def event_thumb(event_id):
|
||||
"""返回事件对应时间点的画面截图(token 校验)。
|
||||
|
||||
由 video_processor 处理成功后按事件时间戳定位视频帧生成:
|
||||
/opt/fam-edge/thumbs/ev_{event_id}.jpg
|
||||
截图缺失返回 404(前端隐藏,不拿视频首帧冒充该事件画面)。
|
||||
"""
|
||||
if not _check_token():
|
||||
return jsonify({"error": "unauthorized"}), 401
|
||||
path = f"/opt/fam-edge/thumbs/ev_{event_id}.jpg"
|
||||
if not os.path.isfile(path):
|
||||
return jsonify({"error": "thumb_not_found"}), 404
|
||||
return send_file(path, mimetype='image/jpeg')
|
||||
|
||||
|
||||
@api_bp.route('/api/oracle/person/avatar', methods=['GET'])
|
||||
def person_avatar():
|
||||
"""人物代表画面(头像):在该人物出现的所有事件中,返回第一个有截图的事件画面。
|
||||
|
||||
人物出现在多个视频/事件中,任选一个截图存在的即可(比视频首帧准确——
|
||||
首帧可能根本没有该人物)。
|
||||
参数: label=人物标识(如 人物A);匹配 person_list_json 数组中的元素。
|
||||
"""
|
||||
if not _check_token():
|
||||
return jsonify({"error": "unauthorized"}), 401
|
||||
label = (request.args.get('label') or '').strip()
|
||||
if not label:
|
||||
return jsonify({"error": "缺少 label"}), 400
|
||||
# JSON 数组元素精确匹配(person_list_json 形如 ["人物A","人物C"])
|
||||
pat = f'%"{label}"%'
|
||||
rows = state.get_db()._conn.execute(
|
||||
"SELECT id FROM events WHERE person_list_json LIKE ? "
|
||||
"ORDER BY id DESC", (pat,)).fetchall()
|
||||
for r in rows:
|
||||
p = f"/opt/fam-edge/thumbs/ev_{r['id']}.jpg"
|
||||
if os.path.isfile(p):
|
||||
return send_file(p, mimetype='image/jpeg')
|
||||
return jsonify({"error": "no_avatar_found"}), 404
|
||||
|
||||
|
||||
@api_bp.route('/api/oracle/activity', methods=['GET'])
|
||||
def activity():
|
||||
"""实时服务状态 + 最近活动流(token 校验)。
|
||||
|
||||
Reference in New Issue
Block a user