2026-01-22 09:08:44 +08:00
|
|
|
from datetime import datetime, timezone, timedelta
|
2026-01-20 17:42:18 +08:00
|
|
|
from typing import List, Optional
|
|
|
|
|
|
2026-01-21 13:29:00 +08:00
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Body
|
|
|
|
|
from pydantic import BaseModel
|
2026-01-20 17:42:18 +08:00
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
|
|
|
|
from db.crud import (
|
|
|
|
|
create_camera,
|
|
|
|
|
delete_camera,
|
|
|
|
|
get_all_cameras,
|
|
|
|
|
get_camera_by_id,
|
|
|
|
|
update_camera,
|
|
|
|
|
)
|
|
|
|
|
from db.models import get_db
|
|
|
|
|
from inference.pipeline import get_pipeline
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/api/cameras", tags=["摄像头管理"])
|
2026-01-21 16:06:39 +08:00
|
|
|
router2 = APIRouter(prefix="/api/camera", tags=["摄像头状态"])
|
2026-01-20 17:42:18 +08:00
|
|
|
|
|
|
|
|
|
2026-01-21 13:29:00 +08:00
|
|
|
class CameraUpdateRequest(BaseModel):
|
|
|
|
|
name: Optional[str] = None
|
|
|
|
|
rtsp_url: Optional[str] = None
|
|
|
|
|
fps_limit: Optional[int] = None
|
|
|
|
|
process_every_n_frames: Optional[int] = None
|
|
|
|
|
enabled: Optional[bool] = None
|
|
|
|
|
|
|
|
|
|
|
2026-01-22 09:08:44 +08:00
|
|
|
def convert_to_china_time(dt: Optional[datetime]) -> Optional[str]:
|
|
|
|
|
"""将 UTC 时间转换为中国时间 (UTC+8)"""
|
|
|
|
|
if dt is None:
|
|
|
|
|
return None
|
|
|
|
|
try:
|
|
|
|
|
china_tz = timezone(timedelta(hours=8))
|
|
|
|
|
if dt.tzinfo is None:
|
|
|
|
|
dt = dt.replace(tzinfo=timezone.utc)
|
|
|
|
|
return dt.astimezone(china_tz).isoformat()
|
|
|
|
|
except Exception:
|
|
|
|
|
return dt.isoformat() if dt else None
|
|
|
|
|
|
|
|
|
|
|
2026-01-20 17:42:18 +08:00
|
|
|
@router.get("", response_model=List[dict])
|
|
|
|
|
def list_cameras(
|
|
|
|
|
enabled_only: bool = True,
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
):
|
|
|
|
|
cameras = get_all_cameras(db, enabled_only=enabled_only)
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
"id": cam.id,
|
|
|
|
|
"name": cam.name,
|
|
|
|
|
"rtsp_url": cam.rtsp_url,
|
|
|
|
|
"enabled": cam.enabled,
|
|
|
|
|
"fps_limit": cam.fps_limit,
|
|
|
|
|
"process_every_n_frames": cam.process_every_n_frames,
|
2026-01-22 09:08:44 +08:00
|
|
|
"created_at": convert_to_china_time(cam.created_at),
|
2026-01-20 17:42:18 +08:00
|
|
|
}
|
|
|
|
|
for cam in cameras
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/{camera_id}", response_model=dict)
|
|
|
|
|
def get_camera(camera_id: int, db: Session = Depends(get_db)):
|
|
|
|
|
camera = get_camera_by_id(db, camera_id)
|
|
|
|
|
if not camera:
|
|
|
|
|
raise HTTPException(status_code=404, detail="摄像头不存在")
|
|
|
|
|
return {
|
|
|
|
|
"id": camera.id,
|
|
|
|
|
"name": camera.name,
|
|
|
|
|
"rtsp_url": camera.rtsp_url,
|
|
|
|
|
"enabled": camera.enabled,
|
|
|
|
|
"fps_limit": camera.fps_limit,
|
|
|
|
|
"process_every_n_frames": camera.process_every_n_frames,
|
2026-01-22 09:08:44 +08:00
|
|
|
"created_at": convert_to_china_time(camera.created_at),
|
2026-01-20 17:42:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("", response_model=dict)
|
|
|
|
|
def add_camera(
|
2026-01-21 17:03:59 +08:00
|
|
|
request: CameraUpdateRequest = Body(...),
|
2026-01-20 17:42:18 +08:00
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
):
|
|
|
|
|
camera = create_camera(
|
|
|
|
|
db,
|
2026-01-21 17:03:59 +08:00
|
|
|
name=request.name,
|
|
|
|
|
rtsp_url=request.rtsp_url,
|
|
|
|
|
fps_limit=request.fps_limit or 30,
|
|
|
|
|
process_every_n_frames=request.process_every_n_frames or 3,
|
2026-01-20 17:42:18 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if camera.enabled:
|
|
|
|
|
pipeline = get_pipeline()
|
|
|
|
|
pipeline.add_camera(camera)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"id": camera.id,
|
|
|
|
|
"name": camera.name,
|
|
|
|
|
"rtsp_url": camera.rtsp_url,
|
|
|
|
|
"enabled": camera.enabled,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/{camera_id}", response_model=dict)
|
|
|
|
|
def modify_camera(
|
|
|
|
|
camera_id: int,
|
2026-01-21 13:29:00 +08:00
|
|
|
request: CameraUpdateRequest = Body(...),
|
2026-01-20 17:42:18 +08:00
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
):
|
|
|
|
|
camera = update_camera(
|
|
|
|
|
db,
|
|
|
|
|
camera_id=camera_id,
|
2026-01-21 13:29:00 +08:00
|
|
|
name=request.name,
|
|
|
|
|
rtsp_url=request.rtsp_url,
|
|
|
|
|
fps_limit=request.fps_limit,
|
|
|
|
|
process_every_n_frames=request.process_every_n_frames,
|
|
|
|
|
enabled=request.enabled,
|
2026-01-20 17:42:18 +08:00
|
|
|
)
|
|
|
|
|
if not camera:
|
|
|
|
|
raise HTTPException(status_code=404, detail="摄像头不存在")
|
|
|
|
|
|
|
|
|
|
pipeline = get_pipeline()
|
2026-01-21 13:29:00 +08:00
|
|
|
if request.enabled is True:
|
2026-01-20 17:42:18 +08:00
|
|
|
pipeline.add_camera(camera)
|
2026-01-21 13:29:00 +08:00
|
|
|
elif request.enabled is False:
|
2026-01-20 17:42:18 +08:00
|
|
|
pipeline.remove_camera(camera_id)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"id": camera.id,
|
|
|
|
|
"name": camera.name,
|
|
|
|
|
"rtsp_url": camera.rtsp_url,
|
|
|
|
|
"enabled": camera.enabled,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/{camera_id}")
|
|
|
|
|
def remove_camera(camera_id: int, db: Session = Depends(get_db)):
|
|
|
|
|
pipeline = get_pipeline()
|
|
|
|
|
pipeline.remove_camera(camera_id)
|
|
|
|
|
|
|
|
|
|
if not delete_camera(db, camera_id):
|
|
|
|
|
raise HTTPException(status_code=404, detail="摄像头不存在")
|
|
|
|
|
|
|
|
|
|
return {"message": "删除成功"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/{camera_id}/status")
|
|
|
|
|
def get_camera_status(camera_id: int, db: Session = Depends(get_db)):
|
|
|
|
|
from db.crud import get_camera_status as get_status
|
|
|
|
|
|
|
|
|
|
status = get_status(db, camera_id)
|
|
|
|
|
pipeline = get_pipeline()
|
|
|
|
|
|
|
|
|
|
stream = pipeline.stream_manager.get_stream(str(camera_id))
|
|
|
|
|
if stream:
|
|
|
|
|
stream_info = stream.get_info()
|
|
|
|
|
else:
|
|
|
|
|
stream_info = None
|
|
|
|
|
|
|
|
|
|
if status:
|
|
|
|
|
return {
|
|
|
|
|
"camera_id": camera_id,
|
|
|
|
|
"is_running": status.is_running,
|
|
|
|
|
"fps": status.fps,
|
|
|
|
|
"error_message": status.error_message,
|
|
|
|
|
"last_check_time": status.last_check_time.isoformat() if status.last_check_time else None,
|
|
|
|
|
"stream": stream_info,
|
|
|
|
|
}
|
|
|
|
|
else:
|
|
|
|
|
return {
|
|
|
|
|
"camera_id": camera_id,
|
|
|
|
|
"is_running": False,
|
|
|
|
|
"fps": 0.0,
|
|
|
|
|
"error_message": None,
|
|
|
|
|
"last_check_time": None,
|
|
|
|
|
"stream": stream_info,
|
|
|
|
|
}
|
2026-01-21 16:06:39 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
router2 = APIRouter(prefix="/api/camera", tags=["摄像头状态"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router2.get("/status/all")
|
|
|
|
|
def get_all_camera_status(db: Session = Depends(get_db)):
|
|
|
|
|
from db.crud import get_all_cameras, get_camera_status as get_status
|
|
|
|
|
|
|
|
|
|
cameras = get_all_cameras(db, enabled_only=False)
|
|
|
|
|
pipeline = get_pipeline()
|
|
|
|
|
|
|
|
|
|
result = []
|
|
|
|
|
for cam in cameras:
|
|
|
|
|
status = get_status(db, cam.id)
|
|
|
|
|
|
|
|
|
|
stream = pipeline.stream_manager.get_stream(str(cam.id))
|
|
|
|
|
stream_info = stream.get_info() if stream else None
|
|
|
|
|
|
|
|
|
|
if status:
|
|
|
|
|
result.append({
|
|
|
|
|
"camera_id": cam.id,
|
|
|
|
|
"is_running": status.is_running,
|
|
|
|
|
"fps": status.fps,
|
|
|
|
|
"error_message": status.error_message,
|
|
|
|
|
"last_check_time": status.last_check_time.isoformat() if status.last_check_time else None,
|
|
|
|
|
"stream": stream_info,
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
result.append({
|
|
|
|
|
"camera_id": cam.id,
|
|
|
|
|
"is_running": False,
|
|
|
|
|
"fps": 0.0,
|
|
|
|
|
"error_message": None,
|
|
|
|
|
"last_check_time": None,
|
|
|
|
|
"stream": stream_info,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return result
|