Abubakar740 commited on
Commit
c275fbc
·
1 Parent(s): 9010dc6

update documentation and add tags

Browse files
Files changed (1) hide show
  1. main.py +76 -33
main.py CHANGED
@@ -19,21 +19,77 @@ from dotenv import load_dotenv
19
  load_dotenv(override=True)
20
 
21
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  description = """
23
- ### 🚀 Quick Start Guide for Non-Tech Users
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- Welcome to the AI Theft Detection Hub. Follow these simple steps to use the system:
 
 
26
 
27
- #### 📽️ 1. How to Monitor a Live Stream
28
- 1. **Add your Camera**: Go to the **POST** `/stream/create` section below. Click *Try it out*, enter a Name and your RTSP link, then click *Execute*.
29
- 2. **Start the AI**: Go to the **POST** `/stream/start/{id}` section. Enter your Camera ID (e.g., `cam-1`) and click *Execute*.
30
- 3. **View the Stream**: Copy one of the URLs returned in the response (Hugging Face or Local) and paste it into a new browser tab to see the live AI analysis.
31
- 4. **Discord Alerts**: If the system detects theft, a notification with a photo will be sent to your Discord channel automatically.
32
 
33
- #### 📁 2. How to Analyze a Recorded Video
34
- 1. **Upload Video**: Go to the **POST** `/video/detect` section. Click *Try it out*, choose your video file, and click *Execute*.
35
- 2. **Check Progress**: Copy the `job_id` from the response. Go to **GET** `/video/status/{job_id}` to see the percentage completed.
36
- 3. **Download Result**: Once the progress reaches `100%`, go to **GET** `/video/download/{job_id}` and click *Execute* to save your analyzed video.
 
 
 
37
 
38
  ---
39
  """
@@ -351,17 +407,17 @@ manager = StreamManager()
351
  async def root(): return RedirectResponse(url="/docs")
352
 
353
  # --- CAMERA ENDPOINTS ---
354
- @app.get("/stream/list_cameras")
355
  def get_cameras(): return {"cameras": list(MOCK_DB["cameras"].values())}
356
 
357
- @app.post("/stream/create")
358
  def create_camera(cam: CameraCreate):
359
  new_id = f"cam-{len(MOCK_DB['cameras']) + 1}"
360
  camera = {**cam.dict(), "id": new_id, "status": "offline", "isStreaming": False}
361
  MOCK_DB["cameras"][new_id] = camera
362
  return {"camera": camera}
363
 
364
- @app.post("/stream/start/{id}")
365
  async def start_camera(id: str, request: Request):
366
  if id not in MOCK_DB["cameras"]:
367
  raise HTTPException(404)
@@ -385,7 +441,7 @@ async def start_camera(id: str, request: Request):
385
  }
386
  }
387
 
388
- @app.post("/stream/stop/{id}")
389
  def stop_camera(id: str):
390
  if id not in MOCK_DB["cameras"]:
391
  raise HTTPException(404)
@@ -395,7 +451,7 @@ def stop_camera(id: str):
395
  MOCK_DB["cameras"][id]["isStreaming"] = False
396
  return {"success": stopped}
397
 
398
- @app.get("/stream/frame/{id}")
399
  def get_frame(id: str):
400
  if id not in manager.active_pipelines:
401
  raise HTTPException(404)
@@ -408,21 +464,8 @@ def get_frame(id: str):
408
  time.sleep(0.05)
409
  return StreamingResponse(generate(), media_type="multipart/x-mixed-replace; boundary=frame")
410
 
411
- @app.get("/cameras/{id}/frame")
412
- def get_frame_legacy(id: str):
413
- if id not in manager.active_pipelines:
414
- raise HTTPException(404)
415
- def generate():
416
- while id in manager.active_pipelines:
417
- frame = manager.active_pipelines[id].latest_frame
418
- if frame is not None:
419
- _, buffer = cv2.imencode('.jpg', frame)
420
- yield (b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + buffer.tobytes() + b'\r\n')
421
- time.sleep(0.05)
422
- return StreamingResponse(generate(), media_type="multipart/x-mixed-replace; boundary=frame")
423
-
424
  # --- VIDEO PROCESSING ENDPOINTS ---
