import os import sys import glob import time import argparse from pathlib import Path import cv2 # opencv-python import ollama # Configuration # 'moondream' is extremely lightweight (1.6B params). # If you want more accuracy at the cost of speed, change this to 'llava' (7B params). MODEL_NAME = 'moondream' # How many seconds to skip between frames. # Higher = Faster processing, but might miss short actions. FRAME_INTERVAL_SECONDS = 5 VIDEO_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv', '.webm', '.flv'} def analyze_frame(frame_bytes): """Sends a single image frame to the local model for description.""" try: response = ollama.chat(model=MODEL_NAME, messages=[ { 'role': 'user', 'content': 'Describe the sexual activity in this image in detail. Be specific about whether there is oral sex or penetration.', 'images': [frame_bytes] } ]) return response['message']['content'] except Exception as e: print(f"Error communicating with Ollama: {e}") return "" def process_video(video_path): """Extracts frames and aggregates analysis.""" cap = cv2.VideoCapture(str(video_path)) if not cap.isOpened(): print(f"Error opening video: {video_path}") return None, False fps = cap.get(cv2.CAP_PROP_FPS) frame_interval = int(fps * FRAME_INTERVAL_SECONDS) frame_count = 0 descriptions = [] print(f"Scanning {video_path.name} (taking 1 frame every {FRAME_INTERVAL_SECONDS}s)...") while cap.isOpened(): ret, frame = cap.read() if not ret: break if frame_count % frame_interval == 0: # Convert frame to bytes for Ollama _, buffer = cv2.imencode('.jpg', frame) frame_bytes = buffer.tobytes() # Analyze frame desc = analyze_frame(frame_bytes) if desc: descriptions.append(desc) # print(f" [Frame {frame_count}] {desc[:50]}...") # Uncomment for debug noise frame_count += 1 cap.release() if not descriptions: return "No frames analyzed.", False # Aggregate Logic # We join all descriptions and check keywords. # This is a naive heuristic because the model doesn't have "memory" of the whole video context, # just individual snapshots. full_text = " ".join(descriptions).lower() has_oral = 'oral' in full_text or 'blowjob' in full_text or 'sucking' in full_text or 'fellatio' in full_text has_penetration = 'penetration' in full_text or 'sex' in full_text or 'intercourse' in full_text or 'vaginal' in full_text or 'anal' in full_text or 'fucking' in full_text # Refined logic: simple keyword matching can be prone to false positives/negatives with small models. # However, for an automated script, this is the baseline. summary = f"Analyzed {len(descriptions)} frames.\n\nCombined Observations:\n{full_text}" # Criteria: Only Blowjobs (Oral), NO Sex (Penetration) # Note: 'sex' is a broad term. Small models might use it generically. # You might need to tune 'has_penetration' keywords based on model behavior. is_match = has_oral and not has_penetration return summary, is_match def update_html_report(report_path, video_name, summary, flag): file_exists = os.path.exists(report_path) with open(report_path, 'a', encoding='utf-8') as f: if not file_exists: f.write("""
| File Name | Summary (Frame Aggregation) | Criteria Met (Only BJ, No Sex) |
|---|---|---|
| {video_name} | \n{display_summary} | \n {flag} | \n