videomerger / index.html
tsbetterworkpmo's picture
Add 3 files
c3f3eea verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VideoMerge Pro - Combine Videos in 4K</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>
.dropzone {
border: 2px dashed #6366f1;
border-radius: 0.5rem;
transition: all 0.3s ease;
}
.dropzone.active {
border-color: #10b981;
background-color: rgba(16, 185, 129, 0.05);
}
.video-preview {
aspect-ratio: 16/9;
background-color: #1e293b;
}
.progress-bar {
height: 6px;
transition: width 0.3s ease;
}
.timeline-track {
height: 60px;
background-color: #1e293b;
overflow-x: auto;
}
.video-clip {
min-width: 120px;
height: 50px;
background-color: #6366f1;
border-radius: 4px;
position: relative;
}
.clip-handle {
width: 6px;
height: 100%;
background-color: rgba(255, 255, 255, 0.5);
position: absolute;
top: 0;
cursor: ew-resize;
}
.clip-handle.left {
left: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.clip-handle.right {
right: 0;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.settings-panel {
transition: all 0.3s ease;
max-height: 0;
overflow: hidden;
}
.settings-panel.open {
max-height: 500px;
}
</style>
</head>
<body class="bg-gray-900 text-gray-100 min-h-screen">
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<header class="mb-8">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-2">
<i class="fas fa-film text-indigo-500 text-2xl"></i>
<h1 class="text-2xl font-bold">VideoMerge <span class="text-indigo-500">Pro</span></h1>
</div>
<div>
<button class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg flex items-center space-x-2">
<i class="fas fa-user"></i>
<span>Sign In</span>
</button>
</div>
</div>
<p class="text-gray-400 mt-2">Combine multiple videos seamlessly and export in stunning 4K quality</p>
</header>
<!-- Main Content -->
<main>
<!-- Upload Section -->
<section class="mb-8">
<div class="dropzone p-8 text-center cursor-pointer" id="dropzone">
<div class="flex flex-col items-center justify-center space-y-4">
<i class="fas fa-cloud-upload-alt text-indigo-500 text-5xl"></i>
<h2 class="text-xl font-semibold">Drag & Drop Video Files</h2>
<p class="text-gray-400">or click to browse files (MP4, MOV, AVI supported)</p>
<input type="file" id="fileInput" class="hidden" accept="video/*" multiple>
<button id="browseBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2 rounded-lg">
Select Videos
</button>
</div>
</div>
</section>
<!-- Video Timeline -->
<section class="mb-8 bg-gray-800 rounded-lg p-4">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Video Timeline</h3>
<div class="flex space-x-2">
<button id="clearAllBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-3 py-1 rounded">
<i class="fas fa-trash mr-1"></i> Clear All
</button>
<button id="addTransitionBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-3 py-1 rounded">
<i class="fas fa-random mr-1"></i> Add Transition
</button>
</div>
</div>
<div class="timeline-track rounded p-2 mb-4" id="timeline">
<div class="flex space-x-2" id="videoClipsContainer">
<!-- Video clips will be added here dynamically -->
<div class="text-gray-400 flex items-center justify-center w-full" id="emptyTimelineMessage">
<p>Add videos to start creating your timeline</p>
</div>
</div>
</div>
<div class="flex justify-between items-center">
<div class="text-sm text-gray-400">
<span id="totalDuration">Total Duration: 0:00</span>
</div>
<div>
<button id="playAllBtn" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg mr-2">
<i class="fas fa-play mr-1"></i> Play All
</button>
<button id="previewBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">
<i class="fas fa-eye mr-1"></i> Preview
</button>
</div>
</div>
</section>
<!-- Preview Section -->
<section class="mb-8 bg-gray-800 rounded-lg p-4 hidden" id="previewSection">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Preview</h3>
<button id="closePreviewBtn" class="text-gray-400 hover:text-white">
<i class="fas fa-times"></i>
</button>
</div>
<div class="video-preview rounded-lg overflow-hidden relative" id="previewVideo">
<video controls class="w-full h-full" id="mergedPreview"></video>
<div class="absolute inset-0 flex items-center justify-center bg-black bg-opacity-50 hidden" id="previewOverlay">
<i class="fas fa-play text-white text-4xl"></i>
</div>
</div>
</section>
<!-- Export Settings -->
<section class="mb-8 bg-gray-800 rounded-lg overflow-hidden">
<div class="p-4 cursor-pointer flex justify-between items-center" id="settingsToggle">
<h3 class="text-lg font-semibold">
<i class="fas fa-cog text-indigo-500 mr-2"></i> Export Settings
</h3>
<i class="fas fa-chevron-down text-gray-400 transition-transform" id="settingsChevron"></i>
</div>
<div class="settings-panel bg-gray-700" id="settingsPanel">
<div class="p-4 grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Resolution -->
<div>
<label class="block text-sm font-medium mb-2">Resolution</label>
<div class="flex space-x-2">
<button class="resolution-btn px-3 py-1 rounded border border-gray-600 hover:bg-gray-600" data-resolution="720p">720p</button>
<button class="resolution-btn px-3 py-1 rounded border border-gray-600 hover:bg-gray-600" data-resolution="1080p">1080p</button>
<button class="resolution-btn px-3 py-1 rounded bg-indigo-600 border-indigo-600 text-white" data-resolution="4k">4K (3840x2160)</button>
</div>
</div>
<!-- Frame Rate -->
<div>
<label class="block text-sm font-medium mb-2">Frame Rate</label>
<div class="flex space-x-2">
<button class="fps-btn px-3 py-1 rounded border border-gray-600 hover:bg-gray-600" data-fps="24">24fps</button>
<button class="fps-btn px-3 py-1 rounded bg-indigo-600 border-indigo-600 text-white" data-fps="30">30fps</button>
<button class="fps-btn px-3 py-1 rounded border border-gray-600 hover:bg-gray-600" data-fps="60">60fps</button>
</div>
</div>
<!-- Format -->
<div>
<label class="block text-sm font-medium mb-2">Format</label>
<div class="flex space-x-2">
<button class="format-btn px-3 py-1 rounded bg-indigo-600 border-indigo-600 text-white" data-format="mp4">MP4</button>
<button class="format-btn px-3 py-1 rounded border border-gray-600 hover:bg-gray-600" data-format="mov">MOV</button>
<button class="format-btn px-3 py-1 rounded border border-gray-600 hover:bg-gray-600" data-format="avi">AVI</button>
</div>
</div>
<!-- Quality -->
<div>
<label class="block text-sm font-medium mb-2">Quality</label>
<div class="flex items-center space-x-2">
<input type="range" min="1" max="100" value="90" class="w-full" id="qualitySlider">
<span class="text-sm w-12 text-center" id="qualityValue">90%</span>
</div>
</div>
</div>
</div>
</section>
<!-- Export Button -->
<section class="text-center">
<button id="exportBtn" class="bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 text-white px-8 py-3 rounded-lg text-lg font-semibold shadow-lg">
<i class="fas fa-file-export mr-2"></i> Export Merged Video (4K 30fps)
</button>
</section>
</main>
<!-- Processing Modal -->
<div class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 hidden" id="processingModal">
<div class="bg-gray-800 rounded-lg p-6 max-w-md w-full">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Processing Your Video</h3>
<div class="text-sm text-gray-400" id="processingStep">Step 1/3: Preparing files</div>
</div>
<div class="w-full bg-gray-700 rounded-full h-2.5 mb-4">
<div class="progress-bar bg-indigo-600 rounded-full h-2.5" id="progressBar" style="width: 0%"></div>
</div>
<div class="text-center py-4">
<i class="fas fa-cog fa-spin text-indigo-500 text-4xl mb-3"></i>
<p class="text-gray-300" id="processingMessage">Combining your videos and optimizing for 4K quality...</p>
</div>
<div class="text-right">
<button id="cancelProcessingBtn" class="text-gray-400 hover:text-white">
Cancel Processing
</button>
</div>
</div>
</div>
<!-- Success Modal -->
<div class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 hidden" id="successModal">
<div class="bg-gray-800 rounded-lg p-6 max-w-md w-full text-center">
<div class="text-green-500 text-5xl mb-4">
<i class="fas fa-check-circle"></i>
</div>
<h3 class="text-xl font-semibold mb-2">Export Complete!</h3>
<p class="text-gray-300 mb-6">Your 4K 30fps video is ready to download.</p>
<div class="flex justify-center space-x-4">
<button id="downloadBtn" class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-lg">
<i class="fas fa-download mr-2"></i> Download
</button>
<button id="newProjectBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-6 py-2 rounded-lg">
<i class="fas fa-plus mr-2"></i> New Project
</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');
const browseBtn = document.getElementById('browseBtn');
const videoClipsContainer = document.getElementById('videoClipsContainer');
const emptyTimelineMessage = document.getElementById('emptyTimelineMessage');
const totalDuration = document.getElementById('totalDuration');
const clearAllBtn = document.getElementById('clearAllBtn');
const playAllBtn = document.getElementById('playAllBtn');
const previewBtn = document.getElementById('previewBtn');
const previewSection = document.getElementById('previewSection');
const closePreviewBtn = document.getElementById('closePreviewBtn');
const mergedPreview = document.getElementById('mergedPreview');
const previewOverlay = document.getElementById('previewOverlay');
const exportBtn = document.getElementById('exportBtn');
const processingModal = document.getElementById('processingModal');
const successModal = document.getElementById('successModal');
const progressBar = document.getElementById('progressBar');
const processingStep = document.getElementById('processingStep');
const processingMessage = document.getElementById('processingMessage');
const downloadBtn = document.getElementById('downloadBtn');
const newProjectBtn = document.getElementById('newProjectBtn');
const cancelProcessingBtn = document.getElementById('cancelProcessingBtn');
const settingsToggle = document.getElementById('settingsToggle');
const settingsPanel = document.getElementById('settingsPanel');
const settingsChevron = document.getElementById('settingsChevron');
const qualitySlider = document.getElementById('qualitySlider');
const qualityValue = document.getElementById('qualityValue');
// State
let videoFiles = [];
let totalDurationSeconds = 0;
// Initialize settings panel
settingsToggle.addEventListener('click', () => {
settingsPanel.classList.toggle('open');
settingsChevron.classList.toggle('rotate-180');
});
// Quality slider
qualitySlider.addEventListener('input', () => {
qualityValue.textContent = `${qualitySlider.value}%`;
});
// Dropzone functionality
['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);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles(files);
}
browseBtn.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', () => {
handleFiles(fileInput.files);
});
function handleFiles(files) {
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (file.type.startsWith('video/')) {
videoFiles.push(file);
addVideoToTimeline(file);
}
}
if (videoFiles.length > 0) {
emptyTimelineMessage.classList.add('hidden');
updateTotalDuration();
}
}
function addVideoToTimeline(file) {
const videoClip = document.createElement('div');
videoClip.className = 'video-clip flex-shrink-0 relative group';
videoClip.draggable = true;
videoClip.dataset.filename = file.name;
// Create video element to get duration
const video = document.createElement('video');
video.src = URL.createObjectURL(file);
video.onloadedmetadata = function() {
const duration = video.duration;
totalDurationSeconds += duration;
updateTotalDuration();
// Set clip width based on duration (120px = 10 seconds)
const width = Math.max(120, (duration / 10) * 120);
videoClip.style.width = `${width}px`;
// Add clip info
const clipInfo = document.createElement('div');
clipInfo.className = 'absolute inset-0 flex items-center justify-center text-white text-xs font-medium truncate px-2';
clipInfo.textContent = `${file.name} (${formatDuration(duration)})`;
videoClip.appendChild(clipInfo);
// Add resize handles
const leftHandle = document.createElement('div');
leftHandle.className = 'clip-handle left opacity-0 group-hover:opacity-100';
videoClip.appendChild(leftHandle);
const rightHandle = document.createElement('div');
rightHandle.className = 'clip-handle right opacity-0 group-hover:opacity-100';
videoClip.appendChild(rightHandle);
// Add drag and drop functionality
videoClip.addEventListener('dragstart', handleDragStart);
videoClip.addEventListener('dragover', handleDragOver);
videoClip.addEventListener('drop', handleDropClip);
};
videoClipsContainer.appendChild(videoClip);
}
function updateTotalDuration() {
const minutes = Math.floor(totalDurationSeconds / 60);
const seconds = Math.floor(totalDurationSeconds % 60);
totalDuration.textContent = `Total Duration: ${minutes}:${seconds.toString().padStart(2, '0')}`;
}
function formatDuration(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
// Timeline functionality
function handleDragStart(e) {
e.dataTransfer.setData('text/plain', e.target.dataset.filename);
e.target.classList.add('opacity-50');
}
function handleDragOver(e) {
e.preventDefault();
const afterElement = getDragAfterElement(videoClipsContainer, e.clientX);
const draggable = document.querySelector('.dragging');
if (afterElement == null) {
videoClipsContainer.appendChild(draggable);
} else {
videoClipsContainer.insertBefore(draggable, afterElement);
}
}
function handleDropClip(e) {
e.preventDefault();
e.target.classList.remove('opacity-50');
}
function getDragAfterElement(container, x) {
const draggableElements = [...container.querySelectorAll('.video-clip:not(.dragging)')];
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect();
const offset = x - box.left - box.width / 2;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
} else {
return closest;
}
}, { offset: Number.NEGATIVE_INFINITY }).element;
}
// Clear all button
clearAllBtn.addEventListener('click', () => {
videoFiles = [];
totalDurationSeconds = 0;
videoClipsContainer.innerHTML = '';
emptyTimelineMessage.classList.remove('hidden');
updateTotalDuration();
});
// Preview functionality
previewBtn.addEventListener('click', () => {
if (videoFiles.length === 0) return;
previewSection.classList.remove('hidden');
// In a real app, we would actually merge the videos here
// For this demo, we'll just show the first video
mergedPreview.src = URL.createObjectURL(videoFiles[0]);
mergedPreview.load();
});
closePreviewBtn.addEventListener('click', () => {
previewSection.classList.add('hidden');
mergedPreview.pause();
});
previewOverlay.addEventListener('click', () => {
mergedPreview.play();
previewOverlay.classList.add('hidden');
});
mergedPreview.addEventListener('pause', () => {
previewOverlay.classList.remove('hidden');
});
// Export functionality
exportBtn.addEventListener('click', () => {
if (videoFiles.length === 0) return;
processingModal.classList.remove('hidden');
let progress = 0;
const interval = setInterval(() => {
progress += 5;
progressBar.style.width = `${progress}%`;
if (progress <= 30) {
processingStep.textContent = 'Step 1/3: Preparing files';
processingMessage.textContent = 'Analyzing video files and preparing for merge...';
} else if (progress <= 70) {
processingStep.textContent = 'Step 2/3: Merging videos';
processingMessage.textContent = 'Combining your videos and applying transitions...';
} else {
processingStep.textContent = 'Step 3/3: Rendering 4K output';
processingMessage.textContent = 'Optimizing for 4K quality at 30fps...';
}
if (progress >= 100) {
clearInterval(interval);
setTimeout(() => {
processingModal.classList.add('hidden');
successModal.classList.remove('hidden');
}, 500);
}
}, 200);
cancelProcessingBtn.addEventListener('click', () => {
clearInterval(interval);
processingModal.classList.add('hidden');
}, { once: true });
});
// Success modal buttons
downloadBtn.addEventListener('click', () => {
// In a real app, this would download the merged video
alert('Download would start here in a real application');
});
newProjectBtn.addEventListener('click', () => {
successModal.classList.add('hidden');
clearAllBtn.click();
});
// Resolution, FPS, and Format buttons
document.querySelectorAll('.resolution-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.resolution-btn').forEach(b => {
b.classList.remove('bg-indigo-600', 'border-indigo-600', 'text-white');
b.classList.add('border-gray-600', 'hover:bg-gray-600');
});
btn.classList.add('bg-indigo-600', 'border-indigo-600', 'text-white');
btn.classList.remove('border-gray-600', 'hover:bg-gray-600');
// Update export button text
if (btn.dataset.resolution === '4k') {
exportBtn.innerHTML = `<i class="fas fa-file-export mr-2"></i> Export Merged Video (4K 30fps)`;
} else {
exportBtn.innerHTML = `<i class="fas fa-file-export mr-2"></i> Export Merged Video (${btn.dataset.resolution.toUpperCase()} 30fps)`;
}
});
});
document.querySelectorAll('.fps-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.fps-btn').forEach(b => {
b.classList.remove('bg-indigo-600', 'border-indigo-600', 'text-white');
b.classList.add('border-gray-600', 'hover:bg-gray-600');
});
btn.classList.add('bg-indigo-600', 'border-indigo-600', 'text-white');
btn.classList.remove('border-gray-600', 'hover:bg-gray-600');
});
});
document.querySelectorAll('.format-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.format-btn').forEach(b => {
b.classList.remove('bg-indigo-600', 'border-indigo-600', 'text-white');
b.classList.add('border-gray-600', 'hover:bg-gray-600');
});
btn.classList.add('bg-indigo-600', 'border-indigo-600', 'text-white');
btn.classList.remove('border-gray-600', 'hover:bg-gray-600');
});
});
});
</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=tsbetterworkpmo/videomerger" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>