morphis / templates /index.html.old
jkh32499's picture
Rename templates/index.html to templates/index.html.old
41a75c2 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LetsPhish Morphis</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" rel="stylesheet">
<style>
:root {
--primary: #4f46e5;
--primary-hover: #4338ca;
--background: #f9fafb;
--card-bg: #ffffff;
--text: #1f2937;
--text-light: #6b7280;
--border: #e5e7eb;
--radius: 8px;
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
body { background-color: var(--background); color: var(--text); padding: 0; margin: 0; min-height: 100vh; }
.navbar { background-color: var(--card-bg); padding: 1rem 2rem; box-shadow: var(--shadow); display: flex; align-items: center; }
.logo { font-size: 1.5rem; font-weight: 700; color: var(--primary); display: flex; align-items: center; gap: 0.5rem; }
.container { max-width: 1200px; margin: 2rem auto; padding: 0 1rem; }
.app-header { text-align: center; margin-bottom: 2rem; }
.app-title { font-size: 2rem; margin-bottom: 0.5rem; color: var(--text); }
.app-description { color: var(--text-light); max-width: 600px; margin: 0 auto; }
.card { background-color: var(--card-bg); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; margin-bottom: 2rem; }
.card-header { padding: 1rem; border-bottom: 1px solid var(--border); font-weight: 600; }
.card-body { padding: 1rem; }
.flex { display: flex; }
.flex-col { flex-direction: column; }
.grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 2rem; }
@media (max-width: 768px) { .grid { grid-template-columns: 1fr; } }
.video-container { position: relative; width: 100%; aspect-ratio: 16/9; overflow: hidden; border-radius: var(--radius); background-color: #000; }
video, #output { position: absolute; width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); }
#video { z-index: 1; }
#output { z-index: 2; }
#canvas { display: none; }
.controls { margin-top: 1rem; display: flex; gap: 1rem; flex-wrap: wrap; }
.btn { padding: 0.5rem 1rem; border-radius: var(--radius); border: none; font-weight: 500; cursor: pointer; display: flex; align-items: center; gap: 0.5rem; transition: all 0.2s; }
.btn-primary { background-color: var(--primary); color: white; }
.btn-primary:hover { background-color: var(--primary-hover); }
.btn-outline { border: 1px solid var(--primary); color: var(--primary); background-color: transparent; }
.btn-outline:hover { background-color: var(--primary); color: white; }
.file-input-container { display: flex; flex-direction: column; gap: 1rem; }
.file-input-label { border: 2px dashed var(--border); border-radius: var(--radius); padding: 2rem; text-align: center; cursor: pointer; transition: all 0.2s; }
.file-input-label:hover { border-color: var(--primary); }
.file-input { display: none; }
.preview-container { width: 128px; height: 128px; border-radius: 50%; overflow: hidden; margin: 1rem auto; border: 4px solid var(--primary); }
.preview-image { width: 100%; height: 100%; object-fit: cover; }
.status-container { display: flex; align-items: center; gap: 0.5rem; color: var(--text-light); margin-top: 1rem; }
.status-dot { width: 10px; height: 10px; border-radius: 50%; background-color: #ccc; }
.status-dot.active { background-color: #10b981; }
.timer { margin-top: 1rem; font-size: 1.2rem; font-weight: bold; text-align: center; }
.footer { padding: 2rem; text-align: center; color: var(--text-light); border-top: 1px solid var(--border); margin-top: 3rem; }
.auth-error { background-color: #fef2f2; color: #dc2626; padding: 1rem; border-radius: var(--radius); margin-bottom: 2rem; text-align: center; }
</style>
</head>
<body>
<nav class="navbar">
<div class="logo">
<i class="fas fa-mask"></i>
<span>LetsPhish Morphis</span>
</div>
</nav>
<div class="container">
<div id="authError" class="auth-error" style="display: none;">
<i class="fas fa-exclamation-triangle"></i>
<span>Authentication failed. Please check your access key.</span>
</div>
<div class="app-header">
<h1 class="app-title">Real-Time Face Swapping</h1>
<p class="app-description">Experience real-time face swapping powered by AI. Upload your own face or use the default one.</p>
</div>
<div class="grid">
<!-- Live preview card spans both columns to be bigger -->
<div class="flex flex-col" style="grid-column: span 2;">
<div class="card">
<div class="card-header">
<i class="fas fa-camera"></i> Live Preview
</div>
<div class="card-body">
<div class="video-container">
<video id="video" autoplay></video>
<img id="output">
</div>
<canvas id="canvas"></canvas>
<div class="controls">
<button id="toggleBtn" class="btn btn-primary">
<i class="fas fa-pause"></i> Pause Face Swap
</button>
<button id="screenshotBtn" class="btn btn-outline">
<i class="fas fa-camera"></i> Take Screenshot
</button>
</div>
<div class="status-container">
<div id="statusDot" class="status-dot active"></div>
<span id="statusText">Face swap active</span>
</div>
<!-- Timer display -->
<div id="timerDisplay" class="timer">Time remaining: 15:00</div>
</div>
</div>
</div>
<!-- Upload card remains in one column -->
<div class="flex flex-col">
<div class="card">
<div class="card-header">
<i class="fas fa-upload"></i> Upload Reference Face
</div>
<div class="card-body">
<div class="preview-container">
<img id="facePreview" class="preview-image" alt="Current face">
</div>
<div class="file-input-container">
<label for="faceUpload" class="file-input-label">
<i class="fas fa-cloud-upload-alt fa-2x"></i>
<p>Drop your image here or click to browse</p>
<p class="text-light">JPG or PNG, max 5MB</p>
</label>
<input type="file" id="faceUpload" class="file-input" accept="image/*">
</div>
<div class="controls">
<button id="uploadBtn" class="btn btn-primary" disabled>
<i class="fas fa-check"></i> Use This Face
</button>
<button id="resetBtn" class="btn btn-outline">
<i class="fas fa-undo"></i> Reset to Default
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="footer">
<p>Powered By LetsPhish Morphis</p>
</footer>
<script>
// Get the key from URL parameters
const urlParams = new URLSearchParams(window.location.search);
const authKey = urlParams.get('key');
// Show error if no key is provided
if (!authKey) {
document.getElementById('authError').style.display = 'block';
document.getElementById('authError').innerHTML = '<i class="fas fa-exclamation-triangle"></i> <span>No access key provided. Please add ?key=your_access_key to the URL.</span>';
}
// Helper function to add key to URL
function addKeyToUrl(url) {
if (!authKey) return url;
const separator = url.includes('?') ? '&' : '?';
return url + separator + 'key=' + encodeURIComponent(authKey);
}
// Helper function to handle authentication errors
function handleAuthError(error) {
console.error('Authentication error:', error);
document.getElementById('authError').style.display = 'block';
document.getElementById('statusDot').classList.remove('active');
document.getElementById('statusDot').style.backgroundColor = 'red';
document.getElementById('statusText').textContent = 'Authentication failed';
// Stop processing
if (intervalId) clearInterval(intervalId);
if (timerIntervalId) clearInterval(timerIntervalId);
isProcessing = false;
toggleBtn.innerHTML = '<i class="fas fa-exclamation-triangle"></i> Auth Error';
toggleBtn.disabled = true;
}
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const output = document.getElementById('output');
const context = canvas.getContext('2d');
const toggleBtn = document.getElementById('toggleBtn');
const screenshotBtn = document.getElementById('screenshotBtn');
const faceUpload = document.getElementById('faceUpload');
const facePreview = document.getElementById('facePreview');
const uploadBtn = document.getElementById('uploadBtn');
const resetBtn = document.getElementById('resetBtn');
const statusDot = document.getElementById('statusDot');
const statusText = document.getElementById('statusText');
const timerDisplay = document.getElementById('timerDisplay');
let isProcessing = true;
let processingInterval = 100; // interval in ms
let intervalId = null;
let selectedFile = null;
let frameCounter = 0;
const skipFrames = 2; // Process every 2nd frame (adjust as needed)
let processingInProgress = false;
// Total time in seconds for live preview
let totalTime = 900;
let timerIntervalId = null;
// Load the current face preview with auth key
if (authKey) {
facePreview.src = addKeyToUrl('/get_current_face');
}
// Function to update timer display in mm:ss format
function updateTimerDisplay() {
let minutes = Math.floor(totalTime / 60);
let seconds = totalTime % 60;
timerDisplay.textContent = `Time remaining: ${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
// Start the countdown timer
function startTimer() {
updateTimerDisplay();
timerIntervalId = setInterval(() => {
totalTime--;
updateTimerDisplay();
if (totalTime <= 0) {
clearInterval(timerIntervalId);
if (isProcessing) {
clearInterval(intervalId);
isProcessing = false;
toggleBtn.innerHTML = '<i class="fas fa-play"></i> Resume Face Swap';
statusDot.classList.remove('active');
statusText.textContent = 'Live preview turned off after 15 minutes';
alert("Live preview has been turned off after 15 minutes.");
}
}
}, 1000);
}
// Toggle face swapping
toggleBtn.addEventListener('click', () => {
if (!authKey) {
handleAuthError('No authentication key');
return;
}
isProcessing = !isProcessing;
if (isProcessing) {
toggleBtn.innerHTML = '<i class="fas fa-pause"></i> Pause Face Swap';
intervalId = setInterval(captureFrame, processingInterval);
statusDot.classList.add('active');
statusText.textContent = 'Face swap active';
// Restart timer if it had stopped
if (totalTime <= 0) {
totalTime = 900;
startTimer();
}
} else {
toggleBtn.innerHTML = '<i class="fas fa-play"></i> Resume Face Swap';
clearInterval(intervalId);
statusDot.classList.remove('active');
statusText.textContent = 'Face swap paused';
}
});
// Take screenshot
screenshotBtn.addEventListener('click', () => {
const link = document.createElement('a');
link.download = 'faceswap-screenshot.jpg';
link.href = output.src;
link.click();
});
// Handle file upload preview
faceUpload.addEventListener('change', (e) => {
selectedFile = e.target.files[0];
if (selectedFile) {
const reader = new FileReader();
reader.onload = (event) => {
facePreview.src = event.target.result;
uploadBtn.disabled = false;
};
reader.readAsDataURL(selectedFile);
}
});
// Upload new face
uploadBtn.addEventListener('click', () => {
if (!selectedFile || !authKey) return;
const formData = new FormData();
formData.append('face', selectedFile);
fetch(addKeyToUrl('/upload_face'), {
method: 'POST',
body: formData
})
.then(response => {
if (response.status === 401) {
throw new Error('Authentication failed');
}
return response.json();
})
.then(data => {
if (data.success) {
alert('Face updated successfully!');
facePreview.src = addKeyToUrl(`/get_current_face?t=${new Date().getTime()}`);
uploadBtn.disabled = true;
} else {
alert('Error: ' + data.message);
}
})
.catch(error => {
if (error.message === 'Authentication failed') {
handleAuthError(error);
} else {
console.error('Error:', error);
alert('An error occurred while uploading the face.');
}
});
});
// Reset to default face
resetBtn.addEventListener('click', () => {
if (!authKey) return;
fetch(addKeyToUrl('/reset_face'), { method: 'POST' })
.then(response => {
if (response.status === 401) {
throw new Error('Authentication failed');
}
return response.json();
})
.then(data => {
if (data.success) {
alert('Reset to default face successfully!');
facePreview.src = addKeyToUrl(`/get_current_face?t=${new Date().getTime()}`);
} else {
alert('Error: ' + data.message);
}
})
.catch(error => {
if (error.message === 'Authentication failed') {
handleAuthError(error);
} else {
console.error('Error:', error);
alert('An error occurred while resetting the face.');
}
});
});
// Access the webcam and stream the video to the video element
navigator.mediaDevices.getUserMedia({ video: true })
.then((stream) => { video.srcObject = stream; })
.catch((err) => {
console.error("Error accessing webcam: " + err);
statusText.textContent = 'Error: Cannot access webcam';
statusDot.classList.remove('active');
statusDot.style.backgroundColor = 'red';
});
// Asynchronous function to capture and process the frame
async function captureFrame() {
if (processingInProgress || !authKey) return;
frameCounter++;
if (frameCounter % skipFrames !== 0) return;
if (!video.videoWidth) return;
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
// Mirror the video: save and restore context
context.save();
context.scale(-1, 1);
context.drawImage(video, -canvas.width, 0, canvas.width, canvas.height);
context.restore();
// Reduce image quality by using a lower quality parameter (0.5 here)
const dataURL = canvas.toDataURL('image/jpeg', 0.5);
processingInProgress = true;
try {
const response = await fetch(addKeyToUrl('/process_frame'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ image: dataURL })
});
if (response.status === 401) {
throw new Error('Authentication failed');
}
const data = await response.json();
output.src = data.image;
} catch (error) {
if (error.message === 'Authentication failed') {
handleAuthError(error);
} else {
console.error('Error:', error);
}
} finally {
processingInProgress = false;
}
}
// Start capturing frames and start the timer only if authenticated
if (authKey) {
intervalId = setInterval(captureFrame, processingInterval);
startTimer();
} else {
// Disable functionality if no auth key
toggleBtn.disabled = true;
uploadBtn.disabled = true;
resetBtn.disabled = true;
}
</script>
</body>
</html>