Factor Studios commited on
Commit
6f4a84a
·
verified ·
1 Parent(s): 4352b30

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +154 -185
app.py CHANGED
@@ -1,220 +1,189 @@
1
- import gradio as gr
2
  import os
3
  import json
4
  import time
5
  import threading
 
 
 
 
 
 
 
 
6
  from datetime import datetime
7
 
 
 
8
  # Import from vision_analyzer (previously cursor_tracker)
9
  from vision_analyzer import (
10
  main_processing_loop,
11
  processing_status,
12
- ANALYSIS_OUTPUT_FOLDER,
13
- log_message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  )
15
 
16
  # Global variable to track if processing is running
17
  processing_thread = None
18
 
19
- def start_processing_gradio(start_index: int = 0):
20
- global processing_thread
 
 
 
21
 
22
- if processing_thread and processing_thread.is_alive():
23
- return "Processing is already running"
 
24
 
25
- if processing_status["is_running"]:
26
- return "Processing is already running"
27
-
28
- # Start processing in a background thread
29
- processing_thread = threading.Thread(target=main_processing_loop, args=(start_index,))
30
- processing_thread.daemon = True
31
- processing_thread.start()
32
-
33
- return f"Processing started in background from index {start_index}"
34
 
35
- def stop_processing_gradio():
 
 
36
  global processing_thread
37
-
38
- if not processing_status["is_running"] and (not processing_thread or not processing_thread.is_alive()):
39
- return "No processing is currently running"
40
-
41
- # Note: This is a graceful stop request
42
- processing_status["is_running"] = False
43
-
44
- return "Stop signal sent to processing pipeline"
45
 
46
- def get_status_gradio():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  return {
48
  "processing_status": processing_status,
49
- "analysis_folder": ANALYSIS_OUTPUT_FOLDER,
50
- "folder_exists": os.path.exists(ANALYSIS_OUTPUT_FOLDER)
51
  }
52
 
53
- def list_analysis_data_gradio():
54
- if not os.path.exists(ANALYSIS_OUTPUT_FOLDER):
55
- return "Analysis output folder does not exist yet"
56
-
57
- json_files = []
58
- for file in os.listdir(ANALYSIS_OUTPUT_FOLDER):
59
- if file.endswith(".json"):
60
- file_path = os.path.join(ANALYSIS_OUTPUT_FOLDER, file)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  file_stats = os.stat(file_path)
62
- json_files.append({
63
  "filename": file,
64
  "size_bytes": file_stats.st_size,
65
  "modified_time": time.ctime(file_stats.st_mtime),
 
66
  })
67
 
