#!/usr/bin/env python3 """ Batch match adaptations from a folder and generate HTML report. """ import sys import json from pathlib import Path from datetime import datetime from rich.console import Console from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn # Add src to path sys.path.insert(0, str(Path(__file__).parent / "src")) from video_matcher.matcher import VideoMatcher console = Console() # Common video file extensions VIDEO_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv', '.webm', '.flv', '.wmv', '.m4v'} def generate_html_report(results, output_path, folder_path): """Generate an HTML report from matching results.""" html_content = f""" Video Matching Report - {datetime.now().strftime('%Y-%m-%d %H:%M')}

🎬 Video Matching Report

Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
Source Folder: {folder_path}
{len(results)} Adaptations Processed
{sum(1 for r in results if r['matches'])} Matched
{sum(1 for r in results if not r['matches'])} No Matches
{sum(len(r['matches']) for r in results)} Total Master Matches
{sum(1 for r in results for m in r.get('matches', []) if m.get('matching_method') == 'akaze')} AKAZE Matches
{sum(1 for r in results for m in r.get('matches', []) if m.get('matching_method') == 'ai_vision')} AI Vision Matches
""" # Add each adaptation result for result in results: adaptation_name = result['adaptation_name'] matches = result['matches'] error = result.get('error') match_class = 'no-matches' if not matches else '' match_count = len(matches) if matches else 0 html_content += f"""
{adaptation_name}
{match_count} Match{'es' if match_count != 1 else ''}
""" if error: html_content += f"""
Error: {error}
""" elif not matches: html_content += """
No matching masters found above threshold
""" else: html_content += """
""" for idx, match in enumerate(matches, 1): confidence = match['confidence'].lower().replace(' ', '-') html_content += f"""
#{idx} {match['master_id']}
{match['confidence']}
Duration
{match['master_duration']:.0f}s
Video Match
{match['video_percentage']:.1f}%
Frames
{match['matching_frames']}/{match['total_frames']}
Combined Score
{match['combined_score']:.1%}
Method
{match.get('matching_method', 'hash').upper().replace('_', ' ')}
""" html_content += """
""" html_content += """
""" html_content += """
""" # Write HTML file with open(output_path, 'w', encoding='utf-8') as f: f.write(html_content) def batch_match_folder(folder_path, threshold=0.80, frame_threshold=0.80, min_avg_similarity=0.90, output_file=None): """ Match all videos in a folder against masters and generate report. Args: folder_path: Path to folder containing adaptation videos threshold: Minimum percentage match threshold frame_threshold: Frame similarity threshold min_avg_similarity: Minimum average similarity of matched frames output_file: Output HTML file path (default: auto-generated) """ folder_path = Path(folder_path) if not folder_path.exists(): console.print(f"[red]✗[/red] Folder not found: {folder_path}") return if not folder_path.is_dir(): console.print(f"[red]✗[/red] Not a directory: {folder_path}") return # Find all video files video_files = [] for ext in VIDEO_EXTENSIONS: video_files.extend(folder_path.glob(f"*{ext}")) video_files.extend(folder_path.glob(f"*{ext.upper()}")) if not video_files: console.print(f"[yellow]No video files found in {folder_path}[/yellow]") return console.print(f"\n[bold]Found {len(video_files)} video file(s) to process[/bold]\n") # Initialize matcher matcher = VideoMatcher() # Check if we have masters masters = matcher.list_masters() if not masters: console.print("[red]✗[/red] No master videos found in library.") console.print("Use 'python cli.py add-master' to add masters first.") return console.print(f"[cyan]Comparing against {len(masters)} master(s)...[/cyan]\n") # Process each video results = [] with Progress( SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), console=console ) as progress: task = progress.add_task("[cyan]Processing adaptations...", total=len(video_files)) for video_file in video_files: progress.update(task, description=f"[cyan]Processing {video_file.name}...") try: matches = matcher.match_adaptation( str(video_file), threshold=threshold, frame_threshold=frame_threshold, min_avg_similarity=min_avg_similarity ) results.append({ 'adaptation_name': video_file.name, 'adaptation_path': str(video_file), 'matches': matches, 'error': None }) except Exception as e: console.print(f"[red]✗[/red] Error processing {video_file.name}: {e}") results.append({ 'adaptation_name': video_file.name, 'adaptation_path': str(video_file), 'matches': [], 'error': str(e) }) progress.advance(task) # Generate output filename if not specified if output_file is None: timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') output_file = f"matching_report_{timestamp}.html" output_path = Path(output_file) # Generate HTML report console.print(f"\n[cyan]Generating HTML report...[/cyan]") generate_html_report(results, output_path, str(folder_path)) # Summary console.print(f"\n[bold green]✓ Report generated successfully![/bold green]") console.print(f"\n[bold]Summary:[/bold]") console.print(f" Total adaptations: {len(results)}") console.print(f" Matched: {sum(1 for r in results if r['matches'])}") console.print(f" No matches: {sum(1 for r in results if not r['matches'])}") console.print(f" Total master matches: {sum(len(r['matches']) for r in results)}") console.print(f"\n[bold cyan]📄 Report saved to:[/bold cyan] {output_path.absolute()}") console.print(f"\n[dim]Open in browser: file://{output_path.absolute()}[/dim]") if __name__ == '__main__': import click @click.command() @click.argument('folder_path', type=click.Path(exists=True)) @click.option('--threshold', '-t', default=0.80, type=float, help='Minimum percentage match (0-1)') @click.option('--frame-threshold', '-f', default=0.80, type=float, help='Frame similarity threshold (0-1)') @click.option('--min-avg-similarity', '-m', default=0.90, type=float, help='Minimum average similarity of matched frames (0-1)') @click.option('--output', '-o', default=None, help='Output HTML file path') def main(folder_path, threshold, frame_threshold, min_avg_similarity, output): """Batch match all videos in a folder and generate HTML report.""" batch_match_folder(folder_path, threshold, frame_threshold, min_avg_similarity, output) main()