import cv2 import os import numpy as np from pathlib import Path from typing import Dict, List, Any from PIL import Image import tempfile import shutil class VideoAnalyzer: def __init__(self): print("🎬 Initializing video analyzer...") self.supported_formats = ['.mp4', '.avi', '.mov', '.mkv', '.webm', '.m4v'] self.max_duration = 30 # seconds self.frames_per_second = 2 # Extract 2 frames per second print(f" Supported formats: {self.supported_formats}") print(f" Max duration: {self.max_duration} seconds") print(" ✅ Video analyzer ready") def analyze_video(self, video_path: str, output_dir: str = None) -> Dict[str, Any]: """ Analyze video by extracting key frames """ print(f"🎬 Analyzing video: {os.path.basename(video_path)}") if not self._is_supported_format(video_path): return {"error": "Unsupported video format"} try: # Open video file cap = cv2.VideoCapture(video_path) if not cap.isOpened(): return {"error": "Could not open video file"} # Get video properties fps = cap.get(cv2.CAP_PROP_FPS) frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) duration = frame_count / fps if fps > 0 else 0 print(f" Video properties: {duration:.1f}s, {fps:.1f} FPS, {frame_count} frames") # Check duration limit if duration > self.max_duration: cap.release() return {"error": f"Video too long ({duration:.1f}s). Max allowed: {self.max_duration}s"} # Extract frames frames_data = self._extract_frames(cap, fps, duration, output_dir) cap.release() analysis = { "file_path": video_path, "file_size_mb": os.path.getsize(video_path) / (1024 * 1024), "duration_seconds": duration, "fps": fps, "frame_count": frame_count, "extracted_frames": frames_data["frames"], "keyframes_analyzed": len(frames_data["frames"]), "frame_extraction_success": frames_data["success"] } print(f" ✅ Video analysis complete - extracted {len(frames_data['frames'])} frames") return analysis except Exception as e: print(f" ❌ Video analysis failed: {str(e)}") return {"error": f"Failed to analyze video: {str(e)}"} def _is_supported_format(self, video_path: str) -> bool: _, ext = os.path.splitext(video_path.lower()) return ext in self.supported_formats def _extract_frames(self, cap: cv2.VideoCapture, fps: float, duration: float, output_dir: str = None) -> Dict[str, Any]: """ Extract key frames from video at specified intervals """ if output_dir is None: output_dir = tempfile.mkdtemp() else: os.makedirs(output_dir, exist_ok=True) frames = [] frame_interval = max(1, int(fps / self.frames_per_second)) # Extract every N frames frame_num = 0 extracted_count = 0 print(f" Extracting frames every {frame_interval} frames (≈{self.frames_per_second} per second)") while True: ret, frame = cap.read() if not ret: break # Extract frame at intervals + first and last frames if frame_num == 0 or frame_num % frame_interval == 0: try: # Convert frame to RGB frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # Create frame filename timestamp = frame_num / fps if fps > 0 else extracted_count frame_filename = f"frame_{extracted_count:03d}_t{timestamp:.1f}s.jpg" frame_path = os.path.join(output_dir, frame_filename) # Save frame as image pil_image = Image.fromarray(frame_rgb) pil_image.save(frame_path, 'JPEG', quality=85) # Analyze frame quality frame_quality = self._analyze_frame_quality(frame) frame_data = { "frame_number": frame_num, "timestamp": timestamp, "filename": frame_filename, "path": frame_path, "quality_score": frame_quality["sharpness"], "brightness": frame_quality["brightness"] } frames.append(frame_data) extracted_count += 1 if extracted_count % 5 == 0: print(f" Extracted {extracted_count} frames...") except Exception as e: print(f" ⚠️ Failed to save frame {frame_num}: {e}") frame_num += 1 # Safety limit if extracted_count >= 60: # Max 60 frames (30 seconds * 2 fps) print(" Reached frame extraction limit") break return { "frames": frames, "success": len(frames) > 0, "output_directory": output_dir } def _analyze_frame_quality(self, frame: np.ndarray) -> Dict[str, float]: """ Quick quality analysis of a video frame """ # Convert to grayscale gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # Calculate sharpness (Laplacian variance) sharpness = cv2.Laplacian(gray, cv2.CV_64F).var() # Calculate brightness brightness = np.mean(gray) return { "sharpness": float(sharpness), "brightness": float(brightness) } def get_best_frames(self, frames_data: List[Dict], max_frames: int = 10) -> List[Dict]: """ Select the best quality frames for analysis """ if len(frames_data) <= max_frames: return frames_data # Sort by quality score (sharpness) and select top frames sorted_frames = sorted(frames_data, key=lambda x: x.get('quality_score', 0), reverse=True) # Also ensure we get frames from different parts of the video selected = [] selected.append(sorted_frames[0]) # Best quality frame # Add frames distributed across time if len(sorted_frames) > 1: selected.append(sorted_frames[-1] if sorted_frames[-1] != sorted_frames[0] else sorted_frames[1]) # Fill remaining slots with best quality frames for frame in sorted_frames: if len(selected) >= max_frames: break if frame not in selected: selected.append(frame) return selected[:max_frames] def is_video_file(filename: str) -> bool: """Helper function to check if a file is a supported video format""" _, ext = os.path.splitext(filename.lower()) supported_formats = ['.mp4', '.avi', '.mov', '.mkv', '.webm', '.m4v'] return ext in supported_formats