fix(头像): 人物头像改为'出现事件画面'而非视频首帧 - 新增 /api/oracle/person/avatar(在该人物所有出现事件中返回第一个有截图的事件画面,JSON 精确匹配);撤销 event_thumb 的视频首帧兜底(首帧可能无人/非本人);fam-ui 人物页改调 avatar 接口
This commit is contained in:
@@ -141,27 +141,41 @@ def event_thumb(event_id):
|
|||||||
|
|
||||||
由 video_processor 处理成功后按事件时间戳定位视频帧生成:
|
由 video_processor 处理成功后按事件时间戳定位视频帧生成:
|
||||||
/opt/fam-edge/thumbs/ev_{event_id}.jpg
|
/opt/fam-edge/thumbs/ev_{event_id}.jpg
|
||||||
若该事件截图缺失(如旧事件/未生成),兜底返回所属视频的首帧缩略图
|
截图缺失返回 404(前端隐藏,不拿视频首帧冒充该事件画面)。
|
||||||
thumbs/{video_id}.jpg,避免前端图片空白。
|
|
||||||
"""
|
"""
|
||||||
if not _check_token():
|
if not _check_token():
|
||||||
return jsonify({"error": "unauthorized"}), 401
|
return jsonify({"error": "unauthorized"}), 401
|
||||||
path = f"/opt/fam-edge/thumbs/ev_{event_id}.jpg"
|
path = f"/opt/fam-edge/thumbs/ev_{event_id}.jpg"
|
||||||
if not os.path.isfile(path):
|
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 jsonify({"error": "thumb_not_found"}), 404
|
||||||
return send_file(path, mimetype='image/jpeg')
|
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'])
|
@api_bp.route('/api/oracle/activity', methods=['GET'])
|
||||||
def activity():
|
def activity():
|
||||||
"""实时服务状态 + 最近活动流(token 校验)。
|
"""实时服务状态 + 最近活动流(token 校验)。
|
||||||
|
|||||||
@@ -807,19 +807,21 @@ elif page == "👤 人物管理":
|
|||||||
f'border:1px solid #713f12;padding:1px 8px;border-radius:10px;'
|
f'border:1px solid #713f12;padding:1px 8px;border-radius:10px;'
|
||||||
f'margin-left:8px;">未命名</span>')
|
f'margin-left:8px;">未命名</span>')
|
||||||
label_str = ' · '.join(g['labels'])
|
label_str = ' · '.join(g['labels'])
|
||||||
# 人物代表画面:取该身份任一 label 出现事件的截图(头像)
|
# 人物代表画面:Oracle 端在该人物出现的所有事件中挑第一个有截图的事件画面
|
||||||
|
# (该人物在多个视频多次出现,肯定能找到出现时刻的画面,不用视频首帧冒充)
|
||||||
rep_img = ''
|
rep_img = ''
|
||||||
if _oracle_url:
|
if _oracle_url:
|
||||||
for lb in g['labels']:
|
for lb in g['labels']:
|
||||||
try:
|
try:
|
||||||
cursor.execute(
|
rp = requests.get(
|
||||||
"SELECT e.id FROM sync_events e WHERE e.person_list_json LIKE %s "
|
f"{_oracle_url}/api/oracle/person/avatar",
|
||||||
"ORDER BY e.id LIMIT 1", (f'%{lb}%',))
|
params={"label": lb, "token": _oracle_token},
|
||||||
r2 = cursor.fetchone()
|
timeout=10)
|
||||||
if r2 and r2['id']:
|
if rp.status_code == 200:
|
||||||
|
import urllib.parse
|
||||||
rep_img = (
|
rep_img = (
|
||||||
f'<img src="{_oracle_url}/api/oracle/event/{r2["id"]}/thumb'
|
f'<img src="{_oracle_url}/api/oracle/person/avatar'
|
||||||
f'?token={_oracle_token}" '
|
f'?label={urllib.parse.quote(lb)}&token={_oracle_token}" '
|
||||||
f'style="width:140px;height:94px;object-fit:cover;border-radius:8px;'
|
f'style="width:140px;height:94px;object-fit:cover;border-radius:8px;'
|
||||||
f'border:1px solid #1e293b;margin:8px 0;" '
|
f'border:1px solid #1e293b;margin:8px 0;" '
|
||||||
f'onerror="this.style.display=\'none\'"/>')
|
f'onerror="this.style.display=\'none\'"/>')
|
||||||
|
|||||||
Reference in New Issue
Block a user