cameras.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. from typing import Any, List
  2. from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
  3. from fastapi.responses import Response
  4. from sqlalchemy.orm import Session
  5. from backend.app.api import deps
  6. from backend.app.core.database import get_db
  7. from backend.app.models import sql_models
  8. from backend.app.schemas import schemas
  9. from backend.app.services.video_core import video_manager
  10. import openpyxl
  11. import io
  12. router = APIRouter()
  13. @router.get("", response_model=List[schemas.Camera])
  14. def read_cameras(
  15. db: Session = Depends(get_db),
  16. skip: int = 0,
  17. limit: int = 100,
  18. current_user: sql_models.User = Depends(deps.get_current_user)
  19. ) -> Any:
  20. cameras = db.query(sql_models.Camera).offset(skip).limit(limit).all()
  21. # Update status from video_manager
  22. for cam in cameras:
  23. stream = video_manager.streams.get(cam.id)
  24. if stream:
  25. cam.status = stream.status
  26. else:
  27. cam.status = 0
  28. return cameras
  29. @router.get("/export_template")
  30. def export_template(
  31. db: Session = Depends(get_db),
  32. current_user: sql_models.User = Depends(deps.get_current_user)
  33. ) -> Any:
  34. """
  35. Export camera configuration template (including current data)
  36. """
  37. wb = openpyxl.Workbook()
  38. ws = wb.active
  39. ws.title = "Cameras"
  40. # Headers
  41. headers = ["Name", "Stream URL"]
  42. ws.append(headers)
  43. # Data
  44. cameras = db.query(sql_models.Camera).all()
  45. for cam in cameras:
  46. ws.append([cam.name, cam.stream_url])
  47. # Adjust column width
  48. ws.column_dimensions['A'].width = 30
  49. ws.column_dimensions['B'].width = 60
  50. # Save to buffer
  51. buffer = io.BytesIO()
  52. wb.save(buffer)
  53. buffer.seek(0)
  54. return Response(
  55. content=buffer.getvalue(),
  56. media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  57. headers={"Content-Disposition": "attachment; filename=cameras_template.xlsx"}
  58. )
  59. @router.post("/import_template")
  60. async def import_template(
  61. file: UploadFile = File(...),
  62. db: Session = Depends(get_db),
  63. current_user: sql_models.User = Depends(deps.get_current_user)
  64. ) -> Any:
  65. """
  66. Import cameras from template
  67. """
  68. if not file.filename.endswith(('.xlsx', '.xls')):
  69. raise HTTPException(status_code=400, detail="Invalid file format. Please upload an Excel file.")
  70. try:
  71. contents = await file.read()
  72. wb = openpyxl.load_workbook(io.BytesIO(contents))
  73. ws = wb.active
  74. # Read headers
  75. rows = list(ws.rows)
  76. if not rows:
  77. return {"message": "Empty file"}
  78. header_row = rows[0]
  79. headers = [cell.value for cell in header_row]
  80. # Validate headers
  81. if "Name" not in headers or "Stream URL" not in headers:
  82. raise HTTPException(status_code=400, detail="Invalid template format. Required columns: Name, Stream URL")
  83. name_idx = headers.index("Name")
  84. url_idx = headers.index("Stream URL")
  85. added_count = 0
  86. updated_count = 0
  87. # Process data
  88. for row in rows[1:]:
  89. name = row[name_idx].value
  90. url = row[url_idx].value
  91. if not name or not url:
  92. continue
  93. # Check if camera exists by URL
  94. existing_cam = db.query(sql_models.Camera).filter(sql_models.Camera.stream_url == url).first()
  95. if existing_cam:
  96. # Update name if changed
  97. if existing_cam.name != name:
  98. existing_cam.name = name
  99. updated_count += 1
  100. else:
  101. # Create new camera
  102. new_cam = sql_models.Camera(name=name, stream_url=url)
  103. db.add(new_cam)
  104. db.flush() # Get ID
  105. video_manager.add_stream(new_cam.id, new_cam.stream_url)
  106. added_count += 1
  107. db.commit()
  108. return {
  109. "message": "Import successful",
  110. "added": added_count,
  111. "updated": updated_count
  112. }
  113. except Exception as e:
  114. raise HTTPException(status_code=400, detail=f"Import failed: {str(e)}")
  115. @router.post("", response_model=schemas.Camera)
  116. def create_camera(
  117. *,
  118. db: Session = Depends(get_db),
  119. camera_in: schemas.CameraCreate,
  120. current_user: sql_models.User = Depends(deps.get_current_user)
  121. ) -> Any:
  122. camera = sql_models.Camera(name=camera_in.name, stream_url=camera_in.stream_url)
  123. db.add(camera)
  124. db.commit()
  125. db.refresh(camera)
  126. # Start stream
  127. video_manager.add_stream(camera.id, camera.stream_url)
  128. return camera
  129. @router.put("/{id}", response_model=schemas.Camera)
  130. def update_camera(
  131. *,
  132. db: Session = Depends(get_db),
  133. id: int,
  134. camera_in: schemas.CameraUpdate,
  135. current_user: sql_models.User = Depends(deps.get_current_user)
  136. ) -> Any:
  137. camera = db.query(sql_models.Camera).filter(sql_models.Camera.id == id).first()
  138. if not camera:
  139. raise HTTPException(status_code=404, detail="Camera not found")
  140. camera.name = camera_in.name
  141. camera.stream_url = camera_in.stream_url
  142. db.commit()
  143. db.refresh(camera)
  144. # Restart stream with new URL
  145. video_manager.add_stream(camera.id, camera.stream_url)
  146. return camera
  147. @router.delete("/{id}", response_model=schemas.Camera)
  148. def delete_camera(
  149. *,
  150. db: Session = Depends(get_db),
  151. id: int,
  152. current_user: sql_models.User = Depends(deps.get_current_user)
  153. ) -> Any:
  154. camera = db.query(sql_models.Camera).filter(sql_models.Camera.id == id).first()
  155. if not camera:
  156. raise HTTPException(status_code=404, detail="Camera not found")
  157. # Stop stream
  158. video_manager.stop_stream(id)
  159. db.delete(camera)
  160. db.commit()
  161. return camera
  162. @router.post("/test")
  163. def test_camera(
  164. *,
  165. camera_in: schemas.CameraTest,
  166. current_user: sql_models.User = Depends(deps.get_current_user)
  167. ) -> Any:
  168. snapshot = video_manager.test_camera_connection(camera_in.stream_url)
  169. if not snapshot:
  170. raise HTTPException(status_code=400, detail="Unable to connect to camera stream")
  171. return {"status": "ok", "image": snapshot}