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:
ericwyuan
2026-08-21 00:57:20 +08:00
parent 43b3178a9d
commit 73f6c77544

View File

@@ -810,7 +810,31 @@ elif page == "👤 人物管理":
f'<span style="color:#64748b;">(与事件时间轴"出现人物"统计一致)</span></div>', f'<span style="color:#64748b;">(与事件时间轴"出现人物"统计一致)</span></div>',
unsafe_allow_html=True) unsafe_allow_html=True)
_meta_cache = {}
def _load_meta(event_id):
"""读 event 的 meta.jsonframe_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): 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: for eid, fidx, _ in frames:
b64 = load_frame_b64(eid, fidx) b64 = load_frame_b64(eid, fidx)
if b64: if b64:
@@ -835,6 +859,25 @@ elif page == "👤 人物管理":
except Exception as e: except Exception as e:
st.error(f"异常: {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: if not person_frames:
st.markdown( st.markdown(
'<div class="fam-empty"><span class="fe-ico">👤</span>' '<div class="fam-empty"><span class="fe-ico">👤</span>'
@@ -892,6 +935,16 @@ elif page == "👤 人物管理":
do_name(label, new_name.strip()) do_name(label, new_name.strip())
else: else:
st.warning("请输入名字") 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) st.markdown('<div style="height:10px"></div>', unsafe_allow_html=True)