video-master-adapt/batch_match_fast.py
nickviljoen 891c36bbfb Add standalone desktop application with web interface
Major Features:
- 🖥️ Standalone desktop app (VideoMatcher.app) - double-click to run
- 🎨 Black & gold branded UI (Montserrat font, #FFC407 accent)
- 📁 Local file browser for master/adaptation folders
-  Fast mode processing (10-20x faster, disables AKAZE/AI Vision)
- 🤖 Smart AI Vision fallback (auto-retry when no matches found)
- 📊 Real-time progress bars (fingerprinting & matching)
- 💾 Local processing (no cloud, no authentication)
- 📤 CSV export with master filenames

Web Application (Enterprise):
- 🌐 Flask web app with Azure AD authentication
- 📦 Box.com integration for cloud storage
- 🐳 Docker support for deployment
- 🔐 JWT validation with httpOnly cookies
- 🎯 REST API endpoints

Enhancements:
- Fixed master filename lookup (was showing "Unknown")
- Automatic fingerprint recovery (detects missing files)
- Improved CSV format (master file next to adaptation)
- Port conflict handling (auto-finds available port)
- Environment variable fixes for standalone mode

Documentation:
- Updated README with standalone app section
- Added 10+ guide documents (UI improvements, fingerprint recovery, etc.)
- Build instructions with PyInstaller
- Comprehensive troubleshooting guide

Technical:
- PyInstaller build configuration (video_matcher.spec)
- Launcher with environment setup (launcher.py)
- Mock authentication for standalone mode
- Video matcher service layer
- Metadata parser and AKAZE video matching

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-31 09:49:04 +02:00

118 lines
3.9 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Fast batch matching without AKAZE - uses original perceptual hash only
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent / "src"))
from video_matcher.matcher import VideoMatcher
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
from datetime import datetime
console = Console()
if len(sys.argv) < 2:
console.print("[red]Usage: python batch_match_fast.py <folder_path> [output.html][/red]")
sys.exit(1)
folder_path = Path(sys.argv[1])
output_file = sys.argv[2] if len(sys.argv) > 2 else None
if not folder_path.exists():
console.print(f"[red]Folder not found: {folder_path}[/red]")
sys.exit(1)
# Find all video files
VIDEO_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv', '.webm', '.flv', '.wmv', '.m4v'}
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]")
sys.exit(1)
console.print(f"\n[bold]Found {len(video_files)} video file(s) to process[/bold]\n")
# Initialize matcher WITHOUT AKAZE (faster)
console.print("[cyan]Using fast mode (perceptual hash only)[/cyan]")
matcher = VideoMatcher(
use_akaze=False, # Disable AKAZE
use_metadata_filter=True, # Keep metadata filtering
enable_ai_vision=True # Keep AI Vision
)
# 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.")
sys.exit(1)
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))
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_fast_{timestamp}.html"
output_path = Path(output_file)
# Generate HTML report
console.print(f"\n[cyan]Generating HTML report...[/cyan]")
# Import the generation function from batch_match
sys.path.insert(0, str(Path(__file__).parent))
from batch_match import generate_html_report
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]")