From 2ba347829162f838c2fb9ce3033bf377b36120e7 Mon Sep 17 00:00:00 2001 From: ericwyuan Date: Fri, 21 Aug 2026 16:13:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E5=A4=B4=E5=83=8F):=20=E4=BA=BA=E7=89=A9?= =?UTF-8?q?=E5=A4=B4=E5=83=8F=E6=94=B9=E4=B8=BA'=E5=87=BA=E7=8E=B0?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E7=94=BB=E9=9D=A2'=E8=80=8C=E9=9D=9E?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E9=A6=96=E5=B8=A7=20-=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20/api/oracle/person/avatar=EF=BC=88=E5=9C=A8=E8=AF=A5?= =?UTF-8?q?=E4=BA=BA=E7=89=A9=E6=89=80=E6=9C=89=E5=87=BA=E7=8E=B0=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E4=B8=AD=E8=BF=94=E5=9B=9E=E7=AC=AC=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E6=9C=89=E6=88=AA=E5=9B=BE=E7=9A=84=E4=BA=8B=E4=BB=B6=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=EF=BC=8CJSON=20=E7=B2=BE=E7=A1=AE=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=EF=BC=89=EF=BC=9B=E6=92=A4=E9=94=80=20event=5Fthumb=20?= =?UTF-8?q?=E7=9A=84=E8=A7=86=E9=A2=91=E9=A6=96=E5=B8=A7=E5=85=9C=E5=BA=95?= =?UTF-8?q?=EF=BC=88=E9=A6=96=E5=B8=A7=E5=8F=AF=E8=83=BD=E6=97=A0=E4=BA=BA?= =?UTF-8?q?/=E9=9D=9E=E6=9C=AC=E4=BA=BA=EF=BC=89=EF=BC=9Bfam-ui=20?= =?UTF-8?q?=E4=BA=BA=E7=89=A9=E9=A1=B5=E6=94=B9=E8=B0=83=20avatar=20?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/fam_edge/api_gateway/api_gateway.py | 38 +++++++++++++------ fam-ui/src/app.py | 18 +++++---- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/fam-edge/src/fam_edge/api_gateway/api_gateway.py b/fam-edge/src/fam_edge/api_gateway/api_gateway.py index c349c03..8050f22 100644 --- a/fam-edge/src/fam_edge/api_gateway/api_gateway.py +++ b/fam-edge/src/fam_edge/api_gateway/api_gateway.py @@ -141,27 +141,41 @@ def event_thumb(event_id): 由 video_processor 处理成功后按事件时间戳定位视频帧生成: /opt/fam-edge/thumbs/ev_{event_id}.jpg - 若该事件截图缺失(如旧事件/未生成),兜底返回所属视频的首帧缩略图 - thumbs/{video_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): - # 兜底:查事件所属视频,返回视频首帧缩略图 - try: - row = state.get_db()._conn.execute( - "SELECT video_id FROM events WHERE id=?", (event_id,)).fetchone() - if row and row['video_id']: - vpath = f"/opt/fam-edge/thumbs/{row['video_id']}.jpg" - if os.path.isfile(vpath): - return send_file(vpath, mimetype='image/jpeg') - except Exception: - pass 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 校验)。 diff --git a/fam-ui/src/app.py b/fam-ui/src/app.py index 4008e0f..7d9febf 100644 --- a/fam-ui/src/app.py +++ b/fam-ui/src/app.py @@ -807,19 +807,21 @@ elif page == "👤 人物管理": f'border:1px solid #713f12;padding:1px 8px;border-radius:10px;' f'margin-left:8px;">未命名') label_str = ' · '.join(g['labels']) - # 人物代表画面:取该身份任一 label 出现事件的截图(头像) + # 人物代表画面:Oracle 端在该人物出现的所有事件中挑第一个有截图的事件画面 + # (该人物在多个视频多次出现,肯定能找到出现时刻的画面,不用视频首帧冒充) rep_img = '' if _oracle_url: for lb in g['labels']: try: - cursor.execute( - "SELECT e.id FROM sync_events e WHERE e.person_list_json LIKE %s " - "ORDER BY e.id LIMIT 1", (f'%{lb}%',)) - r2 = cursor.fetchone() - if r2 and r2['id']: + rp = requests.get( + f"{_oracle_url}/api/oracle/person/avatar", + params={"label": lb, "token": _oracle_token}, + timeout=10) + if rp.status_code == 200: + import urllib.parse rep_img = ( - f'')