68
- return json.dumps({
69
- "files": json_files,
70
- "total_files": len(json_files),
71
- "folder_path": ANALYSIS_OUTPUT_FOLDER
72
- }, indent=2)
73
-
74
- def get_analysis_data_gradio(filename: str):
75
- if not filename.endswith(".json"):
76
- return "File must be a JSON file"
77
-
78
- file_path = os.path.join(ANALYSIS_OUTPUT_FOLDER, filename)
79
-
80
- if not os.path.exists(file_path):
81
- return f"File {filename} not found"
82
-
83
- try:
84
- with open(file_path, "r") as f:
85
- data = json.load(f)
86
-
87
- # Add metadata
88
- file_stats = os.stat(file_path)
89
-
90
- # Extract summary information
91
- frame_analyses = data.get("frame_analyses", [])
92
- summary = data.get("summary", {})
93
-
94
- response_data = {
95
- "filename": filename,
96
- "file_size_bytes": file_stats.st_size,
97
- "modified_time": time.ctime(file_stats.st_mtime),
98
- "total_frames": len(frame_analyses),
99
- "summary": summary,
100
- "frame_samples": frame_analyses[:5] # Return first 5 frames as samples
101
- }
102
-
103
- return json.dumps(response_data, indent=2)
104
-
105
- except json.JSONDecodeError:
106
- return f"Invalid JSON in file {filename}"
107
- except Exception as e:
108
- return f"Error reading file {filename}: {str(e)}"
109
-
110
- def get_full_analysis_data_gradio(filename: str):
111
- if not filename.endswith(".json"):
112
- return "File must be a JSON file"
113
-
114
- file_path = os.path.join(ANALYSIS_OUTPUT_FOLDER, filename)
115
-
116
- if not os.path.exists(file_path):
117
- return f"File {filename} not found"
118
-
119
- try:
120
- with open(file_path, "r") as f:
121
- data = json.load(f)
122
-
123
- # Add metadata
124
- file_stats = os.stat(file_path)
125
- data["metadata"] = {
126
- "filename": filename,
127
- "file_size_bytes": file_stats.st_size,
128
- "modified_time": time.ctime(file_stats.st_mtime)
129
- }
130
-
131
- return json.dumps(data, indent=2)
132
-
133
- except json.JSONDecodeError:
134
- return f"Invalid JSON in file {filename}"
135
- except Exception as e:
136
- return f"Error reading file {filename}: {str(e)}"
137
-
138
- def get_analysis_summary_gradio(filename: str):
139
- if not filename.endswith(".json"):
140
- return "File must be a JSON file"
141
-
142
- file_path = os.path.join(ANALYSIS_OUTPUT_FOLDER, filename)
143
-
144
- if not os.path.exists(file_path):
145
- return f"File {filename} not found"
146
-
147
- try:
148
- with open(file_path, "r") as f:
149
- data = json.load(f)
150
-
151
- # Get basic statistics
152
- frame_analyses = data.get("frame_analyses", [])
153
- summary = data.get("summary", {})
154
-
155
- # Count frames with descriptions
156
- frames_with_descriptions = len([f for f in frame_analyses if f.get("description")])
157
-
158
- file_stats = os.stat(file_path)
159
-
160
- return json.dumps({
161
- "filename": filename,
162
- "file_size_bytes": file_stats.st_size,
163
- "modified_time": time.ctime(file_stats.st_mtime),
164
- "total_frames": len(frame_analyses),
165
- "frames_with_descriptions": frames_with_descriptions,
166
- "summary": summary,
167
- "steps": summary.get("steps", []),
168
- "high_level_goal": summary.get("high_level_goal", ""),
169
- "final_goal": summary.get("final_goal", "")
170
- }, indent=2)
171
-
172
- except json.JSONDecodeError:
173
- return f"Invalid JSON in file {filename}"
174
- except Exception as e:
175
- return f"Error reading file {filename}: {str(e)}"
176
-
177
- # Ensure the analysis output folder exists
178
- os.makedirs(ANALYSIS_OUTPUT_FOLDER, exist_ok=True)
179
 
180
- # Gradio Interface
181
- with gr.Blocks() as demo:
182
- gr.Markdown("# Video Analysis Interface")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
- with gr.Tab("Processing Control"):
185
- gr.Markdown("## Start/Stop Video Processing")
186
- start_index_input = gr.Number(label="Start Index", value=0, precision=0)
187
- start_btn = gr.Button("Start Processing")
188
- stop_btn = gr.Button("Stop Processing")
189
- processing_output = gr.Textbox(label="Processing Status")
190
-
191
- start_btn.click(start_processing_gradio, inputs=start_index_input, outputs=processing_output)
192
- stop_btn.click(stop_processing_gradio, outputs=processing_output)
193
-
194
- with gr.Tab("Analysis Data"):
195
- gr.Markdown("## View Analysis Data")
196
- list_data_btn = gr.Button("List All Analysis Files")
197
- list_data_output = gr.JSON(label="Available Analysis Files")
198
-
199
- list_data_btn.click(list_analysis_data_gradio, outputs=list_data_output)
200
-
201
- gr.Markdown("### Get Specific Analysis Data")
202
- filename_input = gr.Textbox(label="Filename (e.g., video_analysis.json)")
203
- get_data_btn = gr.Button("Get Analysis Data (Summary)")
204
- get_full_data_btn = gr.Button("Get Full Analysis Data")
205
- get_summary_btn = gr.Button("Get Analysis Summary")
206
- specific_data_output = gr.JSON(label="Analysis Data")
207
-
208
- get_data_btn.click(get_analysis_data_gradio, inputs=filename_input, outputs=specific_data_output)
209
- get_full_data_btn.click(get_full_analysis_data_gradio, inputs=filename_input, outputs=specific_data_output)
210
- get_summary_btn.click(get_analysis_summary_gradio, inputs=filename_input, outputs=specific_data_output)
211
 
