| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- from typing import Any, List
- from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
- from fastapi.responses import Response
- from sqlalchemy.orm import Session
- from backend.app.api import deps
- from backend.app.core.database import get_db
- from backend.app.models import sql_models
- from backend.app.schemas import schemas
- from backend.app.services.video_core import video_manager
- import openpyxl
- import io
- router = APIRouter()
- @router.get("", response_model=List[schemas.Camera])
- def read_cameras(
- db: Session = Depends(get_db),
- skip: int = 0,
- limit: int = 100,
- current_user: sql_models.User = Depends(deps.get_current_user)
- ) -> Any:
- cameras = db.query(sql_models.Camera).offset(skip).limit(limit).all()
- # Update status from video_manager
- for cam in cameras:
- stream = video_manager.streams.get(cam.id)
- if stream:
- cam.status = stream.status
- else:
- cam.status = 0
- return cameras
- @router.get("/export_template")
- def export_template(
- db: Session = Depends(get_db),
- current_user: sql_models.User = Depends(deps.get_current_user)
- ) -> Any:
- """
- Export camera configuration template (including current data)
- """
- wb = openpyxl.Workbook()
- ws = wb.active
- ws.title = "Cameras"
-
- # Headers
- headers = ["Name", "Stream URL"]
- ws.append(headers)
-
- # Data
- cameras = db.query(sql_models.Camera).all()
- for cam in cameras:
- ws.append([cam.name, cam.stream_url])
-
- # Adjust column width
- ws.column_dimensions['A'].width = 30
- ws.column_dimensions['B'].width = 60
-
- # Save to buffer
- buffer = io.BytesIO()
- wb.save(buffer)
- buffer.seek(0)
-
- return Response(
- content=buffer.getvalue(),
- media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
- headers={"Content-Disposition": "attachment; filename=cameras_template.xlsx"}
- )
- @router.post("/import_template")
- async def import_template(
- file: UploadFile = File(...),
- db: Session = Depends(get_db),
- current_user: sql_models.User = Depends(deps.get_current_user)
- ) -> Any:
- """
- Import cameras from template
- """
- if not file.filename.endswith(('.xlsx', '.xls')):
- raise HTTPException(status_code=400, detail="Invalid file format. Please upload an Excel file.")
-
- try:
- contents = await file.read()
- wb = openpyxl.load_workbook(io.BytesIO(contents))
- ws = wb.active
-
- # Read headers
- rows = list(ws.rows)
- if not rows:
- return {"message": "Empty file"}
-
- header_row = rows[0]
- headers = [cell.value for cell in header_row]
-
- # Validate headers
- if "Name" not in headers or "Stream URL" not in headers:
- raise HTTPException(status_code=400, detail="Invalid template format. Required columns: Name, Stream URL")
-
- name_idx = headers.index("Name")
- url_idx = headers.index("Stream URL")
-
- added_count = 0
- updated_count = 0
-
- # Process data
- for row in rows[1:]:
- name = row[name_idx].value
- url = row[url_idx].value
-
- if not name or not url:
- continue
-
- # Check if camera exists by URL
- existing_cam = db.query(sql_models.Camera).filter(sql_models.Camera.stream_url == url).first()
-
- if existing_cam:
- # Update name if changed
- if existing_cam.name != name:
- existing_cam.name = name
- updated_count += 1
- else:
- # Create new camera
- new_cam = sql_models.Camera(name=name, stream_url=url)
- db.add(new_cam)
- db.flush() # Get ID
- video_manager.add_stream(new_cam.id, new_cam.stream_url)
- added_count += 1
-
- db.commit()
-
- return {
- "message": "Import successful",
- "added": added_count,
- "updated": updated_count
- }
-
- except Exception as e:
- raise HTTPException(status_code=400, detail=f"Import failed: {str(e)}")
- @router.post("", response_model=schemas.Camera)
- def create_camera(
- *,
- db: Session = Depends(get_db),
- camera_in: schemas.CameraCreate,
- current_user: sql_models.User = Depends(deps.get_current_user)
- ) -> Any:
- camera = sql_models.Camera(name=camera_in.name, stream_url=camera_in.stream_url)
- db.add(camera)
- db.commit()
- db.refresh(camera)
-
- # Start stream
- video_manager.add_stream(camera.id, camera.stream_url)
-
- return camera
- @router.put("/{id}", response_model=schemas.Camera)
- def update_camera(
- *,
- db: Session = Depends(get_db),
- id: int,
- camera_in: schemas.CameraUpdate,
- current_user: sql_models.User = Depends(deps.get_current_user)
- ) -> Any:
- camera = db.query(sql_models.Camera).filter(sql_models.Camera.id == id).first()
- if not camera:
- raise HTTPException(status_code=404, detail="Camera not found")
-
- camera.name = camera_in.name
- camera.stream_url = camera_in.stream_url
-
- db.commit()
- db.refresh(camera)
-
- # Restart stream with new URL
- video_manager.add_stream(camera.id, camera.stream_url)
-
- return camera
- @router.delete("/{id}", response_model=schemas.Camera)
- def delete_camera(
- *,
- db: Session = Depends(get_db),
- id: int,
- current_user: sql_models.User = Depends(deps.get_current_user)
- ) -> Any:
- camera = db.query(sql_models.Camera).filter(sql_models.Camera.id == id).first()
- if not camera:
- raise HTTPException(status_code=404, detail="Camera not found")
-
- # Stop stream
- video_manager.stop_stream(id)
-
- db.delete(camera)
- db.commit()
- return camera
- @router.post("/test")
- def test_camera(
- *,
- camera_in: schemas.CameraTest,
- current_user: sql_models.User = Depends(deps.get_current_user)
- ) -> Any:
- snapshot = video_manager.test_camera_connection(camera_in.stream_url)
- if not snapshot:
- raise HTTPException(status_code=400, detail="Unable to connect to camera stream")
- return {"status": "ok", "image": snapshot}
|