|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Video Clip 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> |
|
|
.video-container { |
|
|
position: relative; |
|
|
padding-bottom: 56.25%; |
|
|
height: 0; |
|
|
overflow: hidden; |
|
|
} |
|
|
.video-container video { |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
} |
|
|
.clip-card { |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.clip-card:hover { |
|
|
transform: translateY(-5px); |
|
|
box-shadow: 0 10px 20px rgba(0,0,0,0.1); |
|
|
} |
|
|
.progress-bar { |
|
|
height: 4px; |
|
|
background-color: #e5e7eb; |
|
|
position: relative; |
|
|
} |
|
|
.progress-fill { |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
height: 100%; |
|
|
background-color: #3b82f6; |
|
|
transition: width 0.3s ease; |
|
|
} |
|
|
.waveform { |
|
|
height: 60px; |
|
|
background: linear-gradient(90deg, #e5e7eb 50%, transparent 50%); |
|
|
background-size: 10px 100%; |
|
|
position: relative; |
|
|
} |
|
|
.waveform-active { |
|
|
background: linear-gradient(90deg, #3b82f6 50%, transparent 50%); |
|
|
background-size: 10px 100%; |
|
|
} |
|
|
.file-info { |
|
|
display: none; |
|
|
} |
|
|
.ffmpeg-loading { |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
bottom: 0; |
|
|
background-color: rgba(255,255,255,0.8); |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
z-index: 1000; |
|
|
} |
|
|
.ffmpeg-progress { |
|
|
width: 80%; |
|
|
max-width: 400px; |
|
|
margin-top: 20px; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-50 min-h-screen"> |
|
|
<div id="ffmpegLoading" class="ffmpeg-loading"> |
|
|
<div class="text-center"> |
|
|
<div class="animate-spin rounded-full h-16 w-16 border-t-4 border-b-4 border-blue-500 mb-4"></div> |
|
|
<h2 class="text-xl font-semibold text-gray-800 mb-2">Loading Video Processor</h2> |
|
|
<p class="text-gray-600 mb-4">This may take a few moments...</p> |
|
|
<div class="ffmpeg-progress"> |
|
|
<div class="w-full bg-gray-200 rounded-full h-2.5"> |
|
|
<div id="ffmpegProgressBar" class="bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div> |
|
|
</div> |
|
|
<p id="ffmpegProgressText" class="text-sm text-gray-600 mt-2">Initializing...</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="container mx-auto px-4 py-8"> |
|
|
<header class="text-center mb-12"> |
|
|
<h1 class="text-4xl font-bold text-gray-800 mb-2">Video Clip Generator</h1> |
|
|
<p class="text-lg text-gray-600">Upload your MP4 video and we'll automatically create interesting clips for you</p> |
|
|
</header> |
|
|
|
|
|
<div class="max-w-3xl mx-auto bg-white rounded-xl shadow-md overflow-hidden p-6 mb-8"> |
|
|
<div class="text-center mb-6"> |
|
|
<div class="flex justify-center mb-4"> |
|
|
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center"> |
|
|
<i class="fas fa-video text-blue-500 text-2xl"></i> |
|
|
</div> |
|
|
</div> |
|
|
<h2 class="text-xl font-semibold text-gray-800 mb-2">Upload Your Video</h2> |
|
|
<p class="text-gray-500 mb-4">We'll analyze your video and create clips between 30-60 seconds</p> |
|
|
|
|
|
<div class="border-2 border-dashed border-gray-300 rounded-lg p-8 mb-4" id="dropZone"> |
|
|
<input type="file" id="videoUpload" accept="video/mp4" class="hidden"> |
|
|
<label for="videoUpload" class="cursor-pointer"> |
|
|
<div class="flex flex-col items-center justify-center"> |
|
|
<i class="fas fa-cloud-upload-alt text-4xl text-blue-500 mb-3"></i> |
|
|
<p class="text-gray-600 mb-2">Drag & drop your MP4 file here</p> |
|
|
<p class="text-sm text-gray-400 mb-3">or</p> |
|
|
<button type="button" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-6 rounded-full transition duration-300"> |
|
|
Browse Files |
|
|
</button> |
|
|
</div> |
|
|
</label> |
|
|
<div id="fileInfo" class="file-info mt-4 p-3 bg-gray-50 rounded-lg"> |
|
|
<div class="flex items-center"> |
|
|
<i class="fas fa-file-video text-blue-500 mr-3"></i> |
|
|
<div class="text-left"> |
|
|
<p class="font-medium text-gray-800" id="fileName"></p> |
|
|
<p class="text-sm text-gray-500" id="fileSize"></p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="uploadProgress" class="hidden"> |
|
|
<div class="flex items-center justify-between mb-2"> |
|
|
<span class="text-sm font-medium text-gray-700">Uploading...</span> |
|
|
<span class="text-sm font-medium text-gray-700" id="progressPercent">0%</span> |
|
|
</div> |
|
|
<div class="progress-bar w-full rounded-full"> |
|
|
<div class="progress-fill rounded-full" id="progressFill" style="width: 0%"></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="videoProcessing" class="hidden max-w-3xl mx-auto bg-white rounded-xl shadow-md overflow-hidden p-6 mb-8"> |
|
|
<div class="flex items-center justify-center"> |
|
|
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mr-4"></div> |
|
|
<div> |
|
|
<h3 class="text-lg font-medium text-gray-800">Processing your video...</h3> |
|
|
<p class="text-gray-500">We're analyzing your video to find the best clips</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="videoPreviewSection" class="hidden max-w-3xl mx-auto bg-white rounded-xl shadow-md overflow-hidden p-6 mb-8"> |
|
|
<h2 class="text-xl font-semibold text-gray-800 mb-4">Your Video</h2> |
|
|
<div class="video-container mb-4 rounded-lg overflow-hidden"> |
|
|
<video id="videoPlayer" controls class="w-full"></video> |
|
|
</div> |
|
|
<div class="flex justify-between items-center"> |
|
|
<span class="text-sm text-gray-500" id="videoDuration">Duration: 0:00</span> |
|
|
<button id="analyzeBtn" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded transition duration-300"> |
|
|
<i class="fas fa-magic mr-2"></i> Generate Clips |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="clipsSection" class="hidden max-w-4xl mx-auto"> |
|
|
<h2 class="text-2xl font-semibold text-gray-800 mb-6">Generated Clips</h2> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6" id="clipsContainer"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script src="https://unpkg.com/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js"></script> |
|
|
<script src="https://unpkg.com/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js"></script> |
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
const videoUpload = document.getElementById('videoUpload'); |
|
|
const dropZone = document.getElementById('dropZone'); |
|
|
const uploadProgress = document.getElementById('uploadProgress'); |
|
|
const progressFill = document.getElementById('progressFill'); |
|
|
const progressPercent = document.getElementById('progressPercent'); |
|
|
const videoProcessing = document.getElementById('videoProcessing'); |
|
|
const videoPreviewSection = document.getElementById('videoPreviewSection'); |
|
|
const videoPlayer = document.getElementById('videoPlayer'); |
|
|
const videoDuration = document.getElementById('videoDuration'); |
|
|
const analyzeBtn = document.getElementById('analyzeBtn'); |
|
|
const clipsSection = document.getElementById('clipsSection'); |
|
|
const clipsContainer = document.getElementById('clipsContainer'); |
|
|
const fileInfo = document.getElementById('fileInfo'); |
|
|
const fileName = document.getElementById('fileName'); |
|
|
const fileSize = document.getElementById('fileSize'); |
|
|
const ffmpegLoading = document.getElementById('ffmpegLoading'); |
|
|
const ffmpegProgressBar = document.getElementById('ffmpegProgressBar'); |
|
|
const ffmpegProgressText = document.getElementById('ffmpegProgressText'); |
|
|
|
|
|
let videoFile = null; |
|
|
let videoUrl = null; |
|
|
let ffmpeg = null; |
|
|
|
|
|
|
|
|
async function initFFmpeg() { |
|
|
const { createFFmpeg, fetchFile } = FFmpeg; |
|
|
ffmpeg = createFFmpeg({ |
|
|
log: true, |
|
|
corePath: 'https://unpkg.com/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js' |
|
|
}); |
|
|
|
|
|
|
|
|
ffmpeg.setProgress(({ ratio }) => { |
|
|
const percent = Math.round(ratio * 100); |
|
|
ffmpegProgressBar.style.width = `${percent}%`; |
|
|
ffmpegProgressText.textContent = `Loading: ${percent}%`; |
|
|
}); |
|
|
|
|
|
try { |
|
|
await ffmpeg.load(); |
|
|
ffmpegProgressBar.style.width = '100%'; |
|
|
ffmpegProgressText.textContent = 'Ready!'; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
ffmpegLoading.style.display = 'none'; |
|
|
}, 500); |
|
|
} catch (error) { |
|
|
console.error('FFmpeg loading error:', error); |
|
|
ffmpegProgressText.textContent = 'Error loading FFmpeg. Please refresh the page.'; |
|
|
} |
|
|
} |
|
|
|
|
|
initFFmpeg(); |
|
|
|
|
|
|
|
|
['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('border-blue-500'); |
|
|
dropZone.classList.remove('border-gray-300'); |
|
|
} |
|
|
|
|
|
function unhighlight() { |
|
|
dropZone.classList.remove('border-blue-500'); |
|
|
dropZone.classList.add('border-gray-300'); |
|
|
} |
|
|
|
|
|
dropZone.addEventListener('drop', handleDrop, false); |
|
|
|
|
|
function handleDrop(e) { |
|
|
const dt = e.dataTransfer; |
|
|
const files = dt.files; |
|
|
if (files.length > 0 && files[0].type === 'video/mp4') { |
|
|
handleFiles(files); |
|
|
} else { |
|
|
alert('Please upload an MP4 video file.'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
videoUpload.addEventListener('change', function(e) { |
|
|
if (this.files.length > 0) { |
|
|
if (this.files[0].type === 'video/mp4') { |
|
|
handleFiles(this.files); |
|
|
} else { |
|
|
alert('Please upload an MP4 video file.'); |
|
|
this.value = ''; |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
function handleFiles(files) { |
|
|
videoFile = files[0]; |
|
|
|
|
|
|
|
|
fileName.textContent = videoFile.name; |
|
|
fileSize.textContent = formatFileSize(videoFile.size); |
|
|
fileInfo.style.display = 'block'; |
|
|
|
|
|
|
|
|
uploadProgress.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
let progress = 0; |
|
|
const interval = setInterval(() => { |
|
|
progress += Math.random() * 10; |
|
|
if (progress >= 100) { |
|
|
progress = 100; |
|
|
clearInterval(interval); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
uploadProgress.classList.add('hidden'); |
|
|
processVideo(); |
|
|
}, 500); |
|
|
} |
|
|
|
|
|
progressFill.style.width = `${progress}%`; |
|
|
progressPercent.textContent = `${Math.round(progress)}%`; |
|
|
}, 200); |
|
|
} |
|
|
|
|
|
function formatFileSize(bytes) { |
|
|
if (bytes === 0) return '0 Bytes'; |
|
|
const k = 1024; |
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB']; |
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k)); |
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
|
|
} |
|
|
|
|
|
function processVideo() { |
|
|
videoProcessing.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
if (videoUrl) { |
|
|
URL.revokeObjectURL(videoUrl); |
|
|
} |
|
|
videoUrl = URL.createObjectURL(videoFile); |
|
|
|
|
|
|
|
|
videoPlayer.src = videoUrl; |
|
|
|
|
|
|
|
|
videoPlayer.onloadedmetadata = function() { |
|
|
videoProcessing.classList.add('hidden'); |
|
|
videoPreviewSection.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
const duration = videoPlayer.duration; |
|
|
const minutes = Math.floor(duration / 60); |
|
|
const seconds = Math.floor(duration % 60); |
|
|
videoDuration.textContent = `Duration: ${minutes}:${seconds < 10 ? '0' + seconds : seconds}`; |
|
|
}; |
|
|
|
|
|
videoPlayer.onerror = function() { |
|
|
videoProcessing.classList.add('hidden'); |
|
|
alert('Error loading video. Please try another file.'); |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
analyzeBtn.addEventListener('click', function() { |
|
|
videoPreviewSection.classList.add('hidden'); |
|
|
videoProcessing.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
generateClips(); |
|
|
}, 1000); |
|
|
}); |
|
|
|
|
|
async function generateClips() { |
|
|
|
|
|
clipsContainer.innerHTML = ''; |
|
|
|
|
|
const duration = videoPlayer.duration; |
|
|
const clipCount = Math.max(1, Math.floor(duration / 45)); |
|
|
|
|
|
|
|
|
for (let i = 0; i < clipCount; i++) { |
|
|
const clipDuration = 30 + Math.random() * 30; |
|
|
const startTime = Math.min(i * (duration / clipCount), duration - clipDuration); |
|
|
const endTime = Math.min(startTime + clipDuration, duration); |
|
|
|
|
|
createClipCard(i + 1, startTime, endTime); |
|
|
} |
|
|
|
|
|
videoProcessing.classList.add('hidden'); |
|
|
clipsSection.classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function createClipCard(index, startTime, endTime) { |
|
|
const card = document.createElement('div'); |
|
|
card.className = 'clip-card bg-white rounded-lg shadow-md overflow-hidden'; |
|
|
|
|
|
|
|
|
const formatTime = (time) => { |
|
|
const minutes = Math.floor(time / 60); |
|
|
const seconds = Math.floor(time % 60); |
|
|
return `${minutes}:${seconds < 10 ? '0' + seconds : seconds}`; |
|
|
}; |
|
|
|
|
|
|
|
|
const waveform = document.createElement('div'); |
|
|
waveform.className = 'waveform mb-2'; |
|
|
|
|
|
|
|
|
const activeWaveform = document.createElement('div'); |
|
|
activeWaveform.className = 'waveform-active absolute top-0 left-0'; |
|
|
activeWaveform.style.width = `${(endTime - startTime) / videoPlayer.duration * 100}%`; |
|
|
activeWaveform.style.left = `${startTime / videoPlayer.duration * 100}%`; |
|
|
waveform.appendChild(activeWaveform); |
|
|
|
|
|
card.innerHTML = ` |
|
|
<div class="p-4"> |
|
|
<div class="flex justify-between items-center mb-3"> |
|
|
<h3 class="text-lg font-medium text-gray-800">Clip #${index}</h3> |
|
|
<span class="text-sm bg-blue-100 text-blue-600 py-1 px-2 rounded-full"> |
|
|
${formatTime(endTime - startTime)} |
|
|
</span> |
|
|
</div> |
|
|
<div class="mb-3"> |
|
|
${waveform.outerHTML} |
|
|
<div class="flex justify-between text-xs text-gray-500"> |
|
|
<span>${formatTime(startTime)}</span> |
|
|
<span>${formatTime(endTime)}</span> |
|
|
</div> |
|
|
</div> |
|
|
<div class="flex justify-between items-center"> |
|
|
<button class="preview-clip text-blue-500 hover:text-blue-600 font-medium flex items-center" |
|
|
data-start="${startTime}" data-end="${endTime}"> |
|
|
<i class="fas fa-play mr-2"></i> Preview |
|
|
</button> |
|
|
<button class="download-clip bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded transition duration-300 flex items-center" |
|
|
data-start="${startTime}" data-end="${endTime}"> |
|
|
<i class="fas fa-download mr-2"></i> Download |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
clipsContainer.appendChild(card); |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('click', function(e) { |
|
|
if (e.target.classList.contains('preview-clip') || e.target.parentElement.classList.contains('preview-clip')) { |
|
|
const button = e.target.classList.contains('preview-clip') ? e.target : e.target.parentElement; |
|
|
const startTime = parseFloat(button.getAttribute('data-start')); |
|
|
const endTime = parseFloat(button.getAttribute('data-end')); |
|
|
|
|
|
videoPlayer.currentTime = startTime; |
|
|
videoPlayer.play(); |
|
|
|
|
|
|
|
|
const checkTime = setInterval(() => { |
|
|
if (videoPlayer.currentTime >= endTime) { |
|
|
videoPlayer.pause(); |
|
|
clearInterval(checkTime); |
|
|
} |
|
|
}, 100); |
|
|
} |
|
|
|
|
|
if (e.target.classList.contains('download-clip') || e.target.parentElement.classList.contains('download-clip')) { |
|
|
const button = e.target.classList.contains('download-clip') ? e.target : e.target.parentElement; |
|
|
const startTime = parseFloat(button.getAttribute('data-start')); |
|
|
const endTime = parseFloat(button.getAttribute('data-end')); |
|
|
|
|
|
downloadClip(startTime, endTime, button); |
|
|
} |
|
|
}); |
|
|
|
|
|
async function downloadClip(startTime, endTime, button) { |
|
|
if (!ffmpeg || !ffmpeg.isLoaded()) { |
|
|
alert('Video processor is still loading. Please wait a moment and try again.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const originalHTML = button.innerHTML; |
|
|
button.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Processing...'; |
|
|
button.disabled = true; |
|
|
|
|
|
try { |
|
|
|
|
|
const statusElement = document.createElement('div'); |
|
|
statusElement.className = 'text-xs text-gray-500 mt-1'; |
|
|
statusElement.textContent = 'Preparing clip...'; |
|
|
button.parentNode.appendChild(statusElement); |
|
|
|
|
|
|
|
|
statusElement.textContent = 'Reading video file...'; |
|
|
await ffmpeg.writeFile('input.mp4', await fetchFile(videoFile)); |
|
|
|
|
|
|
|
|
statusElement.textContent = 'Processing video...'; |
|
|
await ffmpeg.exec([ |
|
|
'-i', 'input.mp4', |
|
|
'-ss', startTime.toString(), |
|
|
'-to', endTime.toString(), |
|
|
'-c', 'copy', |
|
|
'output.mp4' |
|
|
]); |
|
|
|
|
|
|
|
|
statusElement.textContent = 'Finalizing clip...'; |
|
|
const data = await ffmpeg.readFile('output.mp4'); |
|
|
|
|
|
|
|
|
const blob = new Blob([data.buffer], { type: 'video/mp4' }); |
|
|
const url = URL.createObjectURL(blob); |
|
|
const a = document.createElement('a'); |
|
|
a.href = url; |
|
|
a.download = `clip_${Math.floor(startTime)}_${Math.floor(endTime)}.mp4`; |
|
|
document.body.appendChild(a); |
|
|
a.click(); |
|
|
document.body.removeChild(a); |
|
|
|
|
|
|
|
|
URL.revokeObjectURL(url); |
|
|
statusElement.remove(); |
|
|
|
|
|
|
|
|
button.innerHTML = '<i class="fas fa-check mr-2"></i> Downloaded!'; |
|
|
button.classList.remove('bg-blue-500', 'hover:bg-blue-600'); |
|
|
button.classList.add('bg-green-500', 'hover:bg-green-600'); |
|
|
|
|
|
setTimeout(() => { |
|
|
button.innerHTML = originalHTML; |
|
|
button.classList.remove('bg-green-500', 'hover:bg-green-600'); |
|
|
button.classList.add('bg-blue-500', 'hover:bg-blue-600'); |
|
|
button.disabled = false; |
|
|
}, 2000); |
|
|
} catch (error) { |
|
|
console.error('Error processing video:', error); |
|
|
button.innerHTML = '<i class="fas fa-times mr-2"></i> Error'; |
|
|
button.classList.remove('bg-blue-500', 'hover:bg-blue-600'); |
|
|
button.classList.add('bg-red-500', 'hover:bg-red-600'); |
|
|
|
|
|
setTimeout(() => { |
|
|
button.innerHTML = originalHTML; |
|
|
button.classList.remove('bg-red-500', 'hover:bg-red-600'); |
|
|
button.classList.add('bg-blue-500', 'hover:bg-blue-600'); |
|
|
button.disabled = false; |
|
|
}, 2000); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function fetchFile(file) { |
|
|
return new Uint8Array(await readFileAsArrayBuffer(file)); |
|
|
} |
|
|
|
|
|
function readFileAsArrayBuffer(file) { |
|
|
return new Promise((resolve, reject) => { |
|
|
const reader = new FileReader(); |
|
|
reader.onload = () => resolve(reader.result); |
|
|
reader.onerror = reject; |
|
|
reader.readAsArrayBuffer(file); |
|
|
}); |
|
|
} |
|
|
}); |
|
|
</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=beppe1234/space" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> |
|
|
</html> |