Files
censorAll/templates/index.html
2025-12-11 23:26:21 +01:00

485 lines
17 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 Object Segmentation with SAM2</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: 1200px;
margin: 0 auto;
background: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.subtitle {
font-size: 1.1em;
opacity: 0.9;
}
.content {
padding: 30px;
}
.upload-section {
margin-bottom: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 10px;
border: 2px dashed #dee2e6;
}
.upload-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 15px 30px;
font-size: 1.1em;
border-radius: 8px;
cursor: pointer;
transition: transform 0.3s;
}
.upload-btn:hover {
transform: translateY(-2px);
}
.preview-section {
margin: 30px 0;
text-align: center;
}
.preview-container {
position: relative;
display: inline-block;
}
.preview-image {
max-width: 100%;
max-height: 500px;
border: 3px solid #667eea;
border-radius: 10px;
cursor: crosshair;
}
.segmentation-section {
margin: 30px 0;
padding: 20px;
background: #f8f9fa;
border-radius: 10px;
}
.instructions {
background: #e3f2fd;
padding: 15px;
border-radius: 8px;
margin: 20px 0;
border-left: 4px solid #2196f3;
}
.btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 25px;
font-size: 1em;
border-radius: 8px;
cursor: pointer;
margin: 5px;
transition: transform 0.3s;
}
.btn:hover {
transform: translateY(-2px);
}
.btn-secondary {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.status {
margin: 20px 0;
padding: 15px;
border-radius: 8px;
text-align: center;
font-weight: bold;
}
.status-success {
background: #d4edda;
color: #155724;
}
.status-error {
background: #f8d7da;
color: #721c24;
}
.status-info {
background: #d1ecf1;
color: #0c5460;
}
.results-section {
margin: 30px 0;
}
.video-preview {
max-width: 100%;
border-radius: 10px;
border: 3px solid #667eea;
}
.hidden {
display: none;
}
.point-marker {
position: absolute;
width: 12px;
height: 12px;
background: red;
border-radius: 50%;
border: 2px solid white;
transform: translate(-50%, -50%);
pointer-events: none;
}
.loading {
display: inline-block;
width: 50px;
height: 50px;
border: 5px solid #f3f3f3;
border-top: 5px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>🎥 Video Object Segmentation with SAM2</h1>
<p class="subtitle">Upload a video, click on objects, and let AI segment them out!</p>
</header>
<div class="content">
<div class="upload-section">
<h2>📤 Upload Your Video</h2>
<p>Supported formats: MP4, AVI, MOV, MKV</p>
<input type="file" id="videoUpload" accept=".mp4,.avi,.mov,.mkv" style="display: none;">
<button class="upload-btn" onclick="document.getElementById('videoUpload').click()">
📁 Select Video File
</button>
<p id="fileInfo" style="margin-top: 15px;"></p>
</div>
<div class="preview-section hidden" id="previewSection">
<h2>🖼️ Preview & Select Object</h2>
<div class="instructions">
<strong>✨ Instructions:</strong> Click on the object you want to segment in the preview image below.
You can click multiple points to help the AI better understand what to segment.
</div>
<div class="preview-container">
<img id="previewImage" class="preview-image" alt="Video Preview">
</div>
<div style="margin-top: 20px;">
<button class="btn" onclick="clearPoints()">🗑️ Clear Points</button>
<button class="btn btn-secondary" onclick="segmentObject()">✂️ Segment Object</button>
</div>
</div>
<div class="segmentation-section hidden" id="segmentationSection">
<h2>⚙️ Processing</h2>
<div class="status status-info" id="processingStatus">
<div class="loading"></div>
<p style="margin-top: 15px;">Segmenting your video... This may take a while depending on video length.</p>
</div>
</div>
<div class="results-section hidden" id="resultsSection">
<h2>🎉 Results</h2>
<div class="status status-success">
<p>Segmentation completed successfully!</p>
</div>
<div style="text-align: center; margin: 20px 0;">
<video id="resultVideo" class="video-preview" controls autoplay loop muted playsinline>
Your browser does not support the video tag.
</video>
</div>
<div style="text-align: center; margin: 20px 0;">
<button class="btn" onclick="downloadResult()">💾 Download Result</button>
<button class="btn btn-secondary" onclick="resetApp()">🔄 Start Over</button>
</div>
</div>
</div>
</div>
<script>
// Global variables
let currentFilename = '';
let selectedPoints = [];
let imageWidth, imageHeight;
// DOM elements
const videoUpload = document.getElementById('videoUpload');
const fileInfo = document.getElementById('fileInfo');
const previewSection = document.getElementById('previewSection');
const previewImage = document.getElementById('previewImage');
const segmentationSection = document.getElementById('segmentationSection');
const resultsSection = document.getElementById('resultsSection');
const resultVideo = document.getElementById('resultVideo');
const processingStatus = document.getElementById('processingStatus');
// Event listeners
videoUpload.addEventListener('change', handleFileUpload);
previewImage.addEventListener('click', handleImageClick);
function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
console.log('File selected:', file.name);
console.log('File type:', file.type);
console.log('File size:', file.size, 'bytes');
fileInfo.textContent = `Selected: ${file.name} (${(file.size / 1048576).toFixed(2)} MB)`;
currentFilename = file.name;
// Upload file
const formData = new FormData();
formData.append('file', file);
console.log('Uploading file...');
fetch('/upload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.error) {
showError(data.error);
return;
}
console.log('Upload response:', data);
// Show preview section
previewSection.classList.remove('hidden');
if (data.preview) {
// Display preview image
previewImage.src = 'data:image/jpeg;base64,' + data.preview;
// Store image dimensions for point conversion
previewImage.onload = function() {
imageWidth = previewImage.naturalWidth;
imageHeight = previewImage.naturalHeight;
console.log('Preview image loaded:', imageWidth, 'x', imageHeight);
};
previewImage.onerror = function() {
console.error('Failed to load preview image');
previewImage.src = '';
previewImage.alt = 'Preview failed to load';
};
} else {
console.log('No preview available');
previewImage.src = '';
previewImage.alt = 'No preview available';
// You might want to show a message to the user here
}
})
.catch(error => {
console.error('Upload error:', error);
showError('Upload failed: ' + error.message);
});
}
function handleImageClick(event) {
// Get click coordinates relative to the image
const rect = previewImage.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// Convert to original image coordinates
const scaleX = imageWidth / rect.width;
const scaleY = imageHeight / rect.height;
const originalX = Math.round(x * scaleX);
const originalY = Math.round(y * scaleY);
// Add point
selectedPoints.push([originalX, originalY]);
// Add visual marker
addPointMarker(x, y);
console.log('Selected points:', selectedPoints);
}
function addPointMarker(x, y) {
const marker = document.createElement('div');
marker.className = 'point-marker';
marker.style.left = x + 'px';
marker.style.top = y + 'px';
previewImage.parentNode.appendChild(marker);
}
function clearPoints() {
selectedPoints = [];
const markers = document.querySelectorAll('.point-marker');
markers.forEach(marker => marker.remove());
}
function segmentObject() {
if (selectedPoints.length === 0) {
alert('Please select at least one point on the object you want to segment.');
return;
}
if (!currentFilename) {
showError('No video file selected');
return;
}
// Show processing section
previewSection.classList.add('hidden');
segmentationSection.classList.remove('hidden');
// Send segmentation request
fetch('/segment', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
filename: currentFilename,
points: selectedPoints
})
})
.then(response => {
console.log('Segmentation response status:', response.status);
return response.json();
})
.then(data => {
console.log('Segmentation response data:', data);
if (data.error) {
console.error('Segmentation error:', data.error);
showError(data.error);
return;
}
// Show results section
segmentationSection.classList.add('hidden');
resultsSection.classList.remove('hidden');
// Display result video
const videoUrl = '/segmented/' + data.output_filename;
console.log('Setting video source to:', videoUrl);
resultVideo.src = videoUrl;
// Add error handling for video
resultVideo.onerror = function() {
console.error('Video playback error');
console.error('Video error code:', resultVideo.error ? resultVideo.error.code : 'unknown');
showError('Could not load video. Error code: ' + (resultVideo.error ? resultVideo.error.code : 'unknown'));
};
resultVideo.onloadeddata = function() {
console.log('Video loaded successfully');
console.log('Video duration:', resultVideo.duration, 'seconds');
console.log('Video readyState:', resultVideo.readyState);
};
resultVideo.load();
console.log('Video load() called');
// Try to play the video
setTimeout(() => {
resultVideo.play().catch(e => {
console.error('Autoplay failed:', e);
// This is expected in some browsers without user interaction
});
}, 100);
})
.catch(error => {
showError('Segmentation failed: ' + error.message);
});
}
function downloadResult() {
if (!resultVideo.src) return;
const link = document.createElement('a');
link.href = resultVideo.src;
link.download = resultVideo.src.split('/').pop();
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
function resetApp() {
// Reset all state
currentFilename = '';
selectedPoints = [];
fileInfo.textContent = '';
videoUpload.value = '';
// Hide all sections
previewSection.classList.add('hidden');
segmentationSection.classList.add('hidden');
resultsSection.classList.add('hidden');
// Clear markers and video
const markers = document.querySelectorAll('.point-marker');
markers.forEach(marker => marker.remove());
previewImage.src = '';
resultVideo.src = '';
}
function showError(message) {
processingStatus.className = 'status status-error';
processingStatus.innerHTML = `<p>${message}</p>`;
// Show reset button
setTimeout(() => {
processingStatus.innerHTML += '<button class="btn" onclick="resetApp()" style="margin-top: 15px;">Try Again</button>';
}, 1000);
}
</script>
</body>
</html>