[功能] 人物管理按运动视频重设计 - 人物卡新增「运动片段」区块(缩略图/时间/摘要/事件数,点击跳时间轴定位); 新增 GET /api/ui/people/clips 后端接口(按 label/canonical_name 查关联运动片段,含 first_ts/clip_events); Timeline 支持 ?video= 定位

This commit is contained in:
ericwyuan
2026-08-22 12:55:30 +08:00
parent ffcc292cce
commit 4cf4fc4bc2
5 changed files with 121 additions and 2 deletions

View File

@@ -203,6 +203,54 @@ def get_sync_events_for_video(video_id: int) -> List[Dict]:
conn.close()
def get_sync_people_clips(label: str, limit: int = 10) -> List[Dict]:
"""某人物label 或 canonical_name出现过的运动片段列表。
匹配label 本身 + 其 canonical_name 下全部 label命中 sync_events 的
person_list_jsonJSON_CONTAINS→ 关联 video优先运动片段 motion_
返回按 event_start_time 倒序,含 first_ts该人物在片段内最早事件 ts
前端缩略图定位用)与 clip_events片段内该人物事件数
"""
conn = get_conn()
try:
cur = conn.cursor(pymysql.cursors.DictCursor)
labels = {label}
cur.execute(
"SELECT label, canonical_name FROM sync_people WHERE label=%s OR canonical_name=%s",
(label, label))
for r in cur.fetchall():
cn = r.get('canonical_name')
if cn:
cur.execute("SELECT label FROM sync_people WHERE canonical_name=%s", (cn,))
labels.update(x['label'] for x in cur.fetchall())
clips = []
seen = set()
for lb in sorted(labels):
cur.execute(
"""SELECT DISTINCT v.id AS video_id, v.filename, v.event_start_time,
v.duration_sec, v.summary_json, v.camera_name,
(SELECT MIN(e2.ts) FROM sync_events e2
WHERE e2.video_id=v.id AND e2.person_list_json IS NOT NULL
AND JSON_CONTAINS(e2.person_list_json, JSON_QUOTE(%s), '$')) AS first_ts,
(SELECT COUNT(*) FROM sync_events e3
WHERE e3.video_id=v.id AND e3.person_list_json IS NOT NULL
AND JSON_CONTAINS(e3.person_list_json, JSON_QUOTE(%s), '$')) AS clip_events
FROM sync_events e
JOIN sync_videos v ON e.video_id=v.id
WHERE e.person_list_json IS NOT NULL
AND JSON_CONTAINS(e.person_list_json, JSON_QUOTE(%s), '$')
AND v.status='done'""",
(lb, lb, lb))
for row in cur.fetchall():
if row['video_id'] not in seen:
seen.add(row['video_id'])
clips.append(row)
clips.sort(key=lambda x: x.get('event_start_time') or '', reverse=True)
return clips[:int(limit)]
finally:
conn.close()
def query_sync_events_for_person_date(person: str, date_str: str) -> List[Dict]:
"""问答上下文:某人在某天的事件。

View File

@@ -104,6 +104,22 @@ def people():
return jsonify({"groups": _ser(out), "all_labels": all_labels}), 200
@ui_bp.route('/api/ui/people/clips', methods=['GET'])
def people_clips():
"""某人物出现过的运动片段列表(人物卡「运动片段」区块)。
按 label/canonical_name 匹配 sync_events.person_list_json → 关联视频
(运动片段优先)。返回片段 video_id/filename/event_start_time/duration/
summary/camera_name + first_ts缩略图定位+ clip_events片段内事件数
"""
label = request.args.get('label') or ''
if not label:
return jsonify({"error": "缺少 label 参数"}), 400
limit = min(20, request.args.get('limit', 10, type=int) or 10)
clips = db_layer.get_sync_people_clips(label, limit)
return jsonify({"label": label, "clips": _ser(clips)}), 200
@ui_bp.route('/api/ui/attention-events', methods=['GET'])
def attention_events():
"""需关注事件列表(统计图表页),已按人物去重规则清洗好 people 字段。"""