Spaces:
Sleeping
Sleeping
Abubakar740 commited on
Commit ·
c275fbc
1
Parent(s): 9010dc6
update documentation and add tags
Browse files
main.py
CHANGED
|
@@ -19,21 +19,77 @@ from dotenv import load_dotenv
|
|
| 19 |
load_dotenv(override=True)
|
| 20 |
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
description = """
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
|
| 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 |
-
###
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
| 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"])
|