425
- @app.post("/video/detect")
426
  async def detect(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
427
  jid = str(uuid.uuid4())
428
  in_p = os.path.join(UPLOAD_DIR, f"{jid}_{file.filename}")
@@ -435,15 +478,15 @@ async def detect(background_tasks: BackgroundTasks, file: UploadFile = File(...)
435
  background_tasks.add_task(lambda: asyncio.run(process_video_file(jid, in_p, out_p)))
436
  return {"job_id": jid}
437
 
438
- @app.get("/video/jobs")
439
  async def list_jobs():
440
  return [{"job_id": j, "status": d["status"], "progress": f"{d['progress']}%"} for j, d in jobs.items()]
441
 
442
- @app.get("/video/status/{job_id}")
443
  async def get_status(job_id: str):
444
  return jobs.get(job_id, {"error": "Not found"})
445
 
446
- @app.get("/video/download/{job_id}")
447
  async def download(job_id: str):
448
  if job_id in jobs and jobs[job_id]["status"] == "completed":
449
  return FileResponse(jobs[job_id]["output_path"])
 
19
  load_dotenv(override=True)
20
 
21
 
22
+ # --- DOCUMENTATION METADATA ---
23
+ tags_metadata = [
24
+ {
25
+ "name": "Live Stream Monitoring",
26
+ "description": "Real-time AI surveillance for RTSP cameras.",
27
+ },
28
+ {
29
+ "name": "Recorded Video Analysis",
30
+ "description": "Upload and process video files for theft detection.",
31
+ },
32
+ ]
33
+
34
  description = """
35
+ <details>
36
+ <summary><b> USER GUIDE & ENDPOINT MANUAL (Click to Expand)</b></summary>
37
+
38
+ <br>
39
+
40
+ ### 📽️ Section 1: Live Stream Monitoring
41
+ *Use these endpoints to manage and watch live AI security feeds.*
42
+
43
+ 1. **`POST /stream/create` (Register Camera)**
44
+ - **What it does:** Saves your camera's RTSP address and location into the system.
45
+ - **How to use:** Enter a name (e.g., "Cashier-1"), the RTSP link from your camera, and the location.
46
+
47
+ 2. **`POST /stream/start/{id}` (Activate AI)**
48
+ - **What it does:** Powers up the AI "Brain" for a specific camera.
49
+ - **How to use:** Enter the ID (like `cam-1`). It returns a **view_url**. Paste this URL into a new browser tab to see the live AI.
50
+
51
+ 3. **`GET /stream/frame/{id}` (Main Video Feed)**
52
+ - **What it does:** The actual live video stream with AI boxes and the security card.
53
+ - **How to use:** This is the URL used by your browser or dashboard to display the video.
54
+
55
+ 4. **`GET /stream/list_cameras` (Camera Directory)**
56
+ - **What it does:** Displays a list of all added cameras.
57
+ - **How to use:** Use this to see which cameras are currently "Online" or to find a Camera ID.
58
+
59
+ 5. **`POST /stream/stop/{id}` (Deactivate AI)**
60
+ - **What it does:** Shuts down the AI processing for a camera to save computer resources.
61
+ - **How to use:** Enter the ID and click Execute. The camera will move to "Offline" status.
62
+
63
+ <br>
64
+
65
+ ### 📁 Section 2: Recorded Video Analysis
66
+ *Use these endpoints to scan uploaded files for theft incidents.*
67
+
68
+ 1. **`POST /video/detect` (Upload for Analysis)**
69
+ - **What it does:** Uploads a video file (MP4/MOV) to the server for a full AI scan.
70
+ - **How to use:** Select your video file and click Execute. You will receive a **job_id**.
71
+
72
+ 2. **`GET /video/status/{job_id}` (Check Progress)**
73
+ - **What it does:** Provides the scan percentage (0% to 100%) and current state.
74
+ - **How to use:** Enter your **job_id**. Once it reaches 100%, your file is ready.
75
+
76
+ 3. **`GET /video/jobs` (Task History)**
77
+ - **What it does:** Lists every video ever uploaded and whether the scan is finished or failed.
78
+ - **How to use:** Helpful for finding old `job_id`s or checking multiple scans at once.
79
 
80
+ 4. **`GET /video/download/{job_id}` (Get Result)**
81
+ - **What it does:** Allows you to download the final processed video.
82
+ - **How to use:** Once status is 100%, enter the `job_id` here to save the analyzed video to your PC.
83
 
84
+ <br>
 
 
 
 
85
 
86
+ ### 🔔 Section 3: Discord Notifications
87
+ *Automatic alerts for security teams.*
88
+
89
+ - **How it works:** Whenever the AI detects a theft (Live or Recorded), it takes a snapshot of the person and sends a high-priority alert card to your Discord channel.
90
+ - **Requirement:** Ensure your `DISCORD_WEBHOOK_URL` is set in the system settings.
91
+
92
+ </details>
93
 
94
  ---
95
  """
 
407
  async def root(): return RedirectResponse(url="/docs")
408
 
409
  # --- CAMERA ENDPOINTS ---
410
+ @app.get("/stream/list_cameras", tags=["Live Stream Monitoring"])
411
  def get_cameras(): return {"cameras": list(MOCK_DB["cameras"].values())}
412
 
413
+ @app.post("/stream/create", tags=["Live Stream Monitoring"])
414
  def create_camera(cam: CameraCreate):
415
  new_id = f"cam-{len(MOCK_DB['cameras']) + 1}"
416
  camera = {**cam.dict(), "id": new_id, "status": "offline", "isStreaming": False}
417
  MOCK_DB["cameras"][new_id] = camera
418
  return {"camera": camera}
419
 
420
+ @app.post("/stream/start/{id}", tags=["Live Stream Monitoring"])
421
  async def start_camera(id: str, request: Request):
422
  if id not in MOCK_DB["cameras"]:
423
  raise HTTPException(404)
 
441
  }
442
  }
