feat: video analysis switched to push mode (upload whole video, sync response)
Rationale: Oracle cannot reach NAS (Tailscale userspace mode on NAS, no TUN), the old pull+webhook design requires Edge to download video from NAS and callback to NAS - both blocked. New design is one-way NAS -> Oracle: - FAM-Edge: new POST /api/edge/video/push endpoint accepts multipart video upload, reuses existing OpenCV scene-change keyframe selection, analyzes synchronously and returns the result payload directly in the HTTP response (no webhook callback). Old /api/edge/video/analyze kept for compatibility. - FAM-Edge: VideoPreprocessor.save_upload() saves the uploaded file - FAM-Edge: AIOrchestrator.process_push_task() runs the full pipeline (health check -> extract -> select -> compress -> VLM -> fusion) and returns callback-style payload dict - FAM-Core: Dispatcher rewritten to push mode - reads local video file, uploads with task metadata (camera_name, event_start_time from file mtime, known_members_context), applies the result to DB via shared event_receiver.apply_success_event() - FAM-Core: event_receiver success logic extracted into reusable apply_success_event() (used by both webhook route and dispatcher) - config: edge_url -> /api/edge/video/push, push_timeout 1800s, gunicorn Edge timeout raised to 1800s for long synchronous analysis
This commit is contained in:
@@ -9,6 +9,7 @@ from flask import Blueprint, request, jsonify
|
||||
|
||||
from ..logger import setup_logger
|
||||
from ..ai_orchestrator.orchestrator import AIOrchestrator
|
||||
from ..video_preprocessor.preprocessor import VideoPreprocessor
|
||||
|
||||
logger = setup_logger('fam-edge.api_gateway')
|
||||
|
||||
@@ -71,6 +72,63 @@ def receive_task():
|
||||
return jsonify({"status": "accepted", "task_id": task_id}), 202
|
||||
|
||||
|
||||
@api_bp.route('/api/edge/video/push', methods=['POST'])
|
||||
def receive_push_task():
|
||||
"""推送模式:接收 multipart 视频上传,同步分析,结果随 HTTP 响应返回
|
||||
|
||||
NAS 无法被 Oracle 反向访问(Tailscale 不通),因此改为 NAS 主动上传视频,
|
||||
Edge 用 OpenCV 场景变化检测抽帧后分析,摘要直接放在响应里带回。
|
||||
"""
|
||||
global _currently_processing
|
||||
|
||||
task_id_raw = request.form.get('task_id')
|
||||
file = request.files.get('video')
|
||||
if not task_id_raw or not file:
|
||||
return jsonify({"error": "缺少必填字段: task_id, video"}), 400
|
||||
|
||||
try:
|
||||
task_id = int(task_id_raw)
|
||||
except ValueError:
|
||||
return jsonify({"error": "task_id 必须是整数"}), 400
|
||||
|
||||
logger.info(f"[task_id={task_id}] 收到推送任务: {file.filename}")
|
||||
|
||||
# 并发控制(同步处理,占用整个请求周期)
|
||||
with _current_task_lock:
|
||||
if _currently_processing:
|
||||
logger.warning(f"[task_id={task_id}] 已有任务处理中,返回 429")
|
||||
return jsonify({"error": "Queue full", "retry_after": 60}), 429
|
||||
_currently_processing = True
|
||||
|
||||
preprocessor = None
|
||||
try:
|
||||
preprocessor = VideoPreprocessor(task_id)
|
||||
video_path = preprocessor.save_upload(file)
|
||||
|
||||
task_data = {
|
||||
"task_id": task_id,
|
||||
"camera_name": request.form.get('camera_name', ''),
|
||||
"event_start_time": request.form.get('event_start_time', ''),
|
||||
"event_end_time": request.form.get('event_end_time', ''),
|
||||
"known_members_context": request.form.get('known_members_context', ''),
|
||||
}
|
||||
|
||||
result = get_orchestrator().process_push_task(task_data, video_path, preprocessor)
|
||||
return jsonify(result), 200
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[task_id={task_id}] 推送任务异常: {e}", exc_info=True)
|
||||
return jsonify({
|
||||
"task_id": task_id, "status": "failed",
|
||||
"failure_stage": "upload", "error_message": str(e)
|
||||
}), 200
|
||||
finally:
|
||||
if preprocessor is not None:
|
||||
preprocessor.cleanup()
|
||||
with _current_task_lock:
|
||||
_currently_processing = False
|
||||
|
||||
|
||||
@api_bp.route('/health', methods=['GET'])
|
||||
def health():
|
||||
"""健康检查"""
|
||||
|
||||
Reference in New Issue
Block a user