Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>AI Video Creator | Transform Images to Video Scenes</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> | |
@keyframes pulse { | |
0%, 100% { opacity: 1; } | |
50% { opacity: 0.5; } | |
} | |
.animate-pulse { | |
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
} | |
.scene-card:hover .scene-actions { | |
opacity: 1; | |
} | |
.scene-actions { | |
opacity: 0; | |
transition: opacity 0.3s ease; | |
} | |
.dropzone { | |
border: 2px dashed #cbd5e0; | |
transition: all 0.3s ease; | |
} | |
.dropzone.active { | |
border-color: #667eea; | |
background-color: #ebf4ff; | |
} | |
.progress-bar { | |
transition: width 0.5s ease; | |
} | |
.video-preview { | |
aspect-ratio: 16/9; | |
} | |
/* Custom scrollbar */ | |
::-webkit-scrollbar { | |
width: 8px; | |
height: 8px; | |
} | |
::-webkit-scrollbar-track { | |
background: #f1f1f1; | |
} | |
::-webkit-scrollbar-thumb { | |
background: #c1c1c1; | |
border-radius: 4px; | |
} | |
::-webkit-scrollbar-thumb:hover { | |
background: #a8a8a8; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 text-gray-800"> | |
<div class="min-h-screen flex flex-col"> | |
<!-- Header --> | |
<header class="bg-indigo-600 text-white shadow-lg"> | |
<div class="container mx-auto px-4 py-6"> | |
<div class="flex justify-between items-center"> | |
<div class="flex items-center space-x-2"> | |
<i class="fas fa-film text-2xl"></i> | |
<h1 class="text-2xl font-bold">AI Video Creator</h1> | |
</div> | |
<nav> | |
<ul class="flex space-x-6"> | |
<li><a href="#" class="hover:text-indigo-200 transition">Home</a></li> | |
<li><a href="#" class="hover:text-indigo-200 transition">Templates</a></li> | |
<li><a href="#" class="hover:text-indigo-200 transition">Pricing</a></li> | |
<li><a href="#" class="hover:text-indigo-200 transition">Help</a></li> | |
</ul> | |
</nav> | |
<div> | |
<button class="bg-white text-indigo-600 px-4 py-2 rounded-lg font-medium hover:bg-indigo-50 transition"> | |
Sign In | |
</button> | |
</div> | |
</div> | |
</div> | |
</header> | |
<!-- Main Content --> | |
<main class="flex-grow container mx-auto px-4 py-8"> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
<!-- Left Panel - Scene Management --> | |
<div class="lg:col-span-1 bg-white rounded-xl shadow-md p-6 h-fit"> | |
<div class="flex justify-between items-center mb-6"> | |
<h2 class="text-xl font-semibold">Video Scenes</h2> | |
<button id="add-scene-btn" class="bg-indigo-600 text-white p-2 rounded-full hover:bg-indigo-700 transition"> | |
<i class="fas fa-plus"></i> | |
</button> | |
</div> | |
<div id="scenes-container" class="space-y-4 max-h-[500px] overflow-y-auto pr-2"> | |
<!-- Scene cards will be added here dynamically --> | |
<div class="text-center py-8 text-gray-500" id="empty-state"> | |
<i class="fas fa-images text-4xl mb-3"></i> | |
<p>No scenes added yet</p> | |
<p class="text-sm">Click the + button to add your first scene</p> | |
</div> | |
</div> | |
<div class="mt-6 pt-6 border-t border-gray-200"> | |
<h3 class="font-medium mb-3">Scene Settings</h3> | |
<div class="space-y-4"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Transition Effect</label> | |
<select class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
<option>Fade</option> | |
<option>Slide Left</option> | |
<option>Slide Right</option> | |
<option>Zoom</option> | |
<option>None</option> | |
</select> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Scene Duration (seconds)</label> | |
<input type="number" min="1" max="10" value="3" class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Center Panel - Scene Editor --> | |
<div class="lg:col-span-2 space-y-6"> | |
<!-- Current Scene Editor --> | |
<div class="bg-white rounded-xl shadow-md p-6"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-semibold">Scene Editor</h2> | |
<div class="text-sm text-gray-500" id="current-scene-indicator">No scene selected</div> | |
</div> | |
<div id="scene-editor" class="text-center py-12 bg-gray-100 rounded-lg"> | |
<div id="empty-editor" class="space-y-3"> | |
<i class="fas fa-image text-4xl text-gray-400"></i> | |
<p class="text-gray-500">Select or add a scene to edit</p> | |
</div> | |
<div id="active-editor" class="hidden"> | |
<div class="mb-4"> | |
<div class="relative mx-auto max-w-2xl"> | |
<div id="scene-preview" class="bg-gray-200 rounded-lg overflow-hidden video-preview flex items-center justify-center"> | |
<img id="scene-image" src="" alt="Scene preview" class="hidden max-h-full max-w-full"> | |
<div id="no-image" class="text-gray-500 p-4"> | |
<i class="fas fa-image text-4xl mb-3"></i> | |
<p>No image selected</p> | |
</div> | |
</div> | |
<div id="processing-overlay" class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden"> | |
<div class="text-white text-center"> | |
<i class="fas fa-cog fa-spin text-3xl mb-2"></i> | |
<p>AI Processing...</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">AI Enhancements</label> | |
<select id="ai-enhancement" class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
<option value="none">None</option> | |
<option value="upscale">Upscale Resolution</option> | |
<option value="color">Color Correction</option> | |
<option value="animate">Animate Elements</option> | |
<option value="style">Artistic Style</option> | |
<option value="background">Background Replacement</option> | |
</select> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">AI Style</label> | |
<select id="ai-style" class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
<option value="realistic">Realistic</option> | |
<option value="cartoon">Cartoon</option> | |
<option value="watercolor">Watercolor</option> | |
<option value="cyberpunk">Cyberpunk</option> | |
<option value="fantasy">Fantasy</option> | |
</select> | |
</div> | |
</div> | |
<div class="mt-4"> | |
<div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer"> | |
<input type="file" id="scene-upload" accept="image/*" class="hidden"> | |
<div class="space-y-2"> | |
<i class="fas fa-cloud-upload-alt text-3xl text-indigo-500"></i> | |
<p class="font-medium">Drag & drop your image here</p> | |
<p class="text-sm text-gray-500">or click to browse files</p> | |
<p class="text-xs text-gray-400">Supports JPG, PNG (Max 10MB)</p> | |
</div> | |
</div> | |
</div> | |
<div class="mt-6"> | |
<button id="process-scene-btn" class="w-full bg-indigo-600 text-white py-3 px-4 rounded-lg font-medium hover:bg-indigo-700 transition flex items-center justify-center space-x-2"> | |
<i class="fas fa-magic"></i> | |
<span>Apply AI Enhancements</span> | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Video Preview --> | |
<div class="bg-white rounded-xl shadow-md p-6"> | |
<h2 class="text-xl font-semibold mb-4">Video Preview</h2> | |
<div class="bg-gray-900 rounded-lg overflow-hidden video-preview flex items-center justify-center"> | |
<div id="video-empty" class="text-gray-400 p-6 text-center"> | |
<i class="fas fa-video text-4xl mb-3"></i> | |
<p>Your video will appear here</p> | |
<p class="text-sm mt-2">Add and process scenes to generate preview</p> | |
</div> | |
<video id="video-preview" class="hidden w-full h-full object-contain" controls></video> | |
</div> | |
<div class="mt-6"> | |
<div id="progress-container" class="hidden"> | |
<div class="flex justify-between text-sm text-gray-600 mb-1"> | |
<span>Generating video...</span> | |
<span id="progress-percent">0%</span> | |
</div> | |
<div class="w-full bg-gray-200 rounded-full h-2.5"> | |
<div id="progress-bar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div> | |
</div> | |
</div> | |
<button id="generate-video-btn" class="w-full bg-green-600 text-white py-3 px-4 rounded-lg font-medium hover:bg-green-700 transition flex items-center justify-center space-x-2 mt-4"> | |
<i class="fas fa-play-circle"></i> | |
<span>Generate Final Video</span> | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</main> | |
<!-- Footer --> | |
<footer class="bg-gray-800 text-white py-8"> | |
<div class="container mx-auto px-4"> | |
<div class="grid grid-cols-1 md:grid-cols-4 gap-8"> | |
<div> | |
<h3 class="text-lg font-semibold mb-4">AI Video Creator</h3> | |
<p class="text-gray-400">Transform your images into stunning videos with AI-powered enhancements and animations.</p> | |
</div> | |
<div> | |
<h4 class="font-medium mb-4">Features</h4> | |
<ul class="space-y-2 text-gray-400"> | |
<li><a href="#" class="hover:text-white transition">AI Enhancements</a></li> | |
<li><a href="#" class="hover:text-white transition">Scene Transitions</a></li> | |
<li><a href="#" class="hover:text-white transition">Video Templates</a></li> | |
<li><a href="#" class="hover:text-white transition">Cloud Processing</a></li> | |
</ul> | |
</div> | |
<div> | |
<h4 class="font-medium mb-4">Resources</h4> | |
<ul class="space-y-2 text-gray-400"> | |
<li><a href="#" class="hover:text-white transition">Documentation</a></li> | |
<li><a href="#" class="hover:text-white transition">Tutorials</a></li> | |
<li><a href="#" class="hover:text-white transition">API Reference</a></li> | |
<li><a href="#" class="hover:text-white transition">Community</a></li> | |
</ul> | |
</div> | |
<div> | |
<h4 class="font-medium mb-4">Company</h4> | |
<ul class="space-y-2 text-gray-400"> | |
<li><a href="#" class="hover:text-white transition">About Us</a></li> | |
<li><a href="#" class="hover:text-white transition">Careers</a></li> | |
<li><a href="#" class="hover:text-white transition">Privacy Policy</a></li> | |
<li><a href="#" class="hover:text-white transition">Terms of Service</a></li> | |
</ul> | |
</div> | |
</div> | |
<div class="border-t border-gray-700 mt-8 pt-8 text-center text-gray-400"> | |
<p>© 2023 AI Video Creator. All rights reserved.</p> | |
</div> | |
</div> | |
</footer> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// DOM Elements | |
const addSceneBtn = document.getElementById('add-scene-btn'); | |
const scenesContainer = document.getElementById('scenes-container'); | |
const emptyState = document.getElementById('empty-state'); | |
const sceneEditor = document.getElementById('scene-editor'); | |
const emptyEditor = document.getElementById('empty-editor'); | |
const activeEditor = document.getElementById('active-editor'); | |
const currentSceneIndicator = document.getElementById('current-scene-indicator'); | |
const dropzone = document.getElementById('dropzone'); | |
const sceneUpload = document.getElementById('scene-upload'); | |
const sceneImage = document.getElementById('scene-image'); | |
const noImage = document.getElementById('no-image'); | |
const processSceneBtn = document.getElementById('process-scene-btn'); | |
const processingOverlay = document.getElementById('processing-overlay'); | |
const generateVideoBtn = document.getElementById('generate-video-btn'); | |
const videoPreview = document.getElementById('video-preview'); | |
const videoEmpty = document.getElementById('video-empty'); | |
const progressContainer = document.getElementById('progress-container'); | |
const progressBar = document.getElementById('progress-bar'); | |
const progressPercent = document.getElementById('progress-percent'); | |
// State | |
let scenes = []; | |
let currentSceneId = null; | |
// Add new scene | |
addSceneBtn.addEventListener('click', function() { | |
const sceneId = Date.now().toString(); | |
const scene = { | |
id: sceneId, | |
title: `Scene ${scenes.length + 1}`, | |
image: null, | |
processed: false, | |
processing: false | |
}; | |
scenes.push(scene); | |
renderScenes(); | |
// Select the new scene | |
selectScene(sceneId); | |
// Hide empty state if it's the first scene | |
if (scenes.length === 1) { | |
emptyState.classList.add('hidden'); | |
} | |
}); | |
// Render all scenes | |
function renderScenes() { | |
scenesContainer.innerHTML = ''; | |
scenes.forEach(scene => { | |
const sceneCard = document.createElement('div'); | |
sceneCard.className = `scene-card bg-white border rounded-lg p-4 relative ${currentSceneId === scene.id ? 'border-indigo-500 ring-1 ring-indigo-500' : 'border-gray-200'}`; | |
sceneCard.dataset.id = scene.id; | |
sceneCard.innerHTML = ` | |
<div class="flex items-start space-x-3"> | |
<div class="flex-shrink-0 w-16 h-16 bg-gray-100 rounded-md overflow-hidden flex items-center justify-center"> | |
${scene.image ? | |
`<img src="${scene.image}" alt="Scene thumbnail" class="w-full h-full object-cover">` : | |
`<i class="fas fa-image text-gray-400"></i>`} | |
</div> | |
<div class="flex-grow"> | |
<h3 class="font-medium">${scene.title}</h3> | |
<p class="text-sm text-gray-500"> | |
${scene.processing ? | |
'<span class="text-yellow-600"><i class="fas fa-spinner fa-spin mr-1"></i> Processing</span>' : | |
(scene.processed ? '<span class="text-green-600"><i class="fas fa-check-circle mr-1"></i> Processed</span>' : 'Not processed')} | |
</p> | |
</div> | |
</div> | |
<div class="scene-actions absolute top-2 right-2 flex space-x-1"> | |
<button class="edit-scene p-1 text-gray-500 hover:text-indigo-600 transition"> | |
<i class="fas fa-pencil-alt text-sm"></i> | |
</button> | |
<button class="delete-scene p-1 text-gray-500 hover:text-red-600 transition"> | |
<i class="fas fa-trash-alt text-sm"></i> | |
</button> | |
</div> | |
`; | |
scenesContainer.appendChild(sceneCard); | |
// Add event listeners to the buttons we just created | |
sceneCard.querySelector('.edit-scene').addEventListener('click', (e) => { | |
e.stopPropagation(); | |
// In a real app, we'd implement scene title editing | |
alert('Edit scene title functionality would go here'); | |
}); | |
sceneCard.querySelector('.delete-scene').addEventListener('click', (e) => { | |
e.stopPropagation(); | |
deleteScene(scene.id); | |
}); | |
// Select scene on click | |
sceneCard.addEventListener('click', () => { | |
selectScene(scene.id); | |
}); | |
}); | |
} | |
// Select a scene | |
function selectScene(sceneId) { | |
currentSceneId = sceneId; | |
const scene = scenes.find(s => s.id === sceneId); | |
if (scene) { | |
// Update UI | |
currentSceneIndicator.textContent = `Editing: ${scene.title}`; | |
emptyEditor.classList.add('hidden'); | |
activeEditor.classList.remove('hidden'); | |
// Show scene image if it exists | |
if (scene.image) { | |
sceneImage.src = scene.image; | |
sceneImage.classList.remove('hidden'); | |
noImage.classList.add('hidden'); | |
} else { | |
sceneImage.classList.add('hidden'); | |
noImage.classList.remove('hidden'); | |
} | |
// Re-render scenes to update selected state | |
renderScenes(); | |
} | |
} | |
// Delete a scene | |
function deleteScene(sceneId) { | |
if (confirm('Are you sure you want to delete this scene?')) { | |
scenes = scenes.filter(scene => scene.id !== sceneId); | |
// If we deleted the currently selected scene, clear the editor | |
if (currentSceneId === sceneId) { | |
currentSceneId = null; | |
emptyEditor.classList.remove('hidden'); | |
activeEditor.classList.add('hidden'); | |
currentSceneIndicator.textContent = 'No scene selected'; | |
} | |
// Show empty state if no scenes left | |
if (scenes.length === 0) { | |
emptyState.classList.remove('hidden'); | |
} | |
renderScenes(); | |
} | |
} | |
// Drag and drop for image upload | |
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
dropzone.addEventListener(eventName, preventDefaults, false); | |
}); | |
function preventDefaults(e) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
} | |
['dragenter', 'dragover'].forEach(eventName => { | |
dropzone.addEventListener(eventName, highlight, false); | |
}); | |
['dragleave', 'drop'].forEach(eventName => { | |
dropzone.addEventListener(eventName, unhighlight, false); | |
}); | |
function highlight() { | |
dropzone.classList.add('active'); | |
} | |
function unhighlight() { | |
dropzone.classList.remove('active'); | |
} | |
dropzone.addEventListener('drop', handleDrop, false); | |
dropzone.addEventListener('click', () => sceneUpload.click()); | |
function handleDrop(e) { | |
const dt = e.dataTransfer; | |
const files = dt.files; | |
handleFiles(files); | |
} | |
sceneUpload.addEventListener('change', function() { | |
handleFiles(this.files); | |
}); | |
function handleFiles(files) { | |
if (!currentSceneId) return; | |
const file = files[0]; | |
if (!file.type.match('image.*')) { | |
alert('Please select an image file'); | |
return; | |
} | |
if (file.size > 10 * 1024 * 1024) { | |
alert('File size should be less than 10MB'); | |
return; | |
} | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
const scene = scenes.find(s => s.id === currentSceneId); | |
if (scene) { | |
scene.image = e.target.result; | |
scene.processed = false; | |
renderScenes(); | |
// Update the editor view | |
sceneImage.src = e.target.result; | |
sceneImage.classList.remove('hidden'); | |
noImage.classList.add('hidden'); | |
} | |
}; | |
reader.readAsDataURL(file); | |
} | |
// Process scene with AI | |
processSceneBtn.addEventListener('click', function() { | |
if (!currentSceneId) return; | |
const scene = scenes.find(s => s.id === currentSceneId); | |
if (!scene || !scene.image) { | |
alert('Please add an image to the scene first'); | |
return; | |
} | |
const enhancement = document.getElementById('ai-enhancement').value; | |
const style = document.getElementById('ai-style').value; | |
// Simulate AI processing | |
scene.processing = true; | |
renderScenes(); | |
processingOverlay.classList.remove('hidden'); | |
processSceneBtn.disabled = true; | |
// This would be an API call in a real app | |
setTimeout(() => { | |
// Simulate successful processing | |
scene.processing = false; | |
scene.processed = true; | |
// In a real app, we'd get the processed image from the API | |
// For demo, we'll just add a filter to simulate processing | |
sceneImage.style.filter = getFilterForStyle(style); | |
processingOverlay.classList.add('hidden'); | |
processSceneBtn.disabled = false; | |
renderScenes(); | |
// Show success message | |
alert(`Scene processed with ${enhancement} enhancement and ${style} style!`); | |
}, 3000); | |
}); | |
function getFilterForStyle(style) { | |
switch(style) { | |
case 'cartoon': return 'contrast(1.5) saturate(1.5)'; | |
case 'watercolor': return 'contrast(1.2) brightness(1.1) sepia(0.3) saturate(1.5)'; | |
case 'cyberpunk': return 'contrast(1.8) brightness(0.8) hue-rotate(-20deg) saturate(2)'; | |
case 'fantasy': return 'contrast(1.3) brightness(1.1) hue-rotate(10deg) saturate(1.8)'; | |
default: return 'none'; | |
} | |
} | |
// Generate final video | |
generateVideoBtn.addEventListener('click', function() { | |
if (scenes.length === 0) { | |
alert('Please add at least one scene'); | |
return; | |
} | |
const unprocessedScenes = scenes.filter(s => !s.processed); | |
if (unprocessedScenes.length > 0) { | |
if (!confirm(`${unprocessedScenes.length} scenes are not processed. Do you want to continue without processing them?`)) { | |
return; | |
} | |
} | |
// Simulate video generation | |
progressContainer.classList.remove('hidden'); | |
generateVideoBtn.disabled = true; | |
let progress = 0; | |
const interval = setInterval(() => { | |
progress += Math.random() * 10; | |
if (progress > 100) progress = 100; | |
progressBar.style.width = `${progress}%`; | |
progressPercent.textContent = `${Math.floor(progress)}%`; | |
if (progress === 100) { | |
clearInterval(interval); | |
// Simulate video being ready | |
setTimeout(() => { | |
// In a real app, we'd have an actual video URL from the API | |
videoEmpty.classList.add('hidden'); | |
videoPreview.classList.remove('hidden'); | |
// For demo purposes, we'll just show a message | |
videoPreview.innerHTML = ` | |
<div class="h-full flex items-center justify-center text-white text-center p-6"> | |
<div> | |
<i class="fas fa-check-circle text-4xl text-green-400 mb-3"></i> | |
<h3 class="text-xl font-medium mb-2">Video Generated Successfully!</h3> | |
<p class="mb-4">Your AI-enhanced video is ready.</p> | |
<button class="bg-indigo-600 hover:bg-indigo-700 px-4 py-2 rounded-lg transition"> | |
Download Video | |
</button> | |
</div> | |
</div> | |
`; | |
progressContainer.classList.add('hidden'); | |
generateVideoBtn.disabled = false; | |
}, 500); | |
} | |
}, 300); | |
}); | |
}); | |
</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=zdwalter/ai-video-generator" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> | |
</html> |