443
 
444
+ @app.post("/stream/stop/{id}", tags=["Live Stream Monitoring"])
445
  def stop_camera(id: str):
446
  if id not in MOCK_DB["cameras"]:
447
  raise HTTPException(404)
 
451
  MOCK_DB["cameras"][id]["isStreaming"] = False
452
  return {"success": stopped}
453
 
454
+ @app.get("/stream/frame/{id}", tags=["Live Stream Monitoring"])
455
  def get_frame(id: str):
456
  if id not in manager.active_pipelines:
457
  raise HTTPException(404)
 
464
  time.sleep(0.05)
465
  return StreamingResponse(generate(), media_type="multipart/x-mixed-replace; boundary=frame")
466
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
  # --- VIDEO PROCESSING ENDPOINTS ---
468
+ @app.post("/video/detect", tags=["Recorded Video Analysis"])
469
  async def detect(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
470
  jid = str(uuid.uuid4())
471
  in_p = os.path.join(UPLOAD_DIR, f"{jid}_{file.filename}")
 
478
  background_tasks.add_task(lambda: asyncio.run(process_video_file(jid, in_p, out_p)))
479
  return {"job_id": jid}
480
 
481
+ @app.get("/video/jobs", tags=["Recorded Video Analysis"])
482
  async def list_jobs():
483
  return [{"job_id": j, "status": d["status"], "progress": f"{d['progress']}%"} for j, d in jobs.items()]
484
 
485
+ @app.get("/video/status/{job_id}", tags=["Recorded Video Analysis"])
486
  async def get_status(job_id: str):
487
  return jobs.get(job_id, {"error": "Not found"})
488
 
489
+ @app.get("/video/download/{job_id}", tags=["Recorded Video Analysis"])
490
  async def download(job_id: str):
491
  if job_id in jobs and jobs[job_id]["status"] == "completed":
492
  return FileResponse(jobs[job_id]["output_path"])