AnlaAnla 3 дней назад
Сommit
2298264219

+ 10 - 0
.idea/.gitignore

@@ -0,0 +1,10 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 已忽略包含查询文件的默认文件夹
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/

+ 8 - 0
.idea/MonitorCameraServer.iml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="jdk" jdkName="pytorch" jdkType="Python SDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 20 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,20 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
+      <Languages>
+        <language minSize="102" name="Python" />
+      </Languages>
+    </inspection_tool>
+    <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
+      <option name="ignoredErrors">
+        <list>
+          <option value="N803" />
+          <option value="N802" />
+          <option value="N806" />
+        </list>
+      </option>
+    </inspection_tool>
+  </profile>
+</component>

+ 6 - 0
.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>

+ 7 - 0
.idea/misc.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Black">
+    <option name="sdkName" value="C:\Code\Miniconda3" />
+  </component>
+  <component name="ProjectRootManager" version="2" project-jdk-name="pytorch" project-jdk-type="Python SDK" />
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/MonitorCameraServer.iml" filepath="$PROJECT_DIR$/.idea/MonitorCameraServer.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 0 - 0
app/__init__.py


+ 0 - 0
app/api/__init__.py


+ 142 - 0
app/api/camera.py

@@ -0,0 +1,142 @@
+import cv2
+import time
+from datetime import datetime
+import os
+import uuid
+from fastapi import APIRouter, HTTPException
+from pydantic import BaseModel
+from concurrent.futures import ThreadPoolExecutor
+from threading import Lock, Timer
+from app.core.config import settings
+
+# -------------------- 配置 --------------------
+
+executor = ThreadPoolExecutor(max_workers=4)
+lock = Lock()
+
+# 摄像头任务状态
+cam_status = {}
+
+router = APIRouter()
+
+
+class TaskRequest(BaseModel):
+    camera_id: str
+    task_name: str
+
+
+# -------------------- 录像函数 --------------------
+def record_camera(camera_id: str, task_name: str, output_file: str):
+    rtsp_url = settings.CAMERA_CONFIG[camera_id]
+    cap = cv2.VideoCapture(rtsp_url)
+    cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)  # 减少延迟
+
+    if not cap.isOpened():
+        raise RuntimeError(f"无法连接摄像头 {camera_id}")
+
+    frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
+    frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
+    fps = cap.get(cv2.CAP_PROP_FPS)
+    if fps <= 0: fps = 25
+
+    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
+    out = cv2.VideoWriter(output_file, fourcc, fps, (frame_width, frame_height))
+
+    print(f"[{camera_id}] 任务 {task_name} 开始录制...")
+
+    try:
+        while True:
+            ret, frame = cap.read()
+            if not ret:
+                print(f"[{camera_id}] 丢帧,停止录像")
+                break
+            out.write(frame)
+
+            # 检查任务是否被标记停止
+            with lock:
+                status = cam_status.get(camera_id)
+                if not status or status.get("stop_flag"):
+                    break
+            time.sleep(0.001)
+    finally:
+        cap.release()
+        out.release()
+        print(f"[{camera_id}] 任务 {task_name} 完成,文件: {output_file}")
+
+
+def stop_task_internal(camera_id: str):
+    with lock:
+        status = cam_status.get(camera_id)
+        if not status:
+            return
+        status["stop_flag"] = True
+        status["timer"].cancel()
+
+
+# -------------------- API接口 --------------------
+@router.post("/start_task")
+def start_task(req: TaskRequest):
+    camera_id = req.camera_id
+    task_name = req.task_name
+
+    if camera_id not in settings.CAMERA_CONFIG:
+        raise HTTPException(status_code=404, detail=f"摄像头 {camera_id} 未配置")
+
+    with lock:
+        if camera_id in cam_status:
+            return {"status": "running", "task_name": cam_status[camera_id]["task_name"]}
+
+        # 输出文件名
+        now_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
+        filename = f"{camera_id}_{task_name}_{now_time}.mp4"
+        output_file = os.path.join(settings.OUTPUT_DIR, filename)
+
+        # 记录状态
+        stop_flag = False
+        timer = Timer(settings.MAX_TASK_SECONDS, lambda: stop_task_internal(camera_id))
+        timer.start()
+        future = executor.submit(record_camera, camera_id, task_name, output_file)
+        cam_status[camera_id] = {
+            "task_name": task_name,
+            "start_time": time.time(),
+            "timer": timer,
+            "future": future,
+            "file": output_file,
+            "stop_flag": stop_flag
+        }
+
+    return {"status": "started", "file": output_file}
+
+
+@router.post("/stop_task")
+def stop_task(req: TaskRequest):
+    camera_id = req.camera_id
+    task_name = req.task_name
+
+    with lock:
+        status = cam_status.get(camera_id)
+        if not status:
+            return {"status": "not_running"}
+
+        if status["task_name"] != task_name:
+            return {"status": "task_mismatch", "running_task": status["task_name"]}
+
+        # 标记停止
+        status["stop_flag"] = True
+        status["timer"].cancel()
+
+    return {"status": "stopped", "file": status["file"]}
+
+
+@router.get("/status/{camera_id}")
+def status(camera_id: str):
+    with lock:
+        status = cam_status.get(camera_id)
+        if not status:
+            return {"status": "idle"}
+        return {
+            "status": "running",
+            "task_name": status["task_name"],
+            "start_time": status["start_time"],
+            "file": status["file"]
+        }

+ 0 - 0
app/core/__init__.py


+ 13 - 0
app/core/config.py

@@ -0,0 +1,13 @@
+class Settings():
+    CAMERA_CONFIG = {
+        "cam01": "rtsp://admin:password@192.168.1.10:554/live/ch0",
+        # 可扩展多个摄像头
+    }
+
+    OUTPUT_DIR = "./records"
+
+
+    MAX_TASK_SECONDS = 60 * 10  # 10分钟超时
+
+
+settings = Settings()

+ 22 - 0
app/main.py

@@ -0,0 +1,22 @@
+from fastapi import FastAPI
+from fastapi.middleware.cors import CORSMiddleware
+import os
+from app.core.config import settings
+
+from app.api import camera
+
+
+os.makedirs(settings.OUTPUT_DIR, exist_ok=True)
+
+app = FastAPI(title="监控摄像头服务")
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=["*"],
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"]
+)
+
+
+# 注册路由
+app.include_router(camera.router, prefix="/api/camera", tags=["Camera"])

+ 0 - 0
app/utils/__init__.py


+ 8 - 0
run_MonitorCameraServer.py

@@ -0,0 +1,8 @@
+import uvicorn
+import socket
+
+if __name__ == "__main__":
+    host_ip = socket.gethostbyname(socket.gethostname())
+    port = 7961
+    print(f"http://{host_ip}:{port}/docs")
+    uvicorn.run("app.main:app", host="0.0.0.0", port=port)