| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- from __future__ import annotations
- from datetime import datetime
- from fastapi import APIRouter, Depends, Query, Request, Response
- from fastapi.responses import FileResponse, StreamingResponse
- from sqlalchemy.orm import Session
- from app.db.session import get_db
- from app.schemas.common import Page
- from app.schemas.video import (
- VideoAnalysisEventRead,
- VideoChannelCreate,
- VideoChannelRead,
- VideoChannelUpdate,
- VideoEventUpdate,
- VideoModuleConfig,
- VideoModuleSummaryRead,
- VideoPlatformOverview,
- VideoRecordRead,
- VideoSnapshotRead,
- )
- from app.services import video_platform_service as video_service
- router = APIRouter(prefix="/video-platform", tags=["工业视频平台"])
- @router.get("/overview", response_model=VideoPlatformOverview, summary="视频平台总览")
- def overview(
- mine_id: str = Query(...),
- db: Session = Depends(get_db),
- ):
- return video_service.platform_overview(db, mine_id)
- @router.get("/modules", response_model=list[VideoModuleConfig], summary="模块页签与检索项配置")
- def modules(
- mine_id: str = Query(...),
- db: Session = Depends(get_db),
- ):
- return video_service.module_configs(db, mine_id)
- @router.get(
- "/modules/{module}/summary",
- response_model=VideoModuleSummaryRead,
- summary="模块状态总览与统计卡片",
- )
- def module_summary(
- module: str,
- mine_id: str = Query(...),
- db: Session = Depends(get_db),
- ):
- return video_service.module_summary(db, mine_id, module)
- # ---------------------------------------------------------------------------
- # 视频通道(通道管理、设备树、分屏预览)
- # ---------------------------------------------------------------------------
- @router.get("/channels", response_model=list[VideoChannelRead], summary="视频通道列表")
- def list_channels(
- mine_id: str = Query(...),
- module: str | None = Query(None, description="door/local_fan/main_fan"),
- status: str | None = Query(None, description="online/offline/fault"),
- keyword: str | None = Query(None, description="通道名称、安装地点或编码"),
- limit: int = Query(200, ge=1, le=500),
- db: Session = Depends(get_db),
- ):
- return video_service.list_channels(
- db, mine_id, module=module, status=status, keyword=keyword, limit=limit
- )
- @router.post(
- "/channels",
- response_model=VideoChannelRead,
- status_code=201,
- summary="新增视频通道",
- )
- def create_channel(payload: VideoChannelCreate, db: Session = Depends(get_db)):
- return video_service.create_channel(db, payload)
- @router.get(
- "/channels/{channel_id}",
- response_model=VideoChannelRead,
- summary="视频通道详情",
- )
- def channel_detail(channel_id: str, db: Session = Depends(get_db)):
- return video_service.get_channel(db, channel_id)
- @router.patch(
- "/channels/{channel_id}",
- response_model=VideoChannelRead,
- summary="更新视频通道",
- )
- def update_channel(
- channel_id: str,
- payload: VideoChannelUpdate,
- db: Session = Depends(get_db),
- ):
- return video_service.update_channel(db, channel_id, payload)
- @router.delete(
- "/channels/{channel_id}",
- status_code=204,
- summary="删除视频通道",
- )
- def delete_channel(channel_id: str, db: Session = Depends(get_db)) -> Response:
- video_service.delete_channel(db, channel_id)
- return Response(status_code=204)
- @router.get(
- "/channels/{channel_id}/stream",
- summary="通道视频流(读取数据库中的磁盘路径,支持 Range)",
- )
- def channel_stream(
- channel_id: str,
- request: Request,
- db: Session = Depends(get_db),
- ):
- path, content_type = video_service.resolve_channel_media(channel_id, db)
- file_size = path.stat().st_size
- range_header = request.headers.get("range")
- start, end = video_service.parse_range_header(range_header, file_size)
- headers = {
- "Accept-Ranges": "bytes",
- "Content-Length": str(max(end - start + 1, 0)),
- "Cache-Control": "no-store",
- }
- if range_header:
- headers["Content-Range"] = f"bytes {start}-{end}/{file_size}"
- return StreamingResponse(
- video_service.iter_file_range(path, start, end),
- status_code=206,
- media_type=content_type,
- headers=headers,
- )
- return StreamingResponse(
- video_service.iter_file_range(path, start, end),
- status_code=200,
- media_type=content_type,
- headers=headers,
- )
- @router.get(
- "/channels/{channel_id}/snapshot",
- summary="通道最近识别抓拍图(抓图按钮)",
- )
- def channel_snapshot(channel_id: str, db: Session = Depends(get_db)):
- path, content_type = video_service.resolve_channel_latest_snapshot(channel_id, db)
- return FileResponse(path, media_type=content_type, filename=path.name)
- # ---------------------------------------------------------------------------
- # 识别抓拍
- # ---------------------------------------------------------------------------
- @router.get(
- "/snapshots",
- response_model=Page[VideoSnapshotRead],
- summary="识别抓拍图像列表(时间、类型、设备检索)",
- )
- def list_snapshots(
- mine_id: str = Query(...),
- module: str | None = Query(None),
- snapshot_type: str | None = Query(None, description="vehicle_passage/person_passage 等"),
- event_type: str | None = Query(None, description="告警类型或操作类型名称"),
- channel_id: str | None = Query(None),
- start: datetime | None = Query(None, description="抓拍时间起"),
- end: datetime | None = Query(None, description="抓拍时间止"),
- keyword: str | None = Query(None, description="标题、车牌、人员或类型关键字"),
- page: int = Query(1, ge=1),
- page_size: int = Query(24, ge=1, le=200),
- db: Session = Depends(get_db),
- ):
- items, total = video_service.list_snapshots(
- db,
- mine_id,
- module=module,
- snapshot_type=snapshot_type,
- event_type=event_type,
- channel_id=channel_id,
- start=start,
- end=end,
- keyword=keyword,
- page=page,
- page_size=page_size,
- )
- return Page(items=items, total=total, page=page, page_size=page_size)
- @router.get(
- "/snapshots/{snapshot_id}",
- response_model=VideoSnapshotRead,
- summary="识别抓拍详情",
- )
- def snapshot_detail(snapshot_id: str, db: Session = Depends(get_db)):
- return video_service.get_snapshot(db, snapshot_id)
- @router.get("/snapshots/{snapshot_id}/image", summary="识别抓拍原图")
- def snapshot_image(snapshot_id: str, db: Session = Depends(get_db)):
- path, content_type = video_service.resolve_snapshot_media(snapshot_id, db)
- return FileResponse(path, media_type=content_type, filename=path.name)
- @router.get("/snapshots/{snapshot_id}/thumbnail", summary="识别抓拍缩略图")
- def snapshot_thumbnail(snapshot_id: str, db: Session = Depends(get_db)):
- path, content_type = video_service.resolve_snapshot_thumbnail(snapshot_id, db)
- return FileResponse(path, media_type=content_type, filename=path.name)
- # ---------------------------------------------------------------------------
- # 视频分析事件
- # ---------------------------------------------------------------------------
- @router.get(
- "/events",
- response_model=list[VideoAnalysisEventRead],
- summary="视频分析事件与实时告警",
- )
- def list_events(
- mine_id: str = Query(...),
- module: str | None = Query(None),
- level: str | None = Query(None, description="danger/warning/info"),
- status: str | None = Query(None, description="pending/processing/handled/ignored"),
- event_type: str | None = Query(None),
- start: datetime | None = Query(None),
- end: datetime | None = Query(None),
- limit: int = Query(100, ge=1, le=1000),
- db: Session = Depends(get_db),
- ):
- return video_service.list_events(
- db,
- mine_id,
- module=module,
- level=level,
- status=status,
- event_type=event_type,
- start=start,
- end=end,
- limit=limit,
- )
- @router.patch(
- "/events/{event_id}",
- response_model=VideoAnalysisEventRead,
- summary="更新事件处理状态",
- )
- def update_event(
- event_id: str,
- payload: VideoEventUpdate,
- db: Session = Depends(get_db),
- ):
- return video_service.update_event(db, event_id, payload)
- # ---------------------------------------------------------------------------
- # 通行与操作记录
- # ---------------------------------------------------------------------------
- @router.get(
- "/records",
- response_model=Page[VideoRecordRead],
- summary="通行与操作记录列表",
- )
- def list_records(
- mine_id: str = Query(...),
- module: str | None = Query(None),
- record_type: str | None = Query(
- None,
- description=(
- "vehicle_passage/person_passage/door_unclosed/person_track/"
- "area_stay/person_warn/person_detect/device_operation/device_warn"
- ),
- ),
- channel_id: str | None = Query(None),
- start: datetime | None = Query(None),
- end: datetime | None = Query(None),
- keyword: str | None = Query(None),
- page: int = Query(1, ge=1),
- page_size: int = Query(50, ge=1, le=200),
- db: Session = Depends(get_db),
- ):
- items, total = video_service.list_records(
- db,
- mine_id,
- module=module,
- record_type=record_type,
- channel_id=channel_id,
- start=start,
- end=end,
- keyword=keyword,
- page=page,
- page_size=page_size,
- )
- return Page(items=items, total=total, page=page, page_size=page_size)
|