from typing import Dict, List, Any import os from datetime import datetime from .guidelines_parser import GuidelinesParser from .image_analyzer import ImageAnalyzer from .video_analyzer import VideoAnalyzer, is_video_file # Try to import advanced NLP matcher, fallback to simple one try: from .nlp_matcher import NLPMatcher print("āœ… Using advanced NLP matcher with semantic analysis") USE_ADVANCED_NLP = True except ImportError as e: print(f"āš ļø Advanced NLP not available: {e}") print(" Falling back to simple pattern-based matching") from .simple_nlp_matcher import SimpleNLPMatcher as NLPMatcher USE_ADVANCED_NLP = False class ContentEvaluator: def __init__(self, guidelines_path: str): print("šŸš€ Initializing Content Evaluator...") print(" Loading guidelines parser...") self.guidelines_parser = GuidelinesParser(guidelines_path) print(" Loading image analyzer...") self.image_analyzer = ImageAnalyzer() print(" Loading video analyzer...") self.video_analyzer = VideoAnalyzer() print(f" Loading NLP matcher ({'Advanced' if USE_ADVANCED_NLP else 'Simple'})...") self.nlp_matcher = NLPMatcher() print(" āœ… Content evaluator ready!") def evaluate_image(self, image_path: str) -> Dict[str, Any]: print(f"\nšŸ” Evaluating: {os.path.basename(image_path)}") # Analyze image content print("šŸ“ø Step 1: Image analysis...") image_analysis = self.image_analyzer.analyze_image(image_path) if "error" in image_analysis: print(f" āŒ Image analysis failed") return { "approved": False, "reason": f"Image analysis failed: {image_analysis['error']}", "link": os.path.abspath(image_path), "timestamp": datetime.now().isoformat(), "analysis": image_analysis } # Extract text content for NLP analysis text_content = image_analysis.get("extracted_text", {}).get("text", "") print(f"šŸ“ Step 2: Text content - {len(text_content)} characters") if not text_content or len(text_content.strip()) < 3: print(" āš ļø Insufficient text for evaluation") return { "approved": False, "reason": "No readable text found in image for guideline evaluation", "link": os.path.abspath(image_path), "timestamp": datetime.now().isoformat(), "analysis": image_analysis } # Perform NLP analysis against guidelines print("🧠 Step 3: NLP analysis...") approval_rules = self.guidelines_parser.get_approval_rules() rejection_rules = self.guidelines_parser.get_rejection_rules() nlp_results = self.nlp_matcher.analyze_content( text_content, approval_rules, rejection_rules ) print("āš–ļø Step 4: Final decision making...") # Combine results final_approval = self._make_final_decision(image_analysis, nlp_results) result = { "approved": final_approval["approved"], "reason": final_approval["reason"], "link": os.path.abspath(image_path), "timestamp": datetime.now().isoformat(), "confidence": final_approval.get("confidence", 0.0), "text_extracted": text_content, "word_count": len(text_content.split()), "guideline_matches": [ { "category": match.rule_category, "type": match.rule_type, "confidence": match.confidence, "reasoning": match.reasoning } for match in nlp_results["matches"] ], "scores": { "approval_score": nlp_results["approval_score"], "rejection_score": nlp_results["rejection_score"], "net_score": nlp_results["net_score"] }, "image_quality": { "has_text": image_analysis["extracted_text"]["has_text"], "text_confidence": image_analysis["extracted_text"]["confidence"], "is_high_quality": image_analysis["quality_metrics"]["is_high_quality"] } } status = "āœ… APPROVED" if result["approved"] else "āŒ REJECTED" print(f"šŸŽÆ Final Result: {status} (confidence: {result['confidence']:.2f})") return result def _make_final_decision(self, image_analysis: Dict, nlp_results: Dict) -> Dict[str, Any]: print(" Checking text reliability...") # Check if text extraction was reliable text_info = image_analysis.get("extracted_text", {}) text_confidence = text_info.get("confidence", 0) if text_confidence < 30: print(f" āŒ Low OCR confidence: {text_confidence:.1f}%") return { "approved": False, "reason": "Text in image is too unclear for reliable guideline evaluation (low OCR confidence)", "confidence": 0.2 } print(" Checking image quality...") # Check image quality - but don't make it a hard blocker quality_info = image_analysis.get("quality_metrics", {}) quality_penalty = 0.0 if not quality_info.get("is_high_quality", False): print(" āš ļø Lower image quality detected - adding penalty but not blocking") quality_penalty = 0.2 # Small penalty instead of hard rejection print(" Applying NLP decision logic...") # Use NLP results for final decision approved = nlp_results["approved"] net_score = nlp_results["net_score"] rejection_score = nlp_results["rejection_score"] # High rejection score = definite rejection if rejection_score > 0.8: print(f" āŒ High rejection score: {rejection_score:.2f}") return { "approved": False, "reason": nlp_results["reasoning"], "confidence": min(rejection_score, 0.95) } # Strong positive score = approval if approved and net_score > 0.5: print(f" āœ… Strong approval score: {net_score:.2f}") final_confidence = min(net_score - quality_penalty, 0.9) return { "approved": True, "reason": nlp_results["reasoning"], "confidence": max(final_confidence, 0.3) } # Borderline cases if net_score > -0.2 and rejection_score < 0.5: print(f" ⚔ Borderline approval: {net_score:.2f}") final_confidence = max(0.6 - quality_penalty, 0.3) return { "approved": True, "reason": f"Borderline approval - {nlp_results['reasoning']}", "confidence": final_confidence } else: print(f" āŒ Does not meet guidelines: {net_score:.2f}") return { "approved": False, "reason": f"Does not meet brand guidelines - {nlp_results['reasoning']}", "confidence": 0.7 } def evaluate_video(self, video_path: str) -> Dict[str, Any]: """Evaluate video by extracting and analyzing key frames""" print(f"\nšŸŽ¬ Evaluating video: {os.path.basename(video_path)}") # Check if file is a video if not is_video_file(video_path): return { "error": "Not a supported video format", "approved": False, "reason": "File is not a supported video format" } try: # Analyze video and extract frames video_analysis = self.video_analyzer.analyze_video(video_path) if "error" in video_analysis: return { "error": video_analysis["error"], "approved": False, "reason": f"Video analysis failed: {video_analysis['error']}" } frames_data = video_analysis.get("extracted_frames", []) if not frames_data: return { "approved": False, "reason": "No frames could be extracted from video", "confidence": 0.1 } print(f" šŸ–¼ļø Analyzing {len(frames_data)} extracted frames...") # Select best frames for analysis best_frames = self.video_analyzer.get_best_frames(frames_data, max_frames=5) print(f" šŸŽÆ Selected {len(best_frames)} best quality frames") # Analyze each selected frame frame_results = [] combined_text = [] for i, frame_data in enumerate(best_frames, 1): print(f" šŸ” Analyzing frame {i}/{len(best_frames)} (t={frame_data['timestamp']:.1f}s)") frame_path = frame_data["path"] if not os.path.exists(frame_path): print(f" āš ļø Frame file missing: {frame_path}") continue # Analyze frame as an image frame_evaluation = self.evaluate_image(frame_path) if frame_evaluation and not frame_evaluation.get("error"): frame_results.append({ "timestamp": frame_data["timestamp"], "frame_number": frame_data["frame_number"], "evaluation": frame_evaluation, "quality_score": frame_data.get("quality_score", 0) }) # Collect text for combined analysis if frame_evaluation.get("text_extracted"): combined_text.append(frame_evaluation["text_extracted"]) if not frame_results: return { "approved": False, "reason": "No frames could be successfully analyzed", "confidence": 0.2 } # Determine overall video approval based on frames approved_frames = sum(1 for result in frame_results if result["evaluation"].get("approved", False)) total_frames = len(frame_results) approval_rate = approved_frames / total_frames if total_frames > 0 else 0 # Video is approved if majority of key frames are approved overall_approved = approval_rate >= 0.6 # At least 60% of frames approved # Calculate weighted confidence based on frame quality and approval confidence_scores = [ result["evaluation"].get("confidence", 0) * (1 + result.get("quality_score", 0) / 1000) for result in frame_results ] avg_confidence = sum(confidence_scores) / len(confidence_scores) if confidence_scores else 0 # Determine reason if overall_approved: reason = f"Video content approved - {approved_frames}/{total_frames} key frames meet guidelines" else: reason = f"Video content does not meet guidelines - only {approved_frames}/{total_frames} key frames approved" # Add video-specific information result = { "approved": overall_approved, "reason": reason, "confidence": min(avg_confidence, 0.95), "video_analysis": { "duration_seconds": video_analysis.get("duration_seconds", 0), "frames_analyzed": len(frame_results), "frames_approved": approved_frames, "approval_rate": approval_rate, "combined_text": " ".join(combined_text) if combined_text else "", "word_count": len(" ".join(combined_text).split()) if combined_text else 0 }, "frame_results": frame_results, "link": video_path, "media_type": "video" } print(f" šŸŽ¬ Video evaluation complete: {'āœ… APPROVED' if overall_approved else 'āŒ REJECTED'} ({approval_rate:.1%} frame approval)") return result except Exception as e: print(f" āŒ Video evaluation failed: {str(e)}") return { "error": f"Video evaluation failed: {str(e)}", "approved": False, "reason": f"Technical error during video analysis: {str(e)}" } def evaluate_directory(self, directory_path: str) -> List[Dict[str, Any]]: print(f"\nšŸ“ Evaluating directory: {directory_path}") results = [] if not os.path.exists(directory_path): print(" āŒ Directory not found") return [{"error": f"Directory not found: {directory_path}"}] # Get all image files print(" Scanning for image files...") image_files = [] for filename in os.listdir(directory_path): file_path = os.path.join(directory_path, filename) if os.path.isfile(file_path): _, ext = os.path.splitext(filename.lower()) if ext in ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp']: image_files.append(file_path) print(f" Found {len(image_files)} image files") if not image_files: print(" āŒ No supported images found") return [{"error": "No supported image files found in directory"}] # Evaluate each image for i, image_path in enumerate(image_files, 1): print(f"\nšŸ“Š Processing {i}/{len(image_files)}: {os.path.basename(image_path)}") try: result = self.evaluate_image(image_path) results.append(result) except Exception as e: print(f" āŒ Evaluation failed: {str(e)}") results.append({ "approved": False, "reason": f"Evaluation failed: {str(e)}", "link": os.path.abspath(image_path), "timestamp": datetime.now().isoformat(), "error": str(e) }) print(f"\nāœ… Directory evaluation complete - {len(results)} results") return results def generate_summary(self, results: List[Dict[str, Any]]) -> Dict[str, Any]: print("šŸ“Š Generating summary...") if not results: return {"error": "No results to summarize"} total_images = len(results) approved_count = sum(1 for r in results if r.get("approved", False)) rejected_count = total_images - approved_count # Categorize rejection reasons rejection_reasons = {} for result in results: if not result.get("approved", False): reason = result.get("reason", "Unknown") category = self._categorize_rejection_reason(reason) rejection_reasons[category] = rejection_reasons.get(category, 0) + 1 summary = { "total_images": total_images, "approved": approved_count, "rejected": rejected_count, "approval_rate": approved_count / total_images if total_images > 0 else 0, "rejection_categories": rejection_reasons, "timestamp": datetime.now().isoformat() } print(f" šŸ“ˆ Summary: {approved_count}/{total_images} approved ({summary['approval_rate']:.1%})") return summary def _categorize_rejection_reason(self, reason: str) -> str: reason_lower = reason.lower() if "text" in reason_lower and ("unclear" in reason_lower or "confidence" in reason_lower): return "Poor Text Quality" elif "quality" in reason_lower and ("poor" in reason_lower or "low" in reason_lower): return "Poor Image Quality" elif "guideline" in reason_lower or "brand" in reason_lower: return "Brand Guideline Violations" elif "ai-first" in reason_lower or "tech" in reason_lower: return "AI-First Messaging" elif "replace" in reason_lower or "replacement" in reason_lower: return "Artist Replacement Language" else: return "Other Issues"