"""本地事件展示网页:纯只读查询 analysis.db + 播放原始录像文件, 不写入任何数据,可以在 analyze.py 跑到一半时随时启动查看已完成的部分。 """ from flask import Flask, jsonify, render_template, request, send_file from analysis.config import PROJECT_ROOT from analysis.db import get_connection from webapp import transcode_cache app = Flask(__name__) EVENT_TYPES = ["person_presence", "fall", "activity"] @app.route("/") def index(): return render_template("index.html") @app.route("/api/events") def api_events(): event_type = request.args.get("event_type") capture_date = request.args.get("date") # YYYYMMDD query = """ SELECT e.id, e.video_id, e.event_type, e.label, e.start_offset_sec, e.end_offset_sec, e.timestamp_abs, e.confidence, e.thumbnail_path, v.capture_date, v.file_name FROM events e JOIN videos v ON v.id = e.video_id WHERE 1=1 """ params = [] if event_type in EVENT_TYPES: query += " AND e.event_type = ?" params.append(event_type) if capture_date: query += " AND v.capture_date = ?" params.append(capture_date) query += " ORDER BY e.timestamp_abs DESC, e.id DESC LIMIT 500" with get_connection() as conn: rows = conn.execute(query, params).fetchall() events = [ { "id": r["id"], "video_id": r["video_id"], "event_type": r["event_type"], "label": r["label"], "start_offset_sec": r["start_offset_sec"], "end_offset_sec": r["end_offset_sec"], "timestamp_abs": r["timestamp_abs"], "confidence": r["confidence"], "capture_date": r["capture_date"], "file_name": r["file_name"], "thumbnail_url": f"/media/thumb/{r['id']}" if r["thumbnail_path"] else None, } for r in rows ] return jsonify(events) @app.route("/api/stats") def api_stats(): with get_connection() as conn: rows = conn.execute( "SELECT event_type, COUNT(*) AS c FROM events GROUP BY event_type" ).fetchall() return jsonify({r["event_type"]: r["c"] for r in rows}) @app.route("/media//stream") def media_stream(video_id): with get_connection() as conn: row = conn.execute("SELECT file_path FROM videos WHERE id = ?", (video_id,)).fetchone() if row is None: return "视频不存在", 404 try: play_path = transcode_cache.ensure_transcoded(video_id, PROJECT_ROOT / row["file_path"]) except RuntimeError as e: return f"转码失败:{e}", 500 return send_file(play_path, conditional=True) @app.route("/api/cache/stats") def api_cache_stats(): return jsonify(transcode_cache.cache_stats()) @app.route("/api/cache/clear", methods=["POST"]) def api_cache_clear(): return jsonify(transcode_cache.clear_cache()) @app.route("/media/thumb/") def media_thumb(event_id): with get_connection() as conn: row = conn.execute("SELECT thumbnail_path FROM events WHERE id = ?", (event_id,)).fetchone() if row is None or not row["thumbnail_path"]: return "缩略图不存在", 404 return send_file(PROJECT_ROOT / row["thumbnail_path"], conditional=True) if __name__ == "__main__": app.run(host="127.0.0.1", port=5050, debug=False)