vehicle-tracker / web_app.html
Stanley03's picture
Upload 9 files
de631bc verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🚗 Vehicle Tracking - Web App</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.98);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
h1 {
text-align: center;
color: #333;
margin-bottom: 10px;
font-size: 2.5em;
}
.subtitle {
text-align: center;
color: #666;
margin-bottom: 40px;
font-size: 1.1em;
}
.drop-zone {
border: 3px dashed #667eea;
border-radius: 15px;
padding: 80px 40px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.05) 0%, rgba(118, 75, 162, 0.05) 100%);
margin-bottom: 30px;
}
.drop-zone:hover {
border-color: #764ba2;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
transform: translateY(-2px);
}
.drop-zone.dragover {
border-color: #4CAF50;
background: rgba(76, 175, 80, 0.1);
transform: scale(1.02);
}
.drop-zone.processing {
opacity: 0.5;
pointer-events: none;
}
.drop-zone-icon {
font-size: 5em;
margin-bottom: 20px;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%,
100% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
}
.drop-zone-text {
font-size: 1.5em;
color: #667eea;
font-weight: 600;
margin-bottom: 10px;
}
.drop-zone-subtext {
color: #999;
font-size: 1em;
}
.file-input {
display: none;
}
.file-info {
display: none;
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
border-left: 4px solid #667eea;
}
.file-info.show {
display: block;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.file-name {
font-weight: 600;
color: #333;
margin-bottom: 8px;
font-size: 1.1em;
}
.file-size {
color: #666;
font-size: 0.95em;
}
.progress-container {
display: none;
margin: 30px 0;
}
.progress-container.show {
display: block;
}
.progress-bar {
width: 100%;
height: 40px;
background: #e9ecef;
border-radius: 20px;
overflow: hidden;
margin-bottom: 15px;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
width: 0%;
transition: width 0.5s ease;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
font-size: 1.1em;
}
.status-text {
text-align: center;
color: #666;
font-size: 1.05em;
font-weight: 500;
}
.results {
display: none;
background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
border-left: 4px solid #28a745;
padding: 25px;
border-radius: 10px;
margin-top: 20px;
}
.results.show {
display: block;
animation: slideIn 0.3s ease;
}
.results h3 {
color: #155724;
margin-bottom: 15px;
font-size: 1.3em;
}
.results p {
color: #155724;
margin-bottom: 15px;
line-height: 1.6;
}
.btn {
padding: 15px 30px;
border: none;
border-radius: 10px;
font-size: 1.1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-block;
}
.btn-download {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
}
.btn-download:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(40, 167, 69, 0.4);
}
.btn-new {
background: #f8f9fa;
color: #667eea;
border: 2px solid #667eea;
margin-left: 10px;
}
.btn-new:hover {
background: #667eea;
color: white;
}
.error {
display: none;
background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
border-left: 4px solid #dc3545;
padding: 20px;
border-radius: 10px;
margin-top: 20px;
color: #721c24;
}
.error.show {
display: block;
animation: slideIn 0.3s ease;
}
.spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, .3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-top: 30px;
}
.feature {
text-align: center;
padding: 20px;
background: #f8f9fa;
border-radius: 10px;
}
.feature-icon {
font-size: 2.5em;
margin-bottom: 10px;
}
.feature-text {
color: #666;
font-size: 0.95em;
}
</style>
</head>
<body>
<div class="container">
<h1>🚗 Vehicle Tracking System</h1>
<p class="subtitle">Upload your traffic video and get AI-powered vehicle tracking</p>
<div class="drop-zone" id="dropZone">
<div class="drop-zone-icon">🎥</div>
<div class="drop-zone-text">Drop your video here</div>
<div class="drop-zone-subtext">or click to browse (MP4, AVI, MOV, MKV)</div>
</div>
<input type="file" id="fileInput" class="file-input" accept="video/*">
<div class="file-info" id="fileInfo">
<div class="file-name" id="fileName"></div>
<div class="file-size" id="fileSize"></div>
</div>
<div class="progress-container" id="progressContainer">
<div class="progress-bar">
<div class="progress-fill" id="progressFill">
<span id="progressText">0%</span>
</div>
</div>
<div class="status-text" id="statusText">
<span class="spinner"></span> Processing...
</div>
</div>
<div class="results" id="results">
<h3>✅ Processing Complete!</h3>
<p id="resultsText"></p>
<a href="#" id="downloadBtn" class="btn btn-download">📥 Download Tracked Video</a>
<button class="btn btn-new" onclick="location.reload()">🔄 Process Another Video</button>
</div>
<div class="error" id="error">
<strong>❌ Error:</strong> <span id="errorText"></span>
</div>
<div class="features">
<div class="feature">
<div class="feature-icon">🎯</div>
<div class="feature-text">Detects Cars, Trucks, Buses & Motorcycles</div>
</div>
<div class="feature">
<div class="feature-icon">🔢</div>
<div class="feature-text">Tracks with Unique IDs</div>
</div>
<div class="feature">
<div class="feature-icon">📊</div>
<div class="feature-text">Counts Vehicles Automatically</div>
</div>
<div class="feature">
<div class="feature-icon"></div>
<div class="feature-text">Powered by YOLO11 AI</div>
</div>
</div>
</div>
<script>
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const fileInfo = document.getElementById('fileInfo');
const fileName = document.getElementById('fileName');
const fileSize = document.getElementById('fileSize');
const progressContainer = document.getElementById('progressContainer');
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
const statusText = document.getElementById('statusText');
const results = document.getElementById('results');
const resultsText = document.getElementById('resultsText');
const downloadBtn = document.getElementById('downloadBtn');
const error = document.getElementById('error');
const errorText = document.getElementById('errorText');
let selectedFile = null;
let statusCheckInterval = null;
// Click to browse
dropZone.addEventListener('click', () => {
if (!dropZone.classList.contains('processing')) {
fileInput.click();
}
});
// Drag and drop events
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
if (!dropZone.classList.contains('processing')) {
dropZone.classList.add('dragover');
}
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('dragover');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
if (!dropZone.classList.contains('processing')) {
const files = e.dataTransfer.files;
if (files.length > 0) {
handleFile(files[0]);
}
}
});
// File input change
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleFile(e.target.files[0]);
}
});
function handleFile(file) {
// Check if it's a video file
if (!file.type.startsWith('video/')) {
showError('Please select a video file');
return;
}
selectedFile = file;
// Display file info
fileName.textContent = `📁 ${file.name}`;
const sizeMB = (file.size / (1024 * 1024)).toFixed(2);
fileSize.textContent = `Size: ${sizeMB} MB`;
fileInfo.classList.add('show');
// Hide previous results/errors
results.classList.remove('show');
error.classList.remove('show');
// Auto-upload and process
uploadAndProcess();
}
async function uploadAndProcess() {
if (!selectedFile) return;
// Show progress
dropZone.classList.add('processing');
progressContainer.classList.add('show');
updateProgress(0, 'Uploading video...');
const formData = new FormData();
formData.append('video', selectedFile);
try {
// Upload file
const response = await fetch('/upload', {
method: 'POST',
body: formData
});
const data = await response.json();
if (response.ok) {
// Start checking status
checkStatus();
} else {
showError(data.error || 'Upload failed');
dropZone.classList.remove('processing');
}
} catch (err) {
showError('Network error: ' + err.message);
dropZone.classList.remove('processing');
}
}
function checkStatus() {
statusCheckInterval = setInterval(async () => {
try {
const response = await fetch('/status');
const status = await response.json();
updateProgress(status.progress, status.message);
if (status.status === 'complete') {
clearInterval(statusCheckInterval);
showResults(status.output_file);
} else if (status.status === 'error') {
clearInterval(statusCheckInterval);
showError(status.message);
dropZone.classList.remove('processing');
}
} catch (err) {
console.error('Status check error:', err);
}
}, 1000);
}
function updateProgress(percent, message) {
progressFill.style.width = percent + '%';
progressText.textContent = percent + '%';
statusText.innerHTML = `<span class="spinner"></span> ${message}`;
}
function showResults(outputFile) {
dropZone.classList.remove('processing');
progressContainer.classList.remove('show');
resultsText.textContent = 'Your video has been processed successfully! The tracked video includes bounding boxes, vehicle IDs, and counting statistics.';
downloadBtn.href = `/download/${outputFile}`;
results.classList.add('show');
}
function showError(message) {
errorText.textContent = message;
error.classList.add('show');
progressContainer.classList.remove('show');
setTimeout(() => {
error.classList.remove('show');
}, 8000);
}
</script>
</body>
</html>