vidcuttrim / index.html
beatbox1200's picture
error processing video, shared array buffer is not defined, please find out whats wrong with the video processing and fix it please. - Initial Deployment
b2c4dd7 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Video Cutter & Trimmer | HuggingFace Space</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">
<script src="https://cdn.jsdelivr.net/npm/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
hf: {
purple: '#7b3ae1',
dark: '#0b0f19',
light: '#f5f5f7',
}
}
}
}
}
</script>
<style>
.video-container {
aspect-ratio: 16/9;
}
.timeline-handle {
width: 12px;
height: 20px;
top: -8px;
}
.timeline-handle::after {
content: '';
position: absolute;
width: 2px;
height: 10px;
background: white;
top: 5px;
left: 5px;
}
.progress-bar {
height: 4px;
}
@media (max-width: 768px) {
.controls-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>
</head>
<body class="bg-hf-dark text-hf-light min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-6xl">
<!-- Header -->
<header class="flex flex-col md:flex-row justify-between items-center mb-8 gap-4">
<div class="flex items-center gap-3">
<div class="bg-hf-purple p-2 rounded-lg">
<i class="fas fa-video text-white text-xl"></i>
</div>
<h1 class="text-2xl font-bold">Video Cutter & Trimmer</h1>
</div>
<div class="flex gap-2">
<button class="bg-hf-purple hover:bg-purple-800 px-4 py-2 rounded-lg flex items-center gap-2 transition">
<i class="fas fa-share"></i> Share
</button>
<button class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg flex items-center gap-2 transition">
<i class="fas fa-star"></i> Like
</button>
</div>
</header>
<!-- Main Content -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Left Panel - Upload & Settings -->
<div class="lg:col-span-1 bg-gray-800 rounded-xl p-6">
<h2 class="text-xl font-semibold mb-4 flex items-center gap-2">
<i class="fas fa-upload"></i> Upload Video
</h2>
<!-- File Upload -->
<div class="border-2 border-dashed border-gray-600 rounded-lg p-6 text-center mb-6 cursor-pointer hover:border-hf-purple transition"
id="dropZone">
<input type="file" id="fileInput" class="hidden" accept=".mp4,.avi,.mkv,.mov">
<div class="flex flex-col items-center justify-center">
<i class="fas fa-cloud-upload-alt text-4xl text-hf-purple mb-3"></i>
<p class="mb-2">Drag & drop your video file here</p>
<p class="text-sm text-gray-400 mb-3">or</p>
<button class="bg-hf-purple hover:bg-purple-800 px-4 py-2 rounded-lg transition">
Browse Files
</button>
</div>
<p class="text-xs text-gray-400 mt-3">Supports: MP4, AVI, MKV, MOV</p>
</div>
<!-- Video Info -->
<div id="videoInfo" class="hidden mb-6">
<div class="flex justify-between items-center mb-2">
<h3 class="font-medium">File Info</h3>
<button id="clearFile" class="text-red-400 hover:text-red-300 text-sm">
<i class="fas fa-times"></i> Clear
</button>
</div>
<div class="bg-gray-700 rounded-lg p-3">
<div class="flex justify-between mb-1">
<span class="text-gray-400">Name:</span>
<span id="fileName" class="font-mono">video.mp4</span>
</div>
<div class="flex justify-between mb-1">
<span class="text-gray-400">Size:</span>
<span id="fileSize" class="font-mono">24.5 MB</span>
</div>
<div class="flex justify-between mb-1">
<span class="text-gray-400">Duration:</span>
<span id="fileDuration" class="font-mono">02:45</span>
</div>
<div class="flex justify-between">
<span class="text-gray-400">Resolution:</span>
<span id="fileResolution" class="font-mono">1920x1080</span>
</div>
</div>
</div>
<!-- Output Settings -->
<div class="mb-6">
<h3 class="font-medium mb-3 flex items-center gap-2">
<i class="fas fa-cog"></i> Output Settings
</h3>
<div class="space-y-4">
<div>
<label class="block text-sm text-gray-400 mb-1">Format</label>
<select class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2">
<option value="mp4">MP4 (Default)</option>
<option value="avi">AVI</option>
<option value="mkv">MKV</option>
<option value="mov">MOV</option>
</select>
</div>
<div>
<label class="block text-sm text-gray-400 mb-1">Quality</label>
<select class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2">
<option value="original">Original (No compression)</option>
<option value="high">High (90% quality)</option>
<option value="medium">Medium (75% quality)</option>
<option value="low">Low (50% quality)</option>
</select>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" id="preserveAudio" checked class="rounded bg-gray-700">
<label for="preserveAudio" class="text-sm">Preserve original audio quality</label>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="space-y-3">
<button id="processBtn" disabled class="w-full bg-hf-purple hover:bg-purple-800 py-3 rounded-lg font-medium flex items-center justify-center gap-2 transition opacity-50">
<i class="fas fa-cut"></i> Process Video
</button>
<button id="downloadBtn" disabled class="w-full bg-gray-700 hover:bg-gray-600 py-3 rounded-lg font-medium flex items-center justify-center gap-2 transition opacity-50">
<i class="fas fa-download"></i> Download
</button>
</div>
</div>
<!-- Right Panel - Video Preview & Editor -->
<div class="lg:col-span-2 space-y-6">
<!-- Video Preview -->
<div class="bg-gray-800 rounded-xl p-6">
<h2 class="text-xl font-semibold mb-4 flex items-center gap-2">
<i class="fas fa-play"></i> Preview
</h2>
<div class="video-container bg-black rounded-lg overflow-hidden relative" id="videoContainer">
<video id="videoPlayer" class="w-full h-full object-contain" controls></video>
<div id="noVideo" class="absolute inset-0 flex items-center justify-center">
<div class="text-center p-6">
<i class="fas fa-video-slash text-4xl text-gray-600 mb-3"></i>
<p class="text-gray-400">No video loaded</p>
</div>
</div>
</div>
</div>
<!-- Timeline Editor -->
<div class="bg-gray-800 rounded-xl p-6">
<h2 class="text-xl font-semibold mb-4 flex items-center gap-2">
<i class="fas fa-sliders-h"></i> Editor
</h2>
<!-- Trim Controls -->
<div class="mb-6">
<h3 class="font-medium mb-3">Trim Video</h3>
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<label class="block text-sm text-gray-400 mb-1">Start Time</label>
<div class="flex gap-2">
<input type="number" min="0" value="0" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2" id="startMin">
<span class="flex items-center">:</span>
<input type="number" min="0" max="59" value="0" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2" id="startSec">
</div>
</div>
<div>
<label class="block text-sm text-gray-400 mb-1">End Time</label>
<div class="flex gap-2">
<input type="number" min="0" value="0" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2" id="endMin">
<span class="flex items-center">:</span>
<input type="number" min="0" max="59" value="0" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2" id="endSec">
</div>
</div>
</div>
</div>
<!-- Timeline Visualization -->
<div class="mb-6">
<div class="flex justify-between mb-2">
<span id="currentTime">00:00</span>
<span id="totalDuration">00:00</span>
</div>
<div class="relative h-2 bg-gray-700 rounded-full overflow-hidden mb-2">
<div class="absolute top-0 left-0 h-full bg-hf-purple" id="progressBar"></div>
</div>
<div class="relative h-8 bg-gray-700 rounded-lg overflow-hidden">
<div class="absolute inset-0 flex items-center">
<div class="h-4 w-full bg-gray-600 rounded" id="timelineBg"></div>
</div>
<div class="absolute inset-0 flex items-center">
<div class="h-4 bg-hf-purple rounded" id="timelineSelection"></div>
</div>
<div class="absolute timeline-handle bg-hf-purple rounded cursor-move" id="startHandle"></div>
<div class="absolute timeline-handle bg-hf-purple rounded cursor-move" id="endHandle"></div>
</div>
</div>
<!-- Crop Controls -->
<div class="mb-6">
<h3 class="font-medium mb-3">Crop Video</h3>
<div class="flex flex-wrap gap-3 mb-4">
<button class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg flex items-center gap-2 transition crop-btn" data-ratio="original">
<i class="fas fa-expand"></i> Original
</button>
<button class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg flex items-center gap-2 transition crop-btn" data-ratio="16:9">
<i class="fas fa-expand"></i> 16:9
</button>
<button class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg flex items-center gap-2 transition crop-btn" data-ratio="4:3">
<i class="fas fa-expand"></i> 4:3
</button>
<button class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg flex items-center gap-2 transition crop-btn" data-ratio="1:1">
<i class="fas fa-expand"></i> Square (1:1)
</button>
</div>
<div class="hidden" id="cropOverlayContainer">
<div class="relative border-2 border-hf-purple rounded-lg overflow-hidden" id="cropOverlay"></div>
<div class="flex justify-between mt-2">
<span class="text-sm text-gray-400">Drag edges to adjust crop</span>
<button id="applyCrop" class="text-sm text-hf-purple hover:text-purple-300">Apply Crop</button>
</div>
</div>
</div>
<!-- Trim Action Buttons -->
<div class="flex flex-wrap gap-3 mt-6">
<button id="setStartBtn" class="bg-hf-purple hover:bg-purple-800 px-4 py-2 rounded-lg flex items-center gap-2 transition">
<i class="fas fa-flag"></i> Set Start Time Here
</button>
<button id="setEndBtn" class="bg-hf-purple hover:bg-purple-800 px-4 py-2 rounded-lg flex items-center gap-2 transition">
<i class="fas fa-flag-checkered"></i> Set End Time Here
</button>
</div>
</div>
<!-- Output Preview (hidden by default) -->
<div id="outputPreview" class="bg-gray-800 rounded-xl p-6 hidden">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold flex items-center gap-2">
<i class="fas fa-check-circle"></i> Processed Video Preview
</h2>
<div class="flex gap-2">
<button id="backToEditBtn" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg flex items-center gap-2 transition">
<i class="fas fa-edit"></i> Back to Editing
</button>
<button id="startOverBtn" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg flex items-center gap-2 transition">
<i class="fas fa-redo"></i> Start Over
</button>
</div>
</div>
<div class="mb-4 bg-gray-700 rounded-lg p-3">
<div class="flex justify-between mb-1">
<span class="text-gray-400">Trimmed Duration:</span>
<span id="processedDuration" class="font-mono">00:00</span>
</div>
<div class="flex justify-between">
<span class="text-gray-400">Output Resolution:</span>
<span id="processedResolution" class="font-mono">1920x1080</span>
</div>
</div>
<div class="video-container bg-black rounded-lg overflow-hidden mb-4">
<video id="outputVideo" class="w-full h-full" controls></video>
</div>
<div class="flex flex-col sm:flex-row gap-3">
<button id="finalDownloadBtn" disabled class="flex-1 bg-hf-purple hover:bg-purple-800 py-3 rounded-lg font-medium flex items-center justify-center gap-2 transition opacity-50">
<i class="fas fa-download"></i> Download Now
</button>
<button id="startOverBtn" class="flex-1 bg-gray-700 hover:bg-gray-600 py-3 rounded-lg font-medium flex items-center justify-center gap-2 transition">
<i class="fas fa-redo"></i> Process Again
</button>
</div>
</div>
</div>
</div>
<!-- Footer -->
<footer class="mt-12 text-center text-gray-400 text-sm">
<p>Video Cutter & Trimmer - A HuggingFace Space Project</p>
<p class="mt-1">Supports MP4, AVI, MKV, MOV formats with original quality preservation</p>
</footer>
</div>
<script>
// DOM Elements
const fileInput = document.getElementById('fileInput');
const dropZone = document.getElementById('dropZone');
const videoPlayer = document.getElementById('videoPlayer');
const noVideo = document.getElementById('noVideo');
const videoContainer = document.getElementById('videoContainer');
const videoInfo = document.getElementById('videoInfo');
const fileName = document.getElementById('fileName');
const fileSize = document.getElementById('fileSize');
const fileDuration = document.getElementById('fileDuration');
const fileResolution = document.getElementById('fileResolution');
const clearFile = document.getElementById('clearFile');
const processBtn = document.getElementById('processBtn');
const downloadBtn = document.getElementById('downloadBtn');
const progressBar = document.getElementById('progressBar');
const timelineBg = document.getElementById('timelineBg');
const timelineSelection = document.getElementById('timelineSelection');
const startHandle = document.getElementById('startHandle');
const endHandle = document.getElementById('endHandle');
const currentTime = document.getElementById('currentTime');
const totalDuration = document.getElementById('totalDuration');
const startMin = document.getElementById('startMin');
const startSec = document.getElementById('startSec');
const endMin = document.getElementById('endMin');
const endSec = document.getElementById('endSec');
// Variables
let videoFile = null;
let isDragging = false;
let activeHandle = null;
let videoDuration = 0;
let currentCropRatio = 'original';
let cropOverlay = null;
let isCropping = false;
// Event Listeners
dropZone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', handleFileSelect);
dropZone.addEventListener('dragover', handleDragOver);
dropZone.addEventListener('dragleave', handleDragLeave);
dropZone.addEventListener('drop', handleDrop);
clearFile.addEventListener('click', clearVideo);
videoPlayer.addEventListener('timeupdate', updateProgress);
videoPlayer.addEventListener('loadedmetadata', setupVideo);
startHandle.addEventListener('mousedown', startDrag);
endHandle.addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', handleDrag);
document.addEventListener('mouseup', stopDrag);
startMin.addEventListener('change', updateFromTimeInputs);
startSec.addEventListener('change', updateFromTimeInputs);
endMin.addEventListener('change', updateFromTimeInputs);
endSec.addEventListener('change', updateFromTimeInputs);
// New DOM elements
const setStartBtn = document.getElementById('setStartBtn');
const setEndBtn = document.getElementById('setEndBtn');
const outputPreview = document.getElementById('outputPreview');
const outputVideo = document.getElementById('outputVideo');
const backToEditBtn = document.getElementById('backToEditBtn');
const finalDownloadBtn = document.getElementById('finalDownloadBtn');
const startOverBtn = document.getElementById('startOverBtn');
// Event listeners
setStartBtn.addEventListener('click', () => setTrimPoint('start'));
setEndBtn.addEventListener('click', () => setTrimPoint('end'));
backToEditBtn.addEventListener('click', () => {
outputPreview.classList.add('hidden');
});
startOverBtn.addEventListener('click', clearVideo);
finalDownloadBtn.addEventListener('click', downloadProcessedVideo);
processBtn.addEventListener('click', processVideo);
// Crop event listeners
document.querySelectorAll('.crop-btn').forEach(btn => {
btn.addEventListener('click', handleCropSelection);
});
// Functions
function handleFileSelect(e) {
const file = e.target.files[0];
if (file && isValidFile(file)) {
loadVideo(file);
}
}
function handleDragOver(e) {
e.preventDefault();
dropZone.classList.add('border-hf-purple');
}
function handleDragLeave() {
dropZone.classList.remove('border-hf-purple');
}
function handleDrop(e) {
e.preventDefault();
dropZone.classList.remove('border-hf-purple');
const file = e.dataTransfer.files[0];
if (file && isValidFile(file)) {
loadVideo(file);
}
}
function isValidFile(file) {
const validTypes = ['video/mp4', 'video/quicktime', 'video/x-msvideo', 'video/x-matroska'];
const validExtensions = ['.mp4', '.avi', '.mkv', '.mov'];
const extension = file.name.substring(file.name.lastIndexOf('.')).toLowerCase();
return validTypes.includes(file.type) || validExtensions.includes(extension);
}
function loadVideo(file) {
videoFile = file;
const url = URL.createObjectURL(file);
videoPlayer.src = url;
// Show video info
fileName.textContent = file.name;
fileSize.textContent = formatFileSize(file.size);
// Show video elements
noVideo.classList.add('hidden');
videoInfo.classList.remove('hidden');
processBtn.disabled = false;
processBtn.classList.remove('opacity-50');
// Wait for metadata to be loaded
if (videoPlayer.readyState > 0) {
setupVideo();
}
}
function setupVideo() {
videoDuration = videoPlayer.duration;
fileDuration.textContent = formatTime(videoDuration);
totalDuration.textContent = formatTime(videoDuration);
// Set end time inputs
const endMinutes = Math.floor(videoDuration / 60);
const endSeconds = Math.floor(videoDuration % 60);
endMin.value = endMinutes;
endSec.value = endSeconds;
// Setup timeline
setupTimeline();
}
function setupTimeline() {
timelineBg.style.width = '100%';
// Position handles
startHandle.style.left = '0%';
endHandle.style.left = '100%';
// Initial selection
updateSelection();
}
function updateSelection() {
const startPos = parseFloat(startHandle.style.left) / 100;
const endPos = parseFloat(endHandle.style.left) / 100;
timelineSelection.style.left = `${startPos * 100}%`;
timelineSelection.style.width = `${(endPos - startPos) * 100}%`;
// Update time inputs
const startTime = startPos * videoDuration;
const endTime = endPos * videoDuration;
const startMinutes = Math.floor(startTime / 60);
const startSeconds = Math.floor(startTime % 60);
startMin.value = startMinutes;
startSec.value = startSeconds;
const endMinutes = Math.floor(endTime / 60);
const endSeconds = Math.floor(endTime % 60);
endMin.value = endMinutes;
endSec.value = endSeconds;
}
function updateFromTimeInputs() {
const startTime = (parseInt(startMin.value) || 0) * 60 + (parseInt(startSec.value) || 0);
const endTime = (parseInt(endMin.value) || 0) * 60 + (parseInt(endSec.value) || 0);
// Validate times
if (startTime < 0) startTime = 0;
if (endTime > videoDuration) endTime = videoDuration;
if (startTime > endTime) startTime = endTime;
// Update handles
startHandle.style.left = `${(startTime / videoDuration) * 100}%`;
endHandle.style.left = `${(endTime / videoDuration) * 100}%`;
updateSelection();
// Seek video if playing
if (!videoPlayer.paused) {
videoPlayer.currentTime = startTime;
}
}
function startDrag(e) {
isDragging = true;
activeHandle = e.target;
e.preventDefault();
}
function handleDrag(e) {
if (!isDragging) return;
const rect = timelineBg.getBoundingClientRect();
let pos = (e.clientX - rect.left) / rect.width;
// Constrain position
pos = Math.max(0, Math.min(1, pos));
if (activeHandle === startHandle) {
const endPos = parseFloat(endHandle.style.left) / 100;
pos = Math.min(pos, endPos - 0.01); // Small offset to prevent overlap
activeHandle.style.left = `${pos * 100}%`;
} else if (activeHandle === endHandle) {
const startPos = parseFloat(startHandle.style.left) / 100;
pos = Math.max(pos, startPos + 0.01); // Small offset to prevent overlap
activeHandle.style.left = `${pos * 100}%`;
}
updateSelection();
}
function stopDrag() {
isDragging = false;
activeHandle = null;
}
function setTrimPoint(type) {
const currentTime = videoPlayer.currentTime;
const percentage = (currentTime / videoDuration) * 100;
if (type === 'start') {
startHandle.style.left = `${percentage}%`;
startMin.value = Math.floor(currentTime / 60);
startSec.value = Math.floor(currentTime % 60);
} else {
endHandle.style.left = `${percentage}%`;
endMin.value = Math.floor(currentTime / 60);
endSec.value = Math.floor(currentTime % 60);
}
updateSelection();
}
// Connect process button
processBtn.addEventListener('click', processVideo);
// Processing state variables
let processingInterval;
let processingProgress = 0;
const processingMessages = [
"Preparing video for processing...",
"Analyzing video metadata...",
"Trimming video segment (${startTime}s to ${endTime}s)...",
"Applying crop settings (${currentCropRatio})...",
"Encoding video output...",
"Finalizing processed video..."
];
async function processVideo() {
if (!videoFile) {
alert('Please upload a video first');
return;
}
// Check for browser support
if (typeof SharedArrayBuffer === 'undefined') {
alert('Your browser doesn\'t support required features for video processing. Please try a modern browser like Chrome or Firefox.');
return;
}
// Show processing overlay
const processingOverlay = document.createElement('div');
processingOverlay.className = 'fixed inset-0 bg-black bg-opacity-75 flex flex-col items-center justify-center z-50';
processingOverlay.innerHTML = `
<div class="bg-gray-800 rounded-xl p-8 max-w-md w-full">
<h3 class="text-xl font-semibold mb-4">Processing Video</h3>
<div class="mb-2 text-sm" id="processingMessage">Initializing FFmpeg...</div>
<div class="w-full bg-gray-700 rounded-full h-2.5 mb-4">
<div id="processingBar" class="bg-hf-purple h-2.5 rounded-full" style="width: 0%"></div>
</div>
<div class="flex justify-between text-xs text-gray-400">
<span id="processingPercent">0%</span>
<span id="processingTime">Estimated time: calculating...</span>
</div>
</div>
`;
document.body.appendChild(processingOverlay);
try {
const { createFFmpeg, fetchFile } = FFmpeg;
const ffmpeg = createFFmpeg({
log: true,
corePath: 'https://unpkg.com/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js',
progress: ({ ratio }) => {
const percent = Math.round(ratio * 100);
document.getElementById('processingBar').style.width = `${percent}%`;
document.getElementById('processingPercent').textContent = `${percent}%`;
document.getElementById('processingMessage').textContent =
percent < 30 ? "Loading FFmpeg..." :
percent < 60 ? "Processing video..." :
"Finalizing output...";
}
});
try {
await ffmpeg.load();
} catch (error) {
console.error('FFmpeg load error:', error);
processingOverlay.remove();
alert('Error: Your browser doesn\'t support required features for video processing. Please try a modern browser like Chrome or Firefox.');
return;
}
// Get trim times
const startTime = (parseInt(startMin.value) || 0) * 60 + (parseInt(startSec.value) || 0);
const endTime = (parseInt(endMin.value) || 0) * 60 + (parseInt(endSec.value) || 0);
const duration = endTime - startTime;
// Read input file
ffmpeg.FS('writeFile', 'input.mp4', await fetchFile(videoFile));
// Build FFmpeg command
let command = [
'-i', 'input.mp4',
'-ss', startTime.toString(),
'-t', duration.toString()
];
// Add crop if needed
if (currentCropRatio !== 'original') {
const [w, h] = currentCropRatio.split(':').map(Number);
command.push('-vf', `crop=in_w*${w}/${w+1}:in_h*${h}/${h+1}`);
}
command.push('-c:v', 'libx264', '-preset', 'fast', '-c:a', 'copy', 'output.mp4');
try {
// Run FFmpeg
await ffmpeg.run(...command);
// Get output
const data = ffmpeg.FS('readFile', 'output.mp4');
const blob = new Blob([data.buffer], { type: 'video/mp4' });
processedVideoBlob = blob;
const url = URL.createObjectURL(blob);
// Show processed video
processingOverlay.remove();
showProcessedVideo(url, duration);
} catch (error) {
console.error('FFmpeg processing error:', error);
processingOverlay.remove();
alert('Error processing video. Please try a different video or browser.');
return;
}
} catch (error) {
console.error('Error processing video:', error);
processingOverlay.remove();
alert('Error processing video: ' + error.message);
}
}
let processedVideoBlob = null;
function showProcessedVideo(url, duration) {
outputPreview.classList.remove('hidden');
// Create preview with actual processed video
const previewContainer = document.getElementById('outputVideo').parentNode;
previewContainer.innerHTML = `
<div class="relative w-full h-full">
<video id="outputVideo" class="w-full h-full" controls playsinline>
<source src="${url}" type="video/mp4">
</video>
</div>
`;
// Show trimmed duration
document.getElementById('processedDuration').textContent = formatTime(duration);
// Enable download button
finalDownloadBtn.disabled = false;
finalDownloadBtn.classList.remove('opacity-50');
// Scroll to output preview
outputPreview.scrollIntoView({ behavior: 'smooth' });
}
function downloadProcessedVideo() {
if (!processedVideoBlob) return;
const url = URL.createObjectURL(processedVideoBlob);
const a = document.createElement('a');
a.href = url;
a.download = `edited_${videoFile.name}`;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
// Show download complete message
const toast = document.createElement('div');
toast.className = 'fixed bottom-4 right-4 bg-green-600 text-white px-4 py-2 rounded-lg shadow-lg';
toast.textContent = 'Download started!';
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
}
function updateProgress() {
const currentTimeValue = videoPlayer.currentTime;
const duration = videoPlayer.duration;
// Update progress bar
progressBar.style.width = `${(currentTimeValue / duration) * 100}%`;
// Update current time display
currentTime.textContent = formatTime(currentTimeValue);
// Check if current time is outside selection
const startPos = parseFloat(startHandle.style.left) / 100;
const endPos = parseFloat(endHandle.style.left) / 100;
if (currentTimeValue < startPos * duration || currentTimeValue > endPos * duration) {
videoPlayer.pause();
videoPlayer.currentTime = startPos * duration;
}
}
function clearVideo() {
videoPlayer.src = '';
videoFile = null;
noVideo.classList.remove('hidden');
videoInfo.classList.add('hidden');
processBtn.disabled = true;
processBtn.classList.add('opacity-50');
downloadBtn.disabled = true;
downloadBtn.classList.add('opacity-50');
fileInput.value = '';
// Reset timeline
progressBar.style.width = '0%';
currentTime.textContent = '00:00';
totalDuration.textContent = '00:00';
timelineSelection.style.width = '0%';
}
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
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 handleCropSelection(e) {
const ratio = e.target.dataset.ratio;
currentCropRatio = ratio;
// Highlight selected button
document.querySelectorAll('.crop-btn').forEach(btn => {
btn.classList.remove('bg-hf-purple');
btn.classList.add('bg-gray-700');
});
e.target.classList.remove('bg-gray-700');
e.target.classList.add('bg-hf-purple');
if (ratio === 'original') {
document.getElementById('cropOverlayContainer').classList.add('hidden');
videoPlayer.style.objectFit = 'contain';
return;
}
// Show crop overlay
document.getElementById('cropOverlayContainer').classList.remove('hidden');
const container = document.getElementById('cropOverlay');
container.innerHTML = '';
// Create crop overlay based on ratio
const [w, h] = ratio.split(':').map(Number);
const aspectRatio = w / h;
// Create full-size overlay
cropOverlay = document.createElement('div');
cropOverlay.className = 'absolute inset-0 bg-black bg-opacity-50';
// Calculate and apply the crop mask
const containerWidth = container.offsetWidth;
const containerHeight = container.offsetHeight;
const containerAspect = containerWidth / containerHeight;
if (containerAspect > aspectRatio) {
// Container is wider than target aspect - crop sides
const newWidth = containerHeight * aspectRatio;
const sideMargin = (containerWidth - newWidth) / 2;
cropOverlay.style.left = `${sideMargin}px`;
cropOverlay.style.right = `${sideMargin}px`;
} else {
// Container is taller than target aspect - crop top/bottom
const newHeight = containerWidth / aspectRatio;
const vertMargin = (containerHeight - newHeight) / 2;
cropOverlay.style.top = `${vertMargin}px`;
cropOverlay.style.bottom = `${vertMargin}px`;
}
container.appendChild(cropOverlay);
// Store crop settings for processing
console.log('Auto-crop applied with ratio:', ratio);
}
</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=beatbox1200/vidcuttrim" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>