212
- with gr.Tab("Current Status"):
213
- gr.Markdown("## Current Processing Status")
214
- status_btn = gr.Button("Refresh Status")
215
- status_output = gr.JSON(label="Current Status")
216
-
217
- status_btn.click(get_status_gradio, outputs=status_output)
218
 
219
  if __name__ == "__main__":
220
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import json
3
  import time
4
  import threading
5
+ from fastapi import FastAPI, HTTPException, BackgroundTasks
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from fastapi.responses import JSONResponse, FileResponse
8
+ from fastapi.staticfiles import StaticFiles
9
+ import uvicorn
10
+ from typing import Dict
11
+ from pathlib import Path
12
+ import subprocess
13
  from datetime import datetime
14
 
15
+ import torch
16
+
17
  # Import from vision_analyzer (previously cursor_tracker)
18
  from vision_analyzer import (
19
  main_processing_loop,
20
  processing_status,
21
+ log_message,
22
+ FRAMES_OUTPUT_FOLDER # Add this import for frames directory
23
+ )
24
+
25
+ # FastAPI App Definition
26
+ app = FastAPI(title="Video Analysis API",
27
+ description="API to access video frame analysis results and extracted images",
28
+ version="1.0.0")
29
+
30
+ # Add CORS middleware to allow cross-origin requests
31
+ app.add_middleware(
32
+ CORSMiddleware,
33
+ allow_origins=["*"], # Allows all origins
34
+ allow_credentials=True,
35
+ allow_methods=["*"], # Allows all methods
36
+ allow_headers=["*"],
37
  )
38
 
39
  # Global variable to track if processing is running
40
  processing_thread = None
41
 
42
+ def log_message(message):
43
+ """Add a log message with timestamp"""
44
+ timestamp = datetime.now().strftime("%H:%M:%S")
45
+ log_entry = f"[{timestamp}] {message}"
46
+ processing_status["logs"].append(log_entry)
47
 
48
+ # Keep only the last 100 logs
49
+ if len(processing_status["logs"]) > 100:
50
+ processing_status["logs"] = processing_status["logs"][-100:]
51
 
52
+ print(log_entry)
 
 
 
 
 
 
 
 
53
 
54
+ @app.on_event("startup")
55
+ async def startup_event():
56
+ """Run the processing loop in the background when the API starts"""
57
  global processing_thread
58
+ if not (processing_thread and processing_thread.is_alive()):
59
+ log_message("🚀 Starting RAR extraction, frame extraction, and vision analysis pipeline in background...")
60
+ processing_thread = threading.Thread(target=main_processing_loop)
61
+ processing_thread.daemon = True
62
+ processing_thread.start()
 
 
 
63
 
64
+ @app.get("/")
65
+ async def root():
66
+ """Root endpoint that returns basic info"""
67
+ return {
68
+ "message": "Video Analysis API",
69
+ "status": "running",
70
+ "endpoints": {
71
+ "/status": "Get processing status",
72
+ "/courses": "List all available course folders",
73
+ "/images/{course_folder}": "List images in a course folder",
74
+ "/images/{course_folder}/{frame_filename}": "Get specific frame image",
75
+ "/start-processing": "Start processing pipeline",
76
+ "/stop-processing": "Stop processing pipeline"
77
+ }
78
+ }
79
+
80
+ @app.get("/status")
81
+ async def get_status():
82
+ """Get current processing status"""
83
  return {
84
  "processing_status": processing_status,
85
+ "frames_folder": FRAMES_OUTPUT_FOLDER,
86
+ "frames_folder_exists": os.path.exists(FRAMES_OUTPUT_FOLDER)
87
  }
88
 
89
+ # ===== NEW IMAGE SERVING ENDPOINTS =====
90
+
91
+ @app.get("/images/{course_folder}/{frame_filename}")
92
+ async def get_frame_image(course_folder: str, frame_filename: str):
93
+ """
94
+ Serve extracted frame images from course folders
95
+
96
+ Args:
97
+ course_folder: The course folder name (e.g., "course1_video1_mp4_frames")
98
+ frame_filename: The frame file name (e.g., "0001.png")
99
+ """
100
+ # Construct the full path to the image
101
+ image_path = os.path.join(FRAMES_OUTPUT_FOLDER, course_folder, frame_filename)
102
+
103
+ # Check if file exists
104
+ if not os.path.exists(image_path):
105
+ raise HTTPException(status_code=404, detail=f"Image not found: {course_folder}/{frame_filename}")
106
+
107
+ # Verify it's an image file
108
+ if not frame_filename.lower().endswith(('.png', '.jpg', '.jpeg')):
109
+ raise HTTPException(status_code=400, detail="File must be an image (PNG, JPG, JPEG)")
110
+
111
+ # Return the image file
112
+ return FileResponse(image_path)
113
+
114
+ @app.get("/images/{course_folder}")
115
+ async def list_course_images(course_folder: str):
116
+ """
117
+ List all available images in a specific course folder
118
+
119
+ Args:
120
+ course_folder: The course folder name
121
+ """
122
+ folder_path = os.path.join(FRAMES_OUTPUT_FOLDER, course_folder)
123
+
124
+ if not os.path.exists(folder_path):
125
+ raise HTTPException(status_code=404, detail=f"Course folder not found: {course_folder}")
126
+
127
+ # Get all image files
128
+ image_files = []
129
+ for file in os.listdir(folder_path):
130
+ if file.lower().endswith(('.png', '.jpg', '.jpeg')):
131
+ file_path = os.path.join(folder_path, file)
132
  file_stats = os.stat(file_path)
133
+ image_files.append({
134
  "filename": file,
135
  "size_bytes": file_stats.st_size,
136
  "modified_time": time.ctime(file_stats.st_mtime),
137
+ "url": f"/images/{course_folder}/{file}"
138
  })
139
 
140
+ return {
141
+ "course_folder": course_folder,
142
+ "total_images": len(image_files),
143
+ "images": image_files
144
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
+ @app.get("/courses")
147
+ async def list_all_courses():
148
+ """
149
+ List all available course folders with their image counts
150
+ """
151
+ if not os.path.exists(FRAMES_OUTPUT_FOLDER):
152
+ return {"courses": [], "message": "Frames output folder does not exist yet"}
153
+
154
+ courses = []
155
+ for folder in os.listdir(FRAMES_OUTPUT_FOLDER):
156
+ folder_path = os.path.join(FRAMES_OUTPUT_FOLDER, folder)
157
+ if os.path.isdir(folder_path):
158
+ # Count image files
159
+ image_count = len([f for f in os.listdir(folder_path)
160
+ if f.lower().endswith(('.png', '.jpg', '.jpeg'))])
161
+ courses.append({
162
+ "course_folder": folder,
163
+ "image_count": image_count,
164
+ "images_url": f"/images/{folder}",
165
+ "sample_image_url": f"/images/{folder}/0001.png" if image_count > 0 else None
166
+ })
167
 
168
+ return {
169
+ "total_courses": len(courses),
170
+ "courses": courses
171
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
 
 
 
 
 
 
173
 
174
  if __name__ == "__main__":
175
+ # Start the FastAPI server
176
+ print("Starting Video Analysis FastAPI Server...")
177
+ print("API Documentation will be available at: http://localhost:8000/docs")
178
+ print("API Root endpoint: http://localhost:8000/")
179
+
180
+ # Ensure the analysis output folder exists
181
+ os.makedirs(FRAMES_OUTPUT_FOLDER, exist_ok=True)
182
+
183
+ uvicorn.run(
184
+ app,
185
+ host="0.0.0.0",
186
+ port=8000,
187
+ log_level="info",
188
+ reload=False # Set to False for production
189
+ )