Spaces:
Sleeping
Sleeping
| import os | |
| import json | |
| import time | |
| import threading | |
| from fastapi import FastAPI, HTTPException, BackgroundTasks | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse, FileResponse | |
| import uvicorn | |
| from typing import Dict | |
| from pathlib import Path | |
| import subprocess | |
| from datetime import datetime | |
| import torch | |
| # Import from vision_analyzer (previously cursor_tracker) | |
| from vision_analyzer import ( | |
| main_processing_loop, | |
| processing_status, | |
| ANALYSIS_OUTPUT_FOLDER, # Changed from CURSOR_TRACKING_OUTPUT_FOLDER | |
| log_message | |
| ) | |
| # FastAPI App Definition | |
| app = FastAPI(title="Video Analysis API", | |
| description="API to access video frame analysis results", | |
| version="1.0.0") | |
| # Add CORS middleware to allow cross-origin requests | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], # Allows all origins | |
| allow_credentials=True, | |
| allow_methods=["*"], # Allows all methods | |
| allow_headers=["*"], | |
| ) | |
| # Global variable to track if processing is running | |
| processing_thread = None | |
| def log_message(message): | |
| """Add a log message with timestamp""" | |
| timestamp = datetime.now().strftime("%H:%M:%S") | |
| log_entry = f"[{timestamp}] {message}" | |
| processing_status["logs"].append(log_entry) | |
| # Keep only the last 100 logs | |
| if len(processing_status["logs"]) > 100: | |
| processing_status["logs"] = processing_status["logs"][-100:] | |
| print(log_entry) | |
| async def startup_event(): | |
| """Run the processing loop in the background when the API starts""" | |
| global processing_thread | |
| if not (processing_thread and processing_thread.is_alive()): | |
| log_message("🚀 Starting RAR extraction, frame extraction, and vision analysis pipeline in background...") | |
| processing_thread = threading.Thread(target=main_processing_loop) | |
| processing_thread.daemon = True | |
| processing_thread.start() | |
| async def root(): | |
| """Root endpoint that returns basic info""" | |
| return { | |
| "message": "Video Analysis API", | |
| "status": "running", | |
| "endpoints": { | |
| "/status": "Get processing status", | |
| "/analysis-data": "List available analysis files", | |
| "/analysis-data/{filename}": "Get specific analysis data", | |
| "/start-processing": "Start processing pipeline", | |
| "/stop-processing": "Stop processing pipeline" | |
| } | |
| } | |
| async def get_status(): | |
| """Get current processing status""" | |
| return { | |
| "processing_status": processing_status, | |
| "analysis_folder": ANALYSIS_OUTPUT_FOLDER, | |
| "folder_exists": os.path.exists(ANALYSIS_OUTPUT_FOLDER) | |
| } | |
| async def list_analysis_data(): | |
| """List all available analysis JSON files""" | |
| if not os.path.exists(ANALYSIS_OUTPUT_FOLDER): | |
| return {"files": [], "message": "Analysis output folder does not exist yet"} | |
| json_files = [] | |
| for file in os.listdir(ANALYSIS_OUTPUT_FOLDER): | |
| if file.endswith(".json"): | |
| file_path = os.path.join(ANALYSIS_OUTPUT_FOLDER, file) | |
| file_stats = os.stat(file_path) | |
| json_files.append({ | |
| "filename": file, | |
| "size_bytes": file_stats.st_size, | |
| "modified_time": time.ctime(file_stats.st_mtime), | |
| "download_url": f"/analysis-data/{file}" | |
| }) | |
| return { | |
| "files": json_files, | |
| "total_files": len(json_files), | |
| "folder_path": ANALYSIS_OUTPUT_FOLDER | |
| } | |
| async def get_analysis_data(filename: str): | |
| """Get specific analysis data by filename""" | |
| if not filename.endswith(".json"): | |
| raise HTTPException(status_code=400, detail="File must be a JSON file") | |
| file_path = os.path.join(ANALYSIS_OUTPUT_FOLDER, filename) | |
| if not os.path.exists(file_path): | |
| raise HTTPException(status_code=404, detail=f"File {filename} not found") | |
| try: | |
| with open(file_path, "r") as f: | |
| data = json.load(f) | |
| # Add metadata | |
| file_stats = os.stat(file_path) | |
| # Extract summary information | |
| frame_analyses = data.get("frame_analyses", []) | |
| summary = data.get("summary", {}) | |
| response_data = { | |
| "filename": filename, | |
| "file_size_bytes": file_stats.st_size, | |
| "modified_time": time.ctime(file_stats.st_mtime), | |
| "total_frames": len(frame_analyses), | |
| "summary": summary, | |
| "frame_samples": frame_analyses[:5] # Return first 5 frames as samples | |
| } | |
| return response_data | |
| except json.JSONDecodeError: | |
| raise HTTPException(status_code=500, detail=f"Invalid JSON in file {filename}") | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Error reading file {filename}: {str(e)}") | |
| async def get_full_analysis_data(filename: str): | |
| """Get the complete analysis data including all frames""" | |
| if not filename.endswith(".json"): | |
| raise HTTPException(status_code=400, detail="File must be a JSON file") | |
| file_path = os.path.join(ANALYSIS_OUTPUT_FOLDER, filename) | |
| if not os.path.exists(file_path): | |
| raise HTTPException(status_code=404, detail=f"File {filename} not found") | |
| try: | |
| with open(file_path, "r") as f: | |
| data = json.load(f) | |
| # Add metadata | |
| file_stats = os.stat(file_path) | |
| data["metadata"] = { | |
| "filename": filename, | |
| "file_size_bytes": file_stats.st_size, | |
| "modified_time": time.ctime(file_stats.st_mtime) | |
| } | |
| return data | |
| except json.JSONDecodeError: | |
| raise HTTPException(status_code=500, detail=f"Invalid JSON in file {filename}") | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Error reading file {filename}: {str(e)}") | |
| async def start_processing(background_tasks: BackgroundTasks, start_index: int = 0): | |
| """Start the processing pipeline in the background""" | |
| global processing_thread | |
| if processing_thread and processing_thread.is_alive(): | |
| return {"message": "Processing is already running", "status": "already_running"} | |
| if processing_status["is_running"]: | |
| return {"message": "Processing is already running", "status": "already_running"} | |
| # Start processing in a background thread | |
| processing_thread = threading.Thread(target=main_processing_loop, args=(start_index,)) | |
| processing_thread.daemon = True | |
| processing_thread.start() | |
| return {"message": f"Processing started in background from index {start_index}", "status": "started"} | |
| async def stop_processing(): | |
| """Stop the processing pipeline""" | |
| global processing_thread | |
| if not processing_status["is_running"] and (not processing_thread or not processing_thread.is_alive()): | |
| return {"message": "No processing is currently running", "status": "not_running"} | |
| # Note: This is a graceful stop request | |
| processing_status["is_running"] = False | |
| return {"message": "Stop signal sent to processing pipeline", "status": "stop_requested"} | |
| async def get_analysis_summary(filename: str): | |
| """Get a summary of the analysis data""" | |
| if not filename.endswith(".json"): | |
| raise HTTPException(status_code=400, detail="File must be a JSON file") | |
| file_path = os.path.join(ANALYSIS_OUTPUT_FOLDER, filename) | |
| if not os.path.exists(file_path): | |
| raise HTTPException(status_code=404, detail=f"File {filename} not found") | |
| try: | |
| with open(file_path, "r") as f: | |
| data = json.load(f) | |
| # Get basic statistics | |
| frame_analyses = data.get("frame_analyses", []) | |
| summary = data.get("summary", {}) | |
| # Count frames with descriptions | |
| frames_with_descriptions = len([f for f in frame_analyses if f.get("description")]) | |
| file_stats = os.stat(file_path) | |
| return { | |
| "filename": filename, | |
| "file_size_bytes": file_stats.st_size, | |
| "modified_time": time.ctime(file_stats.st_mtime), | |
| "total_frames": len(frame_analyses), | |
| "frames_with_descriptions": frames_with_descriptions, | |
| "summary": summary, | |
| "steps": summary.get("steps", []), | |
| "high_level_goal": summary.get("high_level_goal", ""), | |
| "final_goal": summary.get("final_goal", "") | |
| } | |
| except json.JSONDecodeError: | |
| raise HTTPException(status_code=500, detail=f"Invalid JSON in file {filename}") | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Error reading file {filename}: {str(e)}") | |
| if __name__ == "__main__": | |
| # Start the FastAPI server | |
| print("Starting Video Analysis FastAPI Server...") | |
| print("API Documentation will be available at: http://localhost:8000/docs") | |
| print("API Root endpoint: http://localhost:8000/") | |
| # Ensure the analysis output folder exists | |
| os.makedirs(ANALYSIS_OUTPUT_FOLDER, exist_ok=True) | |
| uvicorn.run( | |
| app, | |
| host="0.0.0.0", | |
| port=8000, | |
| log_level="info", | |
| reload=False # Set to False for production | |
| ) |