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>
280 lines
11 KiB
HTML
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>
|