Fred808 commited on
Commit
cf5a888
Β·
verified Β·
1 Parent(s): dccacf9

Update vision_analyzer.py

Browse files
Files changed (1) hide show
  1. vision_analyzer.py +73 -95
vision_analyzer.py CHANGED
@@ -8,46 +8,25 @@ import re
8
  import threading
9
  from typing import Dict, List, Set, Optional
10
  from huggingface_hub import HfApi, list_repo_files
11
- from fastapi import FastAPI, File, UploadFile, Form
12
- from fastapi.responses import JSONResponse
 
13
  from pathlib import Path
14
  import smtplib
15
  from email.message import EmailMessage
16
- import tempfile
17
- import rarfile
18
- import zipfile
19
- import cv2
20
- import numpy as np
21
- from PIL import Image
22
- import torch
23
- from transformers import AutoProcessor, AutoModelForCausalLM
24
- from fastapi.staticfiles import StaticFiles
25
-
26
- from openai import OpenAI
27
-
28
- # Initialize FastAPI
29
- app = FastAPI()
30
 
31
  # ==== CONFIGURATION ====
32
  HF_TOKEN = os.getenv("HF_TOKEN", "")
33
  SOURCE_REPO_ID = os.getenv("SOURCE_REPO", "Fred808/BG1")
34
 
35
- BASE_URL = "https://openrouter.ai/api/v1"
36
- SITE_URL = os.getenv("SITE_URL", "https://fred808-vis1.hf.space")
37
- SITE_TITLE = os.getenv("SITE_TITLE", "FrameAnalyzer")
38
- LOG_PATH = os.getenv("LOG_PATH", "openrouter_responses.json")
39
-
40
-
41
  # Path Configuration
42
  DOWNLOAD_FOLDER = "downloads"
43
  EXTRACT_FOLDER = "extracted"
44
  FRAMES_OUTPUT_FOLDER = "extracted_frames"
45
- ANALYSIS_OUTPUT_FOLDER = "analysis_results"
46
 
47
  os.makedirs(DOWNLOAD_FOLDER, exist_ok=True)
48
  os.makedirs(EXTRACT_FOLDER, exist_ok=True)
49
  os.makedirs(FRAMES_OUTPUT_FOLDER, exist_ok=True)
50
- os.makedirs(ANALYSIS_OUTPUT_FOLDER, exist_ok=True)
51
 
52
  # State Files
53
  DOWNLOAD_STATE_FILE = "download_progress.json"
@@ -61,7 +40,10 @@ MAX_RETRIES = 3
61
  MIN_FREE_SPACE_GB = 2 # Minimum free space in GB before processing
62
 
63
  # Frame Extraction Parameters
64
- DEFAULT_FPS = 0.1 # Default frames per second for extraction
 
 
 
65
 
66
  # Initialize HF API
67
  hf_api = HfApi(token=HF_TOKEN)
@@ -76,14 +58,11 @@ processing_status = {
76
  "extracted_courses": 0,
77
  "extracted_videos": 0,
78
  "extracted_frames_count": 0,
79
- "analyzed_frames_count": 0,
80
  "last_update": None,
81
  "logs": []
82
  }
83
 
84
-
85
-
86
-
87
  def log_message(message: str):
88
  """Log messages with timestamp"""
89
  timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
@@ -97,7 +76,7 @@ def log_message(message: str):
97
  def log_failed_file(filename: str, error: str):
98
  """Log failed files to persistent file"""
99
  with open(FAILED_FILES_LOG, "a") as f:
100
- f.write(f'{time.strftime("%Y-%m-%d %H:%M:%S")} - {filename}: {error}\n')
101
 
102
  def get_disk_usage(path: str) -> Dict[str, float]:
103
  """Get disk usage statistics in GB"""
@@ -108,7 +87,7 @@ def get_disk_usage(path: str) -> Dict[str, float]:
108
  return {"total": total, "free": free, "used": used}
109
 
110
  def check_disk_space(path: str = ".") -> bool:
111
- """Check if there\'s enough disk space"""
112
  disk_info = get_disk_usage(path)
113
  if disk_info["free"] < MIN_FREE_SPACE_GB:
114
  log_message(f'⚠️ Low disk space: {disk_info["free"]:.2f}GB free, {disk_info["used"]:.2f}GB used')
@@ -245,7 +224,8 @@ def extract_with_retry(rar_path: str, output_dir: str, max_retries: int = 2) ->
245
  time.sleep(1)
246
 
247
  return False
248
-
 
249
  def ensure_dir(path):
250
  os.makedirs(path, exist_ok=True)
251
 
@@ -258,10 +238,12 @@ def extract_frames(video_path, output_dir, fps=DEFAULT_FPS):
258
  log_message(f"[ERROR] Failed to open video file: {video_path}")
259
  return 0
260
  video_fps = cap.get(cv2.CAP_PROP_FPS)
 
261
  if not video_fps or video_fps <= 0:
262
  video_fps = 30 # fallback if FPS is not available
263
  log_message(f"[WARN] Using fallback FPS: {video_fps}")
264
  frame_interval = int(round(video_fps / fps))
 
265
  frame_idx = 0
266
  saved_idx = 1
267
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
@@ -269,21 +251,38 @@ def extract_frames(video_path, output_dir, fps=DEFAULT_FPS):
269
  while cap.isOpened():
270
  ret, frame = cap.read()
271
  if not ret:
 
272
  break
273
  if frame_idx % frame_interval == 0:
274
- if saved_idx <= 10: # Limit to 10 frames for testing
275
- frame_name = f"{saved_idx:04d}.png"
276
- cv2.imwrite(str(Path(output_dir) / frame_name), frame)
277
- saved_idx += 1
278
- else:
279
- break # Stop extracting after 10 frames
280
  frame_idx += 1
281
  cap.release()
282
  log_message(f"Extracted {saved_idx-1} frames from {video_path} to {output_dir}")
283
  return saved_idx - 1
284
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  def process_rar_file(rar_path: str) -> bool:
286
- """Process a single RAR file with new frame processing"""
287
  filename = os.path.basename(rar_path)
288
  processing_status["current_file"] = filename
289
 
@@ -307,39 +306,39 @@ def process_rar_file(rar_path: str) -> bool:
307
  if not extract_with_retry(rar_path, extract_dir):
308
  raise Exception("RAR extraction failed")
309
 
310
- # Process video files
311
- video_files = []
312
- for root, _, files in os.walk(extract_dir):
 
313
  for file in files:
 
314
  if file.lower().endswith((".mp4", ".avi", ".mov", ".mkv")):
315
- video_files.append(os.path.join(root, file))
316
 
317
  processing_status["extracted_courses"] += 1
318
- log_message(f"βœ… Extracted {len(video_files)} videos from \'{course_name}\'")
319
 
320
- # Process each video
321
- for video_path in video_files:
322
  video_filename = Path(video_path).name
323
- video_filename_clean = video_filename.replace(".", "_")
324
- frames_dir = os.path.join(FRAMES_OUTPUT_FOLDER, f"{course_name}_{video_filename_clean}_frames")
325
- ensure_dir(frames_dir)
326
-
327
- # Extract frames
328
- extracted_count = extract_frames(video_path, frames_dir, DEFAULT_FPS)
329
- if extracted_count == 0:
330
- raise Exception(f"No frames extracted from {video_filename}")
331
 
332
- processing_status["extracted_frames_count"] += extracted_count
333
-
334
- # Analyze frames
335
- video_filename_clean = video_filename.replace(".", "_")
336
- analysis_output = os.path.join(ANALYSIS_OUTPUT_FOLDER, f"{course_name}_{video_filename_clean}_analysis.json")
337
- if process_video_frames(frames_dir, video_filename, analysis_output):
338
- processing_status["analyzed_frames_count"] += extracted_count
339
  processing_status["extracted_videos"] += 1
 
 
 
 
 
 
 
340
  else:
341
- raise Exception(f"Frame analysis failed for {video_filename}")
342
-
343
  return True
344
 
345
  except Exception as e:
@@ -351,18 +350,14 @@ def process_rar_file(rar_path: str) -> bool:
351
  finally:
352
  processing_status["current_file"] = None
353
 
354
-
355
-
356
-
357
-
358
  def main_processing_loop(start_index: int = 0):
359
- """Main processing workflow - extraction, frame extraction, and vision analysis"""
360
  processing_status["is_running"] = True
361
 
362
  try:
363
  # Load state
364
  processed_rars = load_json_state(PROCESS_STATE_FILE, {"processed_rars": []})["processed_rars"]
365
- download_state = load_json_state(DOWNLOAD_STATE_FILE, {"next_download_index": 0})
366
 
367
  # Use start_index if provided, otherwise use the saved state
368
  next_index = start_index if start_index > 0 else download_state["next_download_index"]
@@ -432,11 +427,9 @@ def main_processing_loop(start_index: int = 0):
432
 
433
  # Status update
434
  log_message(f"πŸ“Š Progress: {next_index}/{len(rar_files)} files processed")
435
- log_message(f"πŸ“Š Extracted: {processing_status['extracted_courses']} courses")
436
- log_message(f"πŸ“Š Videos Processed: {processing_status['extracted_videos']} videos")
437
- log_message(f"πŸ“Š Frames Extracted: {processing_status['extracted_frames_count']} frames")
438
- log_message(f"πŸ“Š Frames Analyzed: {processing_status['analyzed_frames_count']} frames")
439
- log_message(f"πŸ“Š Failed: {processing_status['failed_files']} files")
440
 
441
  if next_index < len(rar_files):
442
  log_message(f"πŸ”„ Run the script again to process the next file: {os.path.basename(rar_files[next_index])}")
@@ -445,8 +438,8 @@ def main_processing_loop(start_index: int = 0):
445
  else:
446
  log_message("βœ… All files have been processed!")
447
 
448
- log_message(f"πŸŽ‰ Processing complete!")
449
- log_message(f"πŸ“Š Final stats: {processing_status['extracted_courses']} courses extracted, {processing_status['extracted_videos']} videos processed, {processing_status['extracted_frames_count']} frames extracted, {processing_status['analyzed_frames_count']} frames analyzed")
450
 
451
  except KeyboardInterrupt:
452
  log_message("⏹️ Processing interrupted by user")
@@ -456,31 +449,16 @@ def main_processing_loop(start_index: int = 0):
456
  processing_status["is_running"] = False
457
  cleanup_temp_files()
458
 
459
-
460
-
461
- @app.get("/health")
462
- async def health_check():
463
- """Health check endpoint."""
464
- return JSONResponse(content={
465
- "status": "healthy",
466
- "model": "GIT",
467
- "note": "Now using GIT model."
468
- })
469
-
470
- @app.get("/status")
471
- async def get_processing_status():
472
- """Get current processing status."""
473
- return JSONResponse(content=processing_status)
474
-
475
- # Expose necessary functions and variables
476
  __all__ = [
477
  "main_processing_loop",
478
  "processing_status",
479
- "ANALYSIS_OUTPUT_FOLDER",
480
  "log_message",
481
- "analyze_single_frame",
482
  "extract_frames",
483
  "DEFAULT_FPS",
484
  "ensure_dir"
485
  ]
486
 
 
 
 
 
8
  import threading
9
  from typing import Dict, List, Set, Optional
10
  from huggingface_hub import HfApi, list_repo_files
11
+
12
+ import cv2
13
+ import numpy as np
14
  from pathlib import Path
15
  import smtplib
16
  from email.message import EmailMessage
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  # ==== CONFIGURATION ====
19
  HF_TOKEN = os.getenv("HF_TOKEN", "")
20
  SOURCE_REPO_ID = os.getenv("SOURCE_REPO", "Fred808/BG1")
21
 
 
 
 
 
 
 
22
  # Path Configuration
23
  DOWNLOAD_FOLDER = "downloads"
24
  EXTRACT_FOLDER = "extracted"
25
  FRAMES_OUTPUT_FOLDER = "extracted_frames"
 
26
 
27
  os.makedirs(DOWNLOAD_FOLDER, exist_ok=True)
28
  os.makedirs(EXTRACT_FOLDER, exist_ok=True)
29
  os.makedirs(FRAMES_OUTPUT_FOLDER, exist_ok=True)
 
30
 
31
  # State Files
32
  DOWNLOAD_STATE_FILE = "download_progress.json"
 
40
  MIN_FREE_SPACE_GB = 2 # Minimum free space in GB before processing
41
 
42
  # Frame Extraction Parameters
43
+ DEFAULT_FPS = 3 # Default frames per second for extraction
44
+
45
+ # Cursor Tracking Parameters
46
+ CURSOR_THRESHOLD = 0.8
47
 
48
  # Initialize HF API
49
  hf_api = HfApi(token=HF_TOKEN)
 
58
  "extracted_courses": 0,
59
  "extracted_videos": 0,
60
  "extracted_frames_count": 0,
61
+ "tracked_cursors_count": 0,
62
  "last_update": None,
63
  "logs": []
64
  }
65
 
 
 
 
66
  def log_message(message: str):
67
  """Log messages with timestamp"""
68
  timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
 
76
  def log_failed_file(filename: str, error: str):
77
  """Log failed files to persistent file"""
78
  with open(FAILED_FILES_LOG, "a") as f:
79
+ f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {filename}: {error}\n")
80
 
81
  def get_disk_usage(path: str) -> Dict[str, float]:
82
  """Get disk usage statistics in GB"""
 
87
  return {"total": total, "free": free, "used": used}
88
 
89
  def check_disk_space(path: str = ".") -> bool:
90
+ """Check if there's enough disk space"""
91
  disk_info = get_disk_usage(path)
92
  if disk_info["free"] < MIN_FREE_SPACE_GB:
93
  log_message(f'⚠️ Low disk space: {disk_info["free"]:.2f}GB free, {disk_info["used"]:.2f}GB used')
 
224
  time.sleep(1)
225
 
226
  return False
227
+
228
+ # --- Frame Extraction Utilities ---
229
  def ensure_dir(path):
230
  os.makedirs(path, exist_ok=True)
231
 
 
238
  log_message(f"[ERROR] Failed to open video file: {video_path}")
239
  return 0
240
  video_fps = cap.get(cv2.CAP_PROP_FPS)
241
+ # log_message(f"[DEBUG] Video FPS: {video_fps}")
242
  if not video_fps or video_fps <= 0:
243
  video_fps = 30 # fallback if FPS is not available
244
  log_message(f"[WARN] Using fallback FPS: {video_fps}")
245
  frame_interval = int(round(video_fps / fps))
246
+ # log_message(f"[DEBUG] Frame interval: {frame_interval}")
247
  frame_idx = 0
248
  saved_idx = 1
249
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
 
251
  while cap.isOpened():
252
  ret, frame = cap.read()
253
  if not ret:
254
+ # log_message(f"[DEBUG] No more frames to read at frame_idx {frame_idx}.")
255
  break
256
  if frame_idx % frame_interval == 0:
257
+ frame_name = f"{saved_idx:04d}.png"
258
+ cv2.imwrite(str(Path(output_dir) / frame_name), frame)
259
+ # log_message(f"[DEBUG] Saved frame {frame_idx} as {frame_name}")
260
+ saved_idx += 1
 
 
261
  frame_idx += 1
262
  cap.release()
263
  log_message(f"Extracted {saved_idx-1} frames from {video_path} to {output_dir}")
264
  return saved_idx - 1
265
 
266
+ # --- Cursor Tracking Utilities ---
267
+ def to_rgb(img):
268
+ if img is None:
269
+ return None
270
+ if len(img.shape) == 2:
271
+ return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
272
+ if img.shape[2] == 4:
273
+ return cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
274
+ return img
275
+
276
+ def get_mask_from_alpha(template_img):
277
+ if template_img is not None and len(template_img.shape) == 3 and template_img.shape[2] == 4:
278
+ # Use alpha channel as mask (nonzero alpha = 255)
279
+ return (template_img[:, :, 3] > 0).astype(np.uint8) * 255
280
+ return None
281
+
282
+
283
+
284
  def process_rar_file(rar_path: str) -> bool:
285
+ """Process a single RAR file - extract, then process videos for frames and cursor tracking"""
286
  filename = os.path.basename(rar_path)
287
  processing_status["current_file"] = filename
288
 
 
306
  if not extract_with_retry(rar_path, extract_dir):
307
  raise Exception("RAR extraction failed")
308
 
309
+ # Count extracted files
310
+ file_count = 0
311
+ video_files_found = []
312
+ for root, dirs, files in os.walk(extract_dir):
313
  for file in files:
314
+ file_count += 1
315
  if file.lower().endswith((".mp4", ".avi", ".mov", ".mkv")):
316
+ video_files_found.append(os.path.join(root, file))
317
 
318
  processing_status["extracted_courses"] += 1
319
+ log_message(f"βœ… Successfully extracted \'{course_name}\' ({file_count} files, {len(video_files_found)} videos)")
320
 
321
+ # Process video files for frame extraction and cursor tracking
322
+ for video_path in video_files_found:
323
  video_filename = Path(video_path).name
324
+ # Create a unique output directory for frames for each video
325
+ frames_output_dir = os.path.join(FRAMES_OUTPUT_FOLDER, f"{course_name}_{video_filename.replace('.', '_')}_frames")
326
+ ensure_dir(frames_output_dir)
 
 
 
 
 
327
 
328
+ extracted_frames_count = extract_frames(video_path, frames_output_dir, fps=DEFAULT_FPS)
329
+ processing_status["extracted_frames_count"] += extracted_frames_count
330
+ if extracted_frames_count > 0:
 
 
 
 
331
  processing_status["extracted_videos"] += 1
332
+ log_message(f"[INFO] Extracted {extracted_frames_count} frames from {video_filename}")
333
+
334
+ # Perform cursor tracking on the extracted frames
335
+ cursor_output_json = os.path.join(CURSOR_TRACKING_OUTPUT_FOLDER, f"{course_name}_{video_filename.replace('.', '_')}_cursor_data.json")
336
+ tracked_cursors = track_cursor(frames_output_dir, CURSOR_TEMPLATES_DIR, cursor_output_json, threshold=CURSOR_THRESHOLD)
337
+ processing_status["tracked_cursors_count"] += tracked_cursors
338
+ log_message(f"[INFO] Tracked {tracked_cursors} cursors in frames from {video_filename}")
339
  else:
340
+ log_message(f"[WARN] No frames extracted from {video_filename}")
341
+
342
  return True
343
 
344
  except Exception as e:
 
350
  finally:
351
  processing_status["current_file"] = None
352
 
 
 
 
 
353
  def main_processing_loop(start_index: int = 0):
354
+ """Main processing workflow - extraction, frame extraction, and cursor tracking"""
355
  processing_status["is_running"] = True
356
 
357
  try:
358
  # Load state
359
  processed_rars = load_json_state(PROCESS_STATE_FILE, {"processed_rars": []})["processed_rars"]
360
+ download_state = load_json_state(DOWNLOAD_STATE_FILE, {"next_download_index": 5})
361
 
362
  # Use start_index if provided, otherwise use the saved state
363
  next_index = start_index if start_index > 0 else download_state["next_download_index"]
 
427
 
428
  # Status update
429
  log_message(f"πŸ“Š Progress: {next_index}/{len(rar_files)} files processed")
430
+ log_message(f'πŸ“Š Extracted: {processing_status["extracted_courses"]} courses')
431
+ log_message(f'πŸ“Š Videos Processed: {processing_status["extracted_videos"]}')
432
+ log_message(f'πŸ“Š Frames Extracted: {processing_status["extracted_frames_count"]}')
 
 
433
 
434
  if next_index < len(rar_files):
435
  log_message(f"πŸ”„ Run the script again to process the next file: {os.path.basename(rar_files[next_index])}")
 
438
  else:
439
  log_message("βœ… All files have been processed!")
440
 
441
+ log_message("πŸŽ‰ Processing complete!")
442
+ log_message(f'πŸ“Š Final stats: {processing_status["extracted_courses"]} courses extracted, {processing_status["extracted_videos"]} videos processed, {processing_status["extracted_frames_count"]} frames extracted, {processing_status["tracked_cursors_count"]} cursors tracked')
443
 
444
  except KeyboardInterrupt:
445
  log_message("⏹️ Processing interrupted by user")
 
449
  processing_status["is_running"] = False
450
  cleanup_temp_files()
451
 
452
+ # Expose necessary functions and variables for download_api.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
  __all__ = [
454
  "main_processing_loop",
455
  "processing_status",
 
456
  "log_message",
 
457
  "extract_frames",
458
  "DEFAULT_FPS",
459
  "ensure_dir"
460
  ]
461
 
462
+
463
+
464
+