Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>FaceFX - AI NSFW Video Generator</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .gradient-bg { | |
| background: linear-gradient(135deg, #6e45e2 0%, #89d4cf 100%); | |
| } | |
| .preview-container { | |
| position: relative; | |
| overflow: hidden; | |
| border-radius: 12px; | |
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); | |
| } | |
| .preview-overlay { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| right: 0; | |
| background: rgba(0, 0, 0, 0.6); | |
| padding: 1rem; | |
| color: white; | |
| transform: translateY(100%); | |
| transition: transform 0.3s ease; | |
| } | |
| .preview-container:hover .preview-overlay { | |
| transform: translateY(0); | |
| } | |
| .file-upload { | |
| border: 2px dashed #9ca3af; | |
| transition: all 0.3s ease; | |
| } | |
| .file-upload:hover { | |
| border-color: #6b7280; | |
| background-color: rgba(255, 255, 255, 0.05); | |
| } | |
| .file-upload.dragover { | |
| border-color: #8b5cf6; | |
| background-color: rgba(139, 92, 246, 0.1); | |
| } | |
| .slider-thumb::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: #8b5cf6; | |
| cursor: pointer; | |
| } | |
| .progress-bar { | |
| height: 6px; | |
| transition: width 0.3s ease; | |
| } | |
| .face-marker { | |
| position: absolute; | |
| border: 2px solid #8b5cf6; | |
| border-radius: 50%; | |
| pointer-events: none; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-gray-100 min-h-screen"> | |
| <div class="gradient-bg fixed top-0 left-0 w-full h-1/2 -z-10"></div> | |
| <div class="container mx-auto px-4 py-12"> | |
| <header class="flex justify-between items-center mb-12"> | |
| <div class="flex items-center"> | |
| <i class="fas fa-fire text-3xl text-purple-500 mr-3"></i> | |
| <h1 class="text-3xl font-bold bg-gradient-to-r from-purple-500 to-pink-500 bg-clip-text text-transparent">FaceFX</h1> | |
| </div> | |
| <nav> | |
| <ul class="flex space-x-6"> | |
| <li><a href="#" class="hover:text-purple-400 transition">Home</a></li> | |
| <li><a href="#" class="hover:text-purple-400 transition">Examples</a></li> | |
| <li><a href="#" class="hover:text-purple-400 transition">Pricing</a></li> | |
| <li><a href="#" class="hover:text-purple-400 transition">API</a></li> | |
| </ul> | |
| </nav> | |
| </header> | |
| <main class="bg-gray-800 rounded-xl shadow-2xl overflow-hidden"> | |
| <div class="p-8"> | |
| <h2 class="text-2xl font-bold mb-6">Create NSFW Videos with Consistent Faces</h2> | |
| <p class="text-gray-400 mb-8">Upload an image and our AI will generate a high-quality NSFW video while maintaining facial consistency throughout the animation.</p> | |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> | |
| <!-- Upload Section --> | |
| <div class="file-upload rounded-lg p-6 flex flex-col items-center justify-center text-center cursor-pointer" id="uploadArea"> | |
| <i class="fas fa-cloud-upload-alt text-4xl text-purple-500 mb-4"></i> | |
| <h3 class="text-xl font-semibold mb-2">Upload Source Image</h3> | |
| <p class="text-gray-400 mb-4">Drag & drop your image here or click to browse</p> | |
| <input type="file" id="fileInput" accept="image/*" class="hidden"> | |
| <button id="uploadBtn" class="bg-purple-600 hover:bg-purple-700 text-white px-6 py-2 rounded-full transition">Select Image</button> | |
| <div class="mt-4 text-sm text-gray-500">Supports JPG, PNG (Max 10MB)</div> | |
| </div> | |
| <!-- Preview Section --> | |
| <div class="preview-container bg-gray-700 aspect-video relative" id="previewContainer"> | |
| <div class="absolute inset-0 flex items-center justify-center" id="previewPlaceholder"> | |
| <div class="text-center"> | |
| <i class="fas fa-image text-4xl text-gray-500 mb-2"></i> | |
| <p class="text-gray-400">Preview will appear here</p> | |
| </div> | |
| </div> | |
| <canvas id="previewCanvas" class="hidden w-full h-full object-cover"></canvas> | |
| <div class="preview-overlay"> | |
| <div class="flex justify-between items-center"> | |
| <span id="fileNameDisplay" class="truncate">No image selected</span> | |
| <button id="clearBtn" class="text-gray-300 hover:text-white"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Face Detection Section --> | |
| <div class="mt-8 bg-gray-750 rounded-lg p-6" id="faceDetectionSection" style="display: none;"> | |
| <h3 class="text-lg font-semibold mb-4 flex items-center"> | |
| <i class="fas fa-user-circle text-purple-500 mr-2"></i> | |
| Face Detection | |
| </h3> | |
| <div class="flex flex-wrap gap-4 mb-4" id="faceMarkersContainer"> | |
| <!-- Face markers will be added here --> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <div class="flex-1"> | |
| <label class="block text-sm text-gray-400 mb-1">Face Consistency Strength</label> | |
| <input type="range" min="0" max="100" value="75" class="w-full slider-thumb" id="consistencySlider"> | |
| <div class="flex justify-between text-xs text-gray-500 mt-1"> | |
| <span>Low</span> | |
| <span>Medium</span> | |
| <span>High</span> | |
| </div> | |
| </div> | |
| <button id="detectFacesBtn" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded transition"> | |
| <i class="fas fa-search mr-2"></i> Detect Faces | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Generation Settings --> | |
| <div class="mt-8 bg-gray-750 rounded-lg p-6"> | |
| <h3 class="text-lg font-semibold mb-4 flex items-center"> | |
| <i class="fas fa-cog text-purple-500 mr-2"></i> | |
| Video Settings | |
| </h3> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> | |
| <div> | |
| <label class="block text-sm text-gray-400 mb-1">Video Duration</label> | |
| <select class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 text-white"> | |
| <option>5 seconds</option> | |
| <option selected>10 seconds</option> | |
| <option>15 seconds</option> | |
| <option>30 seconds</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-sm text-gray-400 mb-1">Resolution</label> | |
| <select class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 text-white"> | |
| <option>512x512</option> | |
| <option selected>768x768</option> | |
| <option>1024x1024</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-sm text-gray-400 mb-1">FPS</label> | |
| <select class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 text-white"> | |
| <option>24</option> | |
| <option selected>30</option> | |
| <option>60</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="mt-6"> | |
| <label class="block text-sm text-gray-400 mb-1">Animation Style</label> | |
| <div class="grid grid-cols-2 md:grid-cols-4 gap-3"> | |
| <button class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded transition border border-transparent hover:border-purple-500"> | |
| Subtle Motion | |
| </button> | |
| <button class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded transition border border-transparent hover:border-purple-500"> | |
| Fluid Movement | |
| </button> | |
| <button class="bg-purple-600 px-4 py-2 rounded transition border border-purple-500"> | |
| Intimate Focus | |
| </button> | |
| <button class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded transition border border-transparent hover:border-purple-500"> | |
| Extreme Action | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Generation Controls --> | |
| <div class="mt-8 flex flex-col md:flex-row justify-between items-center gap-4"> | |
| <div class="flex items-center space-x-4"> | |
| <div class="flex items-center space-x-2"> | |
| <input type="checkbox" id="nsfwToggle" checked class="w-4 h-4 text-purple-600 bg-gray-700 border-gray-600 rounded focus:ring-purple-500"> | |
| <label for="nsfwToggle" class="text-sm">NSFW Content</label> | |
| </div> | |
| <div class="flex items-center space-x-2"> | |
| <input type="checkbox" id="watermarkToggle" class="w-4 h-4 text-purple-600 bg-gray-700 border-gray-600 rounded focus:ring-purple-500"> | |
| <label for="watermarkToggle" class="text-sm">Add Watermark</label> | |
| </div> | |
| </div> | |
| <div class="flex space-x-3"> | |
| <button id="previewBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-6 py-3 rounded-full transition flex items-center" disabled> | |
| <i class="fas fa-eye mr-2"></i> Preview | |
| </button> | |
| <button id="generateBtn" class="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white px-8 py-3 rounded-full transition flex items-center font-semibold" disabled> | |
| <i class="fas fa-magic mr-2"></i> Generate Video | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Generation Progress --> | |
| <div id="progressSection" class="mt-8 hidden"> | |
| <div class="flex justify-between mb-2"> | |
| <span class="font-medium">Generating your video...</span> | |
| <span id="progressPercent">0%</span> | |
| </div> | |
| <div class="w-full bg-gray-700 rounded-full h-2.5"> | |
| <div id="progressBar" class="progress-bar bg-purple-600 rounded-full h-2.5" style="width: 0%"></div> | |
| </div> | |
| <div class="mt-2 text-sm text-gray-400" id="statusMessage"> | |
| Initializing generation process... | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <footer class="mt-12 text-center text-gray-500 text-sm"> | |
| <p>© 2023 FaceFX AI. All generated content is for personal use only.</p> | |
| <p class="mt-2">By using this service, you agree to our <a href="#" class="text-purple-400 hover:underline">Terms</a> and <a href="#" class="text-purple-400 hover:underline">Privacy Policy</a>.</p> | |
| </footer> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // DOM Elements | |
| const uploadArea = document.getElementById('uploadArea'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const uploadBtn = document.getElementById('uploadBtn'); | |
| const previewContainer = document.getElementById('previewContainer'); | |
| const previewPlaceholder = document.getElementById('previewPlaceholder'); | |
| const previewCanvas = document.getElementById('previewCanvas'); | |
| const fileNameDisplay = document.getElementById('fileNameDisplay'); | |
| const clearBtn = document.getElementById('clearBtn'); | |
| const faceDetectionSection = document.getElementById('faceDetectionSection'); | |
| const detectFacesBtn = document.getElementById('detectFacesBtn'); | |
| const faceMarkersContainer = document.getElementById('faceMarkersContainer'); | |
| const previewBtn = document.getElementById('previewBtn'); | |
| const generateBtn = document.getElementById('generateBtn'); | |
| const progressSection = document.getElementById('progressSection'); | |
| const progressBar = document.getElementById('progressBar'); | |
| const progressPercent = document.getElementById('progressPercent'); | |
| const statusMessage = document.getElementById('statusMessage'); | |
| // Variables | |
| let uploadedImage = null; | |
| let detectedFaces = []; | |
| // Event Listeners | |
| uploadBtn.addEventListener('click', () => fileInput.click()); | |
| uploadArea.addEventListener('click', () => fileInput.click()); | |
| uploadArea.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| uploadArea.classList.add('dragover'); | |
| }); | |
| uploadArea.addEventListener('dragleave', () => { | |
| uploadArea.classList.remove('dragover'); | |
| }); | |
| uploadArea.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| uploadArea.classList.remove('dragover'); | |
| if (e.dataTransfer.files.length) { | |
| fileInput.files = e.dataTransfer.files; | |
| handleFileUpload(e.dataTransfer.files[0]); | |
| } | |
| }); | |
| fileInput.addEventListener('change', () => { | |
| if (fileInput.files.length) { | |
| handleFileUpload(fileInput.files[0]); | |
| } | |
| }); | |
| clearBtn.addEventListener('click', () => { | |
| resetUpload(); | |
| }); | |
| detectFacesBtn.addEventListener('click', detectFaces); | |
| previewBtn.addEventListener('click', previewGeneration); | |
| generateBtn.addEventListener('click', generateVideo); | |
| // Functions | |
| function handleFileUpload(file) { | |
| if (!file.type.match('image.*')) { | |
| alert('Please upload an image file (JPEG, PNG)'); | |
| return; | |
| } | |
| if (file.size > 10 * 1024 * 1024) { | |
| alert('File size exceeds 10MB limit'); | |
| return; | |
| } | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| uploadedImage = new Image(); | |
| uploadedImage.onload = function() { | |
| displayImage(); | |
| fileNameDisplay.textContent = file.name; | |
| previewBtn.disabled = false; | |
| generateBtn.disabled = false; | |
| faceDetectionSection.style.display = 'block'; | |
| }; | |
| uploadedImage.src = e.target.result; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| function displayImage() { | |
| previewPlaceholder.classList.add('hidden'); | |
| previewCanvas.classList.remove('hidden'); | |
| const canvas = previewCanvas; | |
| const ctx = canvas.getContext('2d'); | |
| // Set canvas dimensions to match container while maintaining aspect ratio | |
| const containerWidth = previewContainer.clientWidth; | |
| const containerHeight = previewContainer.clientHeight; | |
| canvas.width = containerWidth; | |
| canvas.height = containerHeight; | |
| // Calculate dimensions to maintain aspect ratio | |
| const imgRatio = uploadedImage.width / uploadedImage.height; | |
| const containerRatio = containerWidth / containerHeight; | |
| let drawWidth, drawHeight, offsetX = 0, offsetY = 0; | |
| if (imgRatio > containerRatio) { | |
| // Image is wider than container | |
| drawHeight = containerHeight; | |
| drawWidth = drawHeight * imgRatio; | |
| offsetX = (containerWidth - drawWidth) / 2; | |
| } else { | |
| // Image is taller than container | |
| drawWidth = containerWidth; | |
| drawHeight = drawWidth / imgRatio; | |
| offsetY = (containerHeight - drawHeight) / 2; | |
| } | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| ctx.drawImage(uploadedImage, offsetX, offsetY, drawWidth, drawHeight); | |
| } | |
| function resetUpload() { | |
| uploadedImage = null; | |
| detectedFaces = []; | |
| previewPlaceholder.classList.remove('hidden'); | |
| previewCanvas.classList.add('hidden'); | |
| fileNameDisplay.textContent = 'No image selected'; | |
| previewBtn.disabled = true; | |
| generateBtn.disabled = true; | |
| faceDetectionSection.style.display = 'none'; | |
| faceMarkersContainer.innerHTML = ''; | |
| fileInput.value = ''; | |
| } | |
| function detectFaces() { | |
| // Simulate face detection | |
| statusMessage.textContent = "Detecting faces in the image..."; | |
| progressSection.classList.remove('hidden'); | |
| progressBar.style.width = '30%'; | |
| progressPercent.textContent = '30%'; | |
| // Simulate API call delay | |
| setTimeout(() => { | |
| // For demo purposes, we'll simulate detecting 1 face in the center | |
| const canvas = previewCanvas; | |
| const rect = canvas.getBoundingClientRect(); | |
| detectedFaces = [{ | |
| x: rect.width / 2, | |
| y: rect.height / 2, | |
| width: 100, | |
| height: 100, | |
| id: 'face-1' | |
| }]; | |
| // Create face markers | |
| createFaceMarkers(); | |
| progressBar.style.width = '100%'; | |
| progressPercent.textContent = '100%'; | |
| statusMessage.textContent = "Face detection complete! Found 1 face."; | |
| setTimeout(() => { | |
| progressSection.classList.add('hidden'); | |
| progressBar.style.width = '0%'; | |
| progressPercent.textContent = '0%'; | |
| }, 2000); | |
| }, 1500); | |
| } | |
| function createFaceMarkers() { | |
| faceMarkersContainer.innerHTML = ''; | |
| detectedFaces.forEach((face, index) => { | |
| // Add marker to preview | |
| const marker = document.createElement('div'); | |
| marker.className = 'face-marker'; | |
| marker.style.left = `${face.x - face.width/2}px`; | |
| marker.style.top = `${face.y - face.height/2}px`; | |
| marker.style.width = `${face.width}px`; | |
| marker.style.height = `${face.height}px`; | |
| marker.id = `marker-${face.id}`; | |
| previewContainer.appendChild(marker); | |
| // Add control to face markers container | |
| const faceControl = document.createElement('div'); | |
| faceControl.className = 'bg-gray-700 rounded-lg p-3 flex items-center'; | |
| faceControl.innerHTML = ` | |
| <div class="w-10 h-10 rounded-full bg-purple-900 flex items-center justify-center mr-3"> | |
| <i class="fas fa-user text-purple-400"></i> | |
| </div> | |
| <div> | |
| <div class="font-medium">Face ${index + 1}</div> | |
| <div class="text-xs text-gray-400">Consistency: High</div> | |
| </div> | |
| <div class="ml-auto flex space-x-2"> | |
| <button class="text-gray-400 hover:text-purple-400 transition" onclick="adjustFaceMarker('${face.id}', 'smaller')"> | |
| <i class="fas fa-search-minus"></i> | |
| </button> | |
| <button class="text-gray-400 hover:text-purple-400 transition" onclick="adjustFaceMarker('${face.id}', 'larger')"> | |
| <i class="fas fa-search-plus"></i> | |
| </button> | |
| <button class="text-gray-400 hover:text-red-400 transition" onclick="removeFaceMarker('${face.id}')"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| `; | |
| faceMarkersContainer.appendChild(faceControl); | |
| }); | |
| } | |
| function adjustFaceMarker(faceId, action) { | |
| const marker = document.getElementById(`marker-${faceId}`); | |
| if (!marker) return; | |
| const currentWidth = parseInt(marker.style.width); | |
| const currentHeight = parseInt(marker.style.height); | |
| if (action === 'larger') { | |
| marker.style.width = `${currentWidth * 1.1}px`; | |
| marker.style.height = `${currentHeight * 1.1}px`; | |
| marker.style.left = `${parseInt(marker.style.left) - currentWidth * 0.05}px`; | |
| marker.style.top = `${parseInt(marker.style.top) - currentHeight * 0.05}px`; | |
| } else { | |
| marker.style.width = `${currentWidth * 0.9}px`; | |
| marker.style.height = `${currentHeight * 0.9}px`; | |
| marker.style.left = `${parseInt(marker.style.left) + currentWidth * 0.05}px`; | |
| marker.style.top = `${parseInt(marker.style.top) + currentHeight * 0.05}px`; | |
| } | |
| } | |
| function removeFaceMarker(faceId) { | |
| const marker = document.getElementById(`marker-${faceId}`); | |
| if (marker) marker.remove(); | |
| detectedFaces = detectedFaces.filter(face => face.id !== faceId); | |
| // Re-render face controls | |
| createFaceMarkers(); | |
| if (detectedFaces.length === 0) { | |
| faceMarkersContainer.innerHTML = '<div class="text-gray-400">No faces detected. Try adjusting the detection.</div>'; | |
| } | |
| } | |
| function previewGeneration() { | |
| if (!uploadedImage || detectedFaces.length === 0) return; | |
| statusMessage.textContent = "Generating preview animation..."; | |
| progressSection.classList.remove('hidden'); | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += 5; | |
| progressBar.style.width = `${progress}%`; | |
| progressPercent.textContent = `${progress}%`; | |
| if (progress >= 100) { | |
| clearInterval(interval); | |
| statusMessage.textContent = "Preview generated! Click Generate Video to create the full version."; | |
| // Simulate showing a preview (in a real app, this would show an actual preview) | |
| setTimeout(() => { | |
| progressSection.classList.add('hidden'); | |
| progressBar.style.width = '0%'; | |
| progressPercent.textContent = '0%'; | |
| alert("Preview generation complete! This is a simulation. In a real app, you would see a short preview of the animation."); | |
| }, 1000); | |
| } | |
| }, 100); | |
| } | |
| function generateVideo() { | |
| if (!uploadedImage || detectedFaces.length === 0) return; | |
| statusMessage.textContent = "Starting video generation with face consistency..."; | |
| progressSection.classList.remove('hidden'); | |
| let progress = 0; | |
| const messages = [ | |
| "Preparing source image...", | |
| "Applying face consistency models...", | |
| "Generating animation frames...", | |
| "Enhancing details...", | |
| "Finalizing video output..." | |
| ]; | |
| const interval = setInterval(() => { | |
| progress += 2; | |
| if (progress <= 100) { | |
| progressBar.style.width = `${progress}%`; | |
| progressPercent.textContent = `${progress}%`; | |
| // Update status message based on progress | |
| const messageIndex = Math.min(Math.floor(progress / 20), messages.length - 1); | |
| statusMessage.textContent = messages[messageIndex]; | |
| } | |
| if (progress >= 100) { | |
| clearInterval(interval); | |
| statusMessage.textContent = "Video generation complete!"; | |
| // Simulate completion | |
| setTimeout(() => { | |
| alert("Video generation complete! This is a simulation. In a real app, you would be able to download the generated video."); | |
| progressSection.classList.add('hidden'); | |
| progressBar.style.width = '0%'; | |
| progressPercent.textContent = '0%'; | |
| }, 1500); | |
| } | |
| }, 100); | |
| } | |
| // Make functions available globally for button clicks in face markers | |
| window.adjustFaceMarker = adjustFaceMarker; | |
| window.removeFaceMarker = removeFaceMarker; | |
| // Handle window resize | |
| window.addEventListener('resize', () => { | |
| if (uploadedImage) { | |
| displayImage(); | |
| } | |
| }); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=ES14195595/spank" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |