video-master-adapt/templates/index.html
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

280 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Video Master Detection</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- MSAL Browser Library -->
<script src="https://alcdn.msauth.net/browser/2.35.0/js/msal-browser.min.js"></script>
<!-- Custom CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
<style>
body {
background-color: #f8f9fa;
}
.main-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header-bar {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.auth-card {
background: white;
border-radius: 8px;
padding: 40px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
text-align: center;
}
.content-card {
background: white;
border-radius: 8px;
padding: 30px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.info-banner {
background-color: #e7f3ff;
border-left: 4px solid #2196F3;
padding: 15px;
margin: 20px 0;
border-radius: 4px;
}
.spinner-border {
width: 3rem;
height: 3rem;
}
.user-badge {
background-color: #f8f9fa;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
}
</style>
</head>
<body>
<div class="main-container">
<!-- Auth Loading State -->
<div id="authLoading" style="display: none;">
<div class="auth-card">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3">Checking authentication...</p>
</div>
</div>
<!-- Auth Required State -->
<div id="authRequired" style="display: none;">
<div class="auth-card">
<h2 class="mb-4">🎬 Video Master Detection</h2>
<div class="mb-4">
<svg width="80" height="80" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z" fill="#6c757d"/>
</svg>
</div>
<h5 class="mb-3">Authentication Required</h5>
<p class="text-muted mb-4">Please sign in with your Microsoft account to access the Video Master Detection system</p>
<button class="btn btn-primary btn-lg" onclick="signIn()">
<svg width="20" height="20" viewBox="0 0 21 21" style="vertical-align: middle; margin-right: 8px;">
<path fill="#f25022" d="M0 0h10v10H0z"/>
<path fill="#00a4ef" d="M11 0h10v10H11z"/>
<path fill="#7fba00" d="M0 11h10v10H0z"/>
<path fill="#ffb900" d="M11 11h10v10H11z"/>
</svg>
Sign in with Microsoft
</button>
</div>
</div>
<!-- Main Content - Authenticated State -->
<div id="mainContent" style="display: none;">
<!-- Header -->
<div class="header-bar">
<div>
<h3 class="mb-0">🎬 Video Master Detection</h3>
<small class="text-muted">Azure AD + Box.com Integration</small>
</div>
<div class="d-flex align-items-center">
<span class="user-badge me-3" id="userEmail">Loading...</span>
<button class="btn btn-outline-secondary btn-sm" onclick="signOut()">Logout</button>
</div>
</div>
<!-- Info Banner -->
<div class="info-banner">
<strong>📦 Box Integration Status:</strong>
<span id="boxStatus">Waiting for Box API credentials...</span>
</div>
<!-- Content Area -->
<div class="content-card">
<h4 class="mb-4">Welcome to Video Master Detection</h4>
<div class="row">
<div class="col-md-6">
<h5>🔐 Authentication</h5>
<div class="alert alert-success">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="vertical-align: middle; margin-right: 8px;">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
</svg>
Azure AD authentication active
</div>
<p class="text-muted small">Your session is secured with Microsoft Azure AD using JWT tokens and httpOnly cookies.</p>
</div>
<div class="col-md-6">
<h5>📂 Box.com Integration</h5>
<div class="alert alert-warning">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="vertical-align: middle; margin-right: 8px;">
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
</svg>
Waiting for Box credentials
</div>
<p class="text-muted small">Box API credentials are pending. Once configured, you'll be able to browse folders and process videos directly from Box.</p>
</div>
</div>
<hr class="my-4">
<h5>🎯 What This App Does</h5>
<ul class="text-muted">
<li>Browse video files from your Box.com storage</li>
<li>Select videos to match against master video library</li>
<li>Advanced 4-stage matching:
<ul>
<li><strong>Stage 0:</strong> Metadata filtering (instant 80-95% reduction)</li>
<li><strong>Tier 1:</strong> Perceptual hash matching (spatial-only)</li>
<li><strong>Tier 2:</strong> AKAZE feature matching (geometric verification)</li>
<li><strong>Tier 3:</strong> AI Vision (GPT-4V) for cross-aspect matching</li>
</ul>
</li>
<li>View detailed matching reports with confidence scores</li>
<li>Export results as HTML or JSON</li>
</ul>
<hr class="my-4">
<h5>⚙️ System Status</h5>
<div id="systemStatus">
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<span class="ms-2">Checking system status...</span>
</div>
</div>
<!-- Placeholder for Phase 2: Box Browser -->
<div class="content-card mt-4" id="boxBrowser" style="display: none;">
<h4 class="mb-4">📁 Browse Box Folders</h4>
<p class="text-muted">This section will be enabled once Box API credentials are configured.</p>
</div>
<!-- Placeholder for Phase 3: Matching Interface -->
<div class="content-card mt-4" id="matchingInterface" style="display: none;">
<h4 class="mb-4">🎬 Video Matching</h4>
<p class="text-muted">Select videos and start matching.</p>
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Authentication Script -->
<script src="{{ url_for('static', filename='js/auth.js') }}"></script>
<!-- Main Application Script -->
<script>
// Initialize authentication on page load
document.addEventListener('DOMContentLoaded', async () => {
// Wait for auth to initialize
await initAuth();
// Check system status if authenticated
if (isAuthenticated) {
checkSystemStatus();
}
});
/**
* Check system status (health check)
*/
async function checkSystemStatus() {
try {
const response = await fetch('/health');
const data = await response.json();
const statusDiv = document.getElementById('systemStatus');
const boxStatusSpan = document.getElementById('boxStatus');
if (data.status === 'healthy') {
statusDiv.innerHTML = `
<svg width="20" height="20" viewBox="0 0 24 24" fill="green" style="vertical-align: middle; margin-right: 8px;">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
</svg>
<span class="text-success">System operational</span>
`;
// Update Box status
if (data.box_connected) {
boxStatusSpan.innerHTML = '<span class="text-success">✓ Connected to Box.com</span>';
// Enable Box browser UI
document.getElementById('boxBrowser').style.display = 'block';
} else if (data.box_note) {
boxStatusSpan.innerHTML = `<span class="text-warning">${data.box_note}</span>`;
} else {
boxStatusSpan.innerHTML = '<span class="text-danger">✗ Box connection failed</span>';
}
} else {
statusDiv.innerHTML = `
<span class="text-danger">⚠ System health check failed</span>
`;
}
} catch (error) {
console.error('Failed to check system status:', error);
document.getElementById('systemStatus').innerHTML = `
<span class="text-danger">⚠ Failed to check system status</span>
`;
}
}
/**
* Update user email display
*/
function updateUserDisplay() {
if (currentUser) {
const email = currentUser.email || currentUser.preferred_username || 'User';
document.getElementById('userEmail').textContent = email;
}
}
// Override updateUserInfo from auth.js to also update our custom display
const originalUpdateUserInfo = window.updateUserInfo || function() {};
window.updateUserInfo = function() {
originalUpdateUserInfo();
updateUserDisplay();
};
</script>
</body>
</html>