feat(人物管理): 头像优先选有人脸帧 + 人物合并到操作
1. person_photo 读 event 的 meta.json(face_count),优先挑人脸数最多的帧做头像,无 meta 时按时间序第一张兜底 2. 人物卡片新增「合并到」下拉框+按钮,调 /api/member/merge(source=当前人物,target=所选人物),展示替换明细数 3. 至此设计文档 8 项需求全部落地(Edge 画框/mark_frames 端点/meta.json/backfill/merge_member/merge 路由/UI 头像+合并)
This commit is contained in:
@@ -810,7 +810,31 @@ elif page == "👤 人物管理":
|
||||
f'<span style="color:#64748b;">(与事件时间轴"出现人物"统计一致)</span></div>',
|
||||
unsafe_allow_html=True)
|
||||
|
||||
_meta_cache = {}
|
||||
|
||||
def _load_meta(event_id):
|
||||
"""读 event 的 meta.json(frame_index -> face_count),缺失返回空 dict"""
|
||||
if event_id not in _meta_cache:
|
||||
mpath = os.path.join(FRAME_DIR, f'event_{event_id}', 'meta.json')
|
||||
try:
|
||||
with open(mpath) as f:
|
||||
_meta_cache[event_id] = json.load(f) or {}
|
||||
except (OSError, ValueError):
|
||||
_meta_cache[event_id] = {}
|
||||
return _meta_cache[event_id]
|
||||
|
||||
def person_photo(frames):
|
||||
"""头像优先挑有人脸的帧(face_count 最大),其次按时间序第一张"""
|
||||
best, best_faces = None, -1
|
||||
for eid, fidx, _ in frames:
|
||||
meta = _load_meta(eid)
|
||||
faces = int(meta.get(str(fidx), 0) or 0)
|
||||
if faces > best_faces:
|
||||
b64 = load_frame_b64(eid, fidx)
|
||||
if b64:
|
||||
best, best_faces = b64, faces
|
||||
if best:
|
||||
return best
|
||||
for eid, fidx, _ in frames:
|
||||
b64 = load_frame_b64(eid, fidx)
|
||||
if b64:
|
||||
@@ -835,6 +859,25 @@ elif page == "👤 人物管理":
|
||||
except Exception as e:
|
||||
st.error(f"异常: {e}")
|
||||
|
||||
def do_merge(src_key, tgt_key):
|
||||
"""把 src 并入 tgt(判断是同一人时)"""
|
||||
try:
|
||||
resp = requests.post(
|
||||
f"{_core_url}/api/member/merge",
|
||||
json={"source": src_key, "target": tgt_key},
|
||||
timeout=15)
|
||||
if resp.status_code == 200:
|
||||
result = resp.json()
|
||||
st.success(
|
||||
f"已合并:{src_key} → {result.get('target', tgt_key)},"
|
||||
f"明细 {result.get('updated_event_details_count', 0)} 条 / "
|
||||
f"事件 {result.get('updated_monitor_events_count', 0)} 条")
|
||||
st.rerun()
|
||||
else:
|
||||
st.error(f"合并失败: {resp.status_code} {resp.text[:150]}")
|
||||
except Exception as e:
|
||||
st.error(f"合并异常: {e}")
|
||||
|
||||
if not person_frames:
|
||||
st.markdown(
|
||||
'<div class="fam-empty"><span class="fe-ico">👤</span>'
|
||||
@@ -892,6 +935,16 @@ elif page == "👤 人物管理":
|
||||
do_name(label, new_name.strip())
|
||||
else:
|
||||
st.warning("请输入名字")
|
||||
merge_targets = [k for k in person_frames if k != key]
|
||||
if merge_targets:
|
||||
merge_to = st.selectbox(
|
||||
"合并到", options=[""] + merge_targets,
|
||||
key=f"mg_{label}", label_visibility="collapsed",
|
||||
placeholder="合并到其他人物…")
|
||||
if merge_to:
|
||||
if st.button("合并", key=f"mbtn_{label}",
|
||||
use_container_width=True):
|
||||
do_merge(key, merge_to)
|
||||
st.markdown('<div style="height:10px"></div>', unsafe_allow_html=True)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user