video-master-adapt/launcher.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

168 lines
4.9 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Video Matcher Standalone Launcher
Starts local Flask server and opens browser automatically
"""
import os
import sys
import time
import webbrowser
import socket
import threading
from pathlib import Path
# CRITICAL: Set environment variables BEFORE any other imports
# This ensures standalone mode is activated before Flask app initializes
os.environ['STANDALONE_MODE'] = '1'
os.environ['DISABLE_AUTH'] = '1'
# Add project root to path
PROJECT_ROOT = Path(__file__).parent
sys.path.insert(0, str(PROJECT_ROOT))
def check_server_running(host, port):
"""Check if a server is already running on the specified port"""
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(1)
s.connect((host, port))
return True
except (socket.timeout, ConnectionRefusedError, OSError):
return False
def find_free_port(start_port=5000, max_attempts=10):
"""Find an available port starting from start_port"""
for port in range(start_port, start_port + max_attempts):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('127.0.0.1', port))
return port
except OSError:
continue
raise RuntimeError(f"Could not find free port in range {start_port}-{start_port + max_attempts}")
def wait_for_server(host, port, timeout=10):
"""Wait for server to be ready"""
start_time = time.time()
while time.time() - start_time < timeout:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(1)
s.connect((host, port))
return True
except (socket.timeout, ConnectionRefusedError):
time.sleep(0.1)
return False
def open_browser(url, delay=1.5):
"""Open browser after a short delay"""
time.sleep(delay)
try:
webbrowser.open(url)
print(f"✓ Opened browser at {url}")
except Exception as e:
print(f"⚠ Could not open browser automatically: {e}")
print(f" Please open your browser and navigate to: {url}")
def setup_environment():
"""Setup environment variables for standalone mode"""
# Environment variables already set at module level, but ensure they're set
os.environ.setdefault('STANDALONE_MODE', '1')
os.environ.setdefault('DISABLE_AUTH', '1')
# Use local data directory
data_dir = PROJECT_ROOT / 'data'
data_dir.mkdir(exist_ok=True)
# Ensure required directories exist
(data_dir / 'fingerprints').mkdir(exist_ok=True)
(data_dir / 'jobs').mkdir(exist_ok=True)
# Set temp directory for downloads (if using Box)
temp_dir = PROJECT_ROOT / 'tmp' / 'video_downloads'
temp_dir.mkdir(parents=True, exist_ok=True)
os.environ['VIDEO_TEMP_DIR'] = str(temp_dir)
print(f"✓ Data directory: {data_dir}")
print(f"✓ Temp directory: {temp_dir}")
def main():
"""Main launcher function"""
print("=" * 60)
print(" VIDEO MATCHER - Standalone Application")
print("=" * 60)
print()
# Setup environment
print("Setting up environment...")
setup_environment()
print()
# Find available port (skip check for existing server, always start fresh)
host = '127.0.0.1'
try:
port = find_free_port()
url = f"http://{host}:{port}"
print(f"✓ Starting server on port: {port}")
print()
except RuntimeError as e:
print(f"✗ Error: {e}")
input("Press Enter to exit...")
sys.exit(1)
# Import Flask app
try:
from app import app
print("✓ Application loaded successfully")
print()
except Exception as e:
print(f"✗ Error loading application: {e}")
import traceback
traceback.print_exc()
input("Press Enter to exit...")
sys.exit(1)
# Start browser opener in background thread
browser_thread = threading.Thread(target=open_browser, args=(url,), daemon=True)
browser_thread.start()
# Start Flask server
print(f"Starting server at {url}")
print()
print("=" * 60)
print(" APPLICATION RUNNING")
print("=" * 60)
print(f" URL: {url}")
print(f" Press Ctrl+C to stop the server")
print("=" * 60)
print()
try:
# Disable Flask reloader in standalone mode
app.run(
host=host,
port=port,
debug=False,
use_reloader=False,
threaded=True
)
except KeyboardInterrupt:
print("\n\n✓ Server stopped by user")
except Exception as e:
print(f"\n\n✗ Server error: {e}")
import traceback
traceback.print_exc()
input("Press Enter to exit...")
sys.exit(1)
if __name__ == '__main__':
try:
main()
except Exception as e:
print(f"\n\n✗ Fatal error: {e}")
import traceback
traceback.print_exc()
input("Press Enter to exit...")
sys.exit(1)