Update detection_api.py
Browse files- detection_api.py +33 -81
detection_api.py
CHANGED
|
@@ -501,17 +501,17 @@ async def detect_video(
|
|
| 501 |
file: UploadFile = File(..., description="Video file to analyze"),
|
| 502 |
frame_skip: int = Form(5, ge=1, le=30, description="Process every Nth frame"),
|
| 503 |
max_frames: int = Form(1000, ge=10, le=5000, description="Maximum frames to process"),
|
| 504 |
-
enable_fight_detection: bool = Form(True, description="Enable fight detection")
|
| 505 |
-
save_processed: bool = Form(False, description="Save processed video with annotations")
|
| 506 |
):
|
| 507 |
"""
|
| 508 |
Detect weapons (knife/dao/gun), fights, and NSFW content in videos
|
| 509 |
-
|
| 510 |
Supports: MP4, AVI, MOV, MKV, WEBM, FLV, WMV
|
| 511 |
Max size: 500MB
|
|
|
|
| 512 |
"""
|
| 513 |
request_id = generate_request_id()
|
| 514 |
start_time = datetime.now()
|
|
|
|
| 515 |
|
| 516 |
try:
|
| 517 |
# Validate file extension
|
|
@@ -528,7 +528,6 @@ async def detect_video(
|
|
| 528 |
# Get file size
|
| 529 |
file_size = upload_path.stat().st_size
|
| 530 |
if not validate_file_size(file_size, config.MAX_VIDEO_SIZE):
|
| 531 |
-
upload_path.unlink() # Delete the file
|
| 532 |
raise HTTPException(
|
| 533 |
status_code=400,
|
| 534 |
detail=f"File too large. Maximum size: {config.MAX_VIDEO_SIZE / (1024 * 1024):.1f}MB"
|
|
@@ -557,19 +556,6 @@ async def detect_video(
|
|
| 557 |
"size_mb": round(file_size / (1024 * 1024), 2)
|
| 558 |
}
|
| 559 |
|
| 560 |
-
# Prepare output video if requested
|
| 561 |
-
out_writer = None
|
| 562 |
-
processed_video_path = None
|
| 563 |
-
if save_processed:
|
| 564 |
-
processed_video_path = config.PROCESSED_DIR / "videos" / f"{request_id}_processed.mp4"
|
| 565 |
-
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
| 566 |
-
out_writer = cv2.VideoWriter(
|
| 567 |
-
str(processed_video_path),
|
| 568 |
-
fourcc,
|
| 569 |
-
fps,
|
| 570 |
-
(width, height)
|
| 571 |
-
)
|
| 572 |
-
|
| 573 |
# Process video frames
|
| 574 |
logger.info(f"Processing video {request_id}: {total_frames} frames, skip={frame_skip}")
|
| 575 |
|
|
@@ -636,21 +622,12 @@ async def detect_video(
|
|
| 636 |
all_nsfw.extend(processed['nsfw'])
|
| 637 |
all_fights.extend(processed['fights'])
|
| 638 |
|
| 639 |
-
# Write annotated frame if saving video
|
| 640 |
-
if out_writer and 'annotated_image' in result:
|
| 641 |
-
out_writer.write(result['annotated_image'])
|
| 642 |
-
elif out_writer:
|
| 643 |
-
# Write original frame if no detections
|
| 644 |
-
out_writer.write(frame)
|
| 645 |
-
|
| 646 |
# Log progress every 100 frames
|
| 647 |
if processed_count % 100 == 0:
|
| 648 |
logger.info(f"Processed {processed_count} frames...")
|
| 649 |
|
| 650 |
# Release resources
|
| 651 |
cap.release()
|
| 652 |
-
if out_writer:
|
| 653 |
-
out_writer.release()
|
| 654 |
|
| 655 |
# Calculate summary
|
| 656 |
knife_count = sum(1 for w in all_weapons if 'knife' in w.class_name.lower() or 'dao' in w.class_name.lower())
|
|
@@ -689,11 +666,6 @@ async def detect_video(
|
|
| 689 |
# Calculate processing time
|
| 690 |
processing_time = (datetime.now() - start_time).total_seconds() * 1000
|
| 691 |
|
| 692 |
-
# Prepare processed video URL if saved
|
| 693 |
-
processed_video_url = None
|
| 694 |
-
if save_processed and processed_video_path and processed_video_path.exists():
|
| 695 |
-
processed_video_url = f"/results/videos/{request_id}_processed.mp4"
|
| 696 |
-
|
| 697 |
return VideoDetectionResponse(
|
| 698 |
success=True,
|
| 699 |
request_id=request_id,
|
|
@@ -704,7 +676,7 @@ async def detect_video(
|
|
| 704 |
summary=summary,
|
| 705 |
risk_level=risk_level,
|
| 706 |
action_required=(summary["total_detections"] > 0),
|
| 707 |
-
processed_video_url=
|
| 708 |
processing_time_ms=processing_time
|
| 709 |
)
|
| 710 |
|
|
@@ -718,67 +690,47 @@ async def detect_video(
|
|
| 718 |
detail=f"Internal server error: {str(e)}"
|
| 719 |
)
|
| 720 |
finally:
|
| 721 |
-
#
|
| 722 |
-
if upload_path.exists()
|
| 723 |
try:
|
| 724 |
upload_path.unlink()
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
@app.get("/results/images/{filename}")
|
| 730 |
-
async def get_processed_image(filename: str):
|
| 731 |
-
"""Get processed/annotated image"""
|
| 732 |
-
file_path = config.PROCESSED_DIR / "images" / filename
|
| 733 |
-
if not file_path.exists():
|
| 734 |
-
raise HTTPException(status_code=404, detail="File not found")
|
| 735 |
-
return FileResponse(file_path)
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
@app.get("/results/videos/{filename}")
|
| 739 |
-
async def get_processed_video(filename: str):
|
| 740 |
-
"""Get processed/annotated video"""
|
| 741 |
-
file_path = config.PROCESSED_DIR / "videos" / filename
|
| 742 |
-
if not file_path.exists():
|
| 743 |
-
raise HTTPException(status_code=404, detail="File not found")
|
| 744 |
-
return FileResponse(file_path)
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
@app.get("/health")
|
| 748 |
-
async def health_check():
|
| 749 |
-
"""Health check endpoint"""
|
| 750 |
-
if moderator:
|
| 751 |
-
status = moderator.get_model_status()
|
| 752 |
-
return {
|
| 753 |
-
"status": "healthy",
|
| 754 |
-
"models_loaded": True,
|
| 755 |
-
"model_details": status
|
| 756 |
-
}
|
| 757 |
-
else:
|
| 758 |
-
return {
|
| 759 |
-
"status": "initializing",
|
| 760 |
-
"models_loaded": False
|
| 761 |
-
}
|
| 762 |
|
| 763 |
|
| 764 |
@app.delete("/cleanup")
|
| 765 |
async def cleanup_old_files(hours: int = 24):
|
| 766 |
-
"""Clean up old files from upload and results directories"""
|
| 767 |
try:
|
| 768 |
from datetime import timedelta
|
| 769 |
cutoff_time = datetime.now() - timedelta(hours=hours)
|
| 770 |
|
| 771 |
deleted_count = 0
|
|
|
|
|
|
|
| 772 |
for directory in [config.UPLOAD_DIR, config.RESULTS_DIR, config.PROCESSED_DIR]:
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 782 |
|
| 783 |
return {
|
| 784 |
"success": True,
|
|
|
|
| 501 |
file: UploadFile = File(..., description="Video file to analyze"),
|
| 502 |
frame_skip: int = Form(5, ge=1, le=30, description="Process every Nth frame"),
|
| 503 |
max_frames: int = Form(1000, ge=10, le=5000, description="Maximum frames to process"),
|
| 504 |
+
enable_fight_detection: bool = Form(True, description="Enable fight detection")
|
|
|
|
| 505 |
):
|
| 506 |
"""
|
| 507 |
Detect weapons (knife/dao/gun), fights, and NSFW content in videos
|
|
|
|
| 508 |
Supports: MP4, AVI, MOV, MKV, WEBM, FLV, WMV
|
| 509 |
Max size: 500MB
|
| 510 |
+
Note: Videos are automatically deleted after processing to save disk space
|
| 511 |
"""
|
| 512 |
request_id = generate_request_id()
|
| 513 |
start_time = datetime.now()
|
| 514 |
+
upload_path = None
|
| 515 |
|
| 516 |
try:
|
| 517 |
# Validate file extension
|
|
|
|
| 528 |
# Get file size
|
| 529 |
file_size = upload_path.stat().st_size
|
| 530 |
if not validate_file_size(file_size, config.MAX_VIDEO_SIZE):
|
|
|
|
| 531 |
raise HTTPException(
|
| 532 |
status_code=400,
|
| 533 |
detail=f"File too large. Maximum size: {config.MAX_VIDEO_SIZE / (1024 * 1024):.1f}MB"
|
|
|
|
| 556 |
"size_mb": round(file_size / (1024 * 1024), 2)
|
| 557 |
}
|
| 558 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 559 |
# Process video frames
|
| 560 |
logger.info(f"Processing video {request_id}: {total_frames} frames, skip={frame_skip}")
|
| 561 |
|
|
|
|
| 622 |
all_nsfw.extend(processed['nsfw'])
|
| 623 |
all_fights.extend(processed['fights'])
|
| 624 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
# Log progress every 100 frames
|
| 626 |
if processed_count % 100 == 0:
|
| 627 |
logger.info(f"Processed {processed_count} frames...")
|
| 628 |
|
| 629 |
# Release resources
|
| 630 |
cap.release()
|
|
|
|
|
|
|
| 631 |
|
| 632 |
# Calculate summary
|
| 633 |
knife_count = sum(1 for w in all_weapons if 'knife' in w.class_name.lower() or 'dao' in w.class_name.lower())
|
|
|
|
| 666 |
# Calculate processing time
|
| 667 |
processing_time = (datetime.now() - start_time).total_seconds() * 1000
|
| 668 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 669 |
return VideoDetectionResponse(
|
| 670 |
success=True,
|
| 671 |
request_id=request_id,
|
|
|
|
| 676 |
summary=summary,
|
| 677 |
risk_level=risk_level,
|
| 678 |
action_required=(summary["total_detections"] > 0),
|
| 679 |
+
processed_video_url=None, # Always None since we don't save processed videos
|
| 680 |
processing_time_ms=processing_time
|
| 681 |
)
|
| 682 |
|
|
|
|
| 690 |
detail=f"Internal server error: {str(e)}"
|
| 691 |
)
|
| 692 |
finally:
|
| 693 |
+
# Always cleanup uploaded video file after processing
|
| 694 |
+
if upload_path and upload_path.exists():
|
| 695 |
try:
|
| 696 |
upload_path.unlink()
|
| 697 |
+
logger.info(f"Cleaned up uploaded video: {upload_path}")
|
| 698 |
+
except Exception as cleanup_error:
|
| 699 |
+
logger.warning(f"Failed to cleanup uploaded video {upload_path}: {cleanup_error}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 700 |
|
| 701 |
|
| 702 |
@app.delete("/cleanup")
|
| 703 |
async def cleanup_old_files(hours: int = 24):
|
| 704 |
+
"""Clean up old files from upload and results directories (excluding videos from uploads as they are auto-deleted)"""
|
| 705 |
try:
|
| 706 |
from datetime import timedelta
|
| 707 |
cutoff_time = datetime.now() - timedelta(hours=hours)
|
| 708 |
|
| 709 |
deleted_count = 0
|
| 710 |
+
|
| 711 |
+
# Clean up images from all directories
|
| 712 |
for directory in [config.UPLOAD_DIR, config.RESULTS_DIR, config.PROCESSED_DIR]:
|
| 713 |
+
images_path = directory / "images"
|
| 714 |
+
if images_path.exists():
|
| 715 |
+
for file in images_path.iterdir():
|
| 716 |
+
if file.is_file():
|
| 717 |
+
file_time = datetime.fromtimestamp(file.stat().st_mtime)
|
| 718 |
+
if file_time < cutoff_time:
|
| 719 |
+
file.unlink()
|
| 720 |
+
deleted_count += 1
|
| 721 |
+
|
| 722 |
+
# Clean up any remaining uploaded videos (should be rare since they're auto-deleted)
|
| 723 |
+
upload_videos_path = config.UPLOAD_DIR / "videos"
|
| 724 |
+
if upload_videos_path.exists():
|
| 725 |
+
for file in upload_videos_path.iterdir():
|
| 726 |
+
if file.is_file():
|
| 727 |
+
file_time = datetime.fromtimestamp(file.stat().st_mtime)
|
| 728 |
+
if file_time < cutoff_time:
|
| 729 |
+
file.unlink()
|
| 730 |
+
deleted_count += 1
|
| 731 |
+
logger.info(f"Cleaned up old uploaded video: {file}")
|
| 732 |
+
|
| 733 |
+
# Note: No need to clean processed videos since we don't save them anymore
|
| 734 |
|
| 735 |
return {
|
| 736 |
"success": True,
|