Add-Video-Watermark / index.html
Open-Source-Lab's picture
Update index.html
11ad04c verified
<!-- https://poe.com/AddVideoWatermark-01
https://huggingface.co/spaces/Selfit/Remove-Watermark
https://huggingface.co/spaces/abdul9999/NoWatermark
-->
<html>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
primary: '#5D5CDE'
}
}
}
}
</script>
</head>
<body class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-6xl">
<div class="text-center mb-8">
<h1 class="text-4xl font-bold mb-2 bg-gradient-to-r from-primary to-purple-600 bg-clip-text text-transparent">
Add-Video-Watermark
</h1>
<p class="text-gray-600 dark:text-gray-400">Add custom text or image watermarks to your videos using canvas technology</p>
</div>
<div class="bg-gray-50 dark:bg-gray-800 rounded-xl p-6 mb-6">
<h2 class="text-xl font-semibold mb-4">1. Upload Video</h2>
<div class="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-8 text-center">
<input type="file" id="videoInput" accept="video/*" class="hidden">
<button onclick="document.getElementById('videoInput').click()" class="bg-primary text-white px-6 py-3 rounded-lg hover:bg-purple-600 transition-colors mb-2">
Choose Video File
</button>
<p class="text-sm text-gray-500 dark:text-gray-400">Supports MP4, WebM and other web-compatible formats</p>
<div id="videoInfo" class="mt-4 hidden">
<p class="text-sm font-medium text-green-600 dark:text-green-400"></p>
</div>
</div>
</div>
<div class="bg-gray-50 dark:bg-gray-800 rounded-xl p-6 mb-6" id="watermarkSection" style="display: none;">
<h2 class="text-xl font-semibold mb-4">2. Watermark Type</h2>
<div class="flex flex-wrap gap-4 mb-4">
<label class="flex items-center space-x-2 cursor-pointer">
<input type="radio" name="watermarkType" value="text" class="text-primary" checked>
<span>Text Watermark</span>
</label>
<label class="flex items-center space-x-2 cursor-pointer">
<input type="radio" name="watermarkType" value="image" class="text-primary">
<span>Image Watermark</span>
</label>
</div>
<div id="textOptions">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium mb-2">Watermark Text</label>
<input type="text" id="watermarkText" placeholder="Enter your watermark text" value="https://poe.com/Add-Video-Watermark" class="w-full px-3 py-2 text-base border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 focus:ring-2 focus:ring-primary focus:border-primary">
</div>
<div>
<label class="block text-sm font-medium mb-2">Font Size</label>
<input type="range" id="fontSize" min="12" max="72" value="24" class="w-full h-2 bg-gray-200 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer">
<div class="text-sm text-gray-500 dark:text-gray-400 text-center mt-1">
<span id="fontSizeValue">24</span>px
</div>
</div>
<div>
<label class="block text-sm font-medium mb-2">Text Color</label>
<input type="color" id="textColor" value="#ffffff" class="w-16 h-10 border border-gray-300 dark:border-gray-600 rounded cursor-pointer">
</div>
<div>
<label class="block text-sm font-medium mb-2">Opacity</label>
<input type="range" id="textOpacity" min="0.1" max="1" step="0.1" value="0.8" class="w-full h-2 bg-gray-200 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer">
<div class="text-sm text-gray-500 dark:text-gray-400 text-center mt-1">
<span id="textOpacityValue">80</span>%
</div>
</div>
</div>
</div>
<div id="imageOptions" class="hidden">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium mb-2">Watermark Image</label>
<input type="file" id="imageInput" accept="image/png,image/jpg,image/jpeg" class="w-full px-3 py-2 text-base border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 focus:ring-2 focus:ring-primary focus:border-primary">
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">PNG recommended for transparency</p>
</div>
<div>
<label class="block text-sm font-medium mb-2">Size (%)</label>
<input type="range" id="imageSize" min="5" max="50" value="15" class="w-full h-2 bg-gray-200 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer">
<div class="text-sm text-gray-500 dark:text-gray-400 text-center mt-1">
<span id="imageSizeValue">15</span>%
</div>
</div>
<div>
<label class="block text-sm font-medium mb-2">Opacity</label>
<input type="range" id="imageOpacity" min="0.1" max="1" step="0.1" value="0.8" class="w-full h-2 bg-gray-200 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer">
<div class="text-sm text-gray-500 dark:text-gray-400 text-center mt-1">
<span id="imageOpacityValue">80</span>%
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 dark:bg-gray-800 rounded-xl p-6 mb-6" id="previewSection" style="display: none;">
<h2 class="text-xl font-semibold mb-4">3. Position Preview</h2>
<div class="relative max-w-4xl mx-auto">
<video id="previewVideo" class="w-full rounded-lg shadow-lg" controls crossorigin="anonymous"></video>
<canvas id="previewCanvas" class="absolute top-0 left-0 w-full h-full pointer-events-none rounded-lg"></canvas>
</div>
<div class="mt-4 text-center">
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Click on the video to position your watermark</p>
<div class="flex flex-wrap justify-center gap-2">
<button onclick="setPosition('top-left')" class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">Top Left</button>
<button onclick="setPosition('top-right')" class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">Top Right</button>
<button onclick="setPosition('bottom-left')" class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">Bottom Left</button>
<button onclick="setPosition('bottom-right')" class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">Bottom Right</button>
<button onclick="setPosition('center')" class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">Center</button>
</div>
</div>
</div>
<div class="bg-gray-50 dark:bg-gray-800 rounded-xl p-6" id="processSection" style="display: none;">
<h2 class="text-xl font-semibold mb-4">4. Process Video</h2>
<!-- Export Format Selection -->
<div class="mb-6">
<h3 class="text-lg font-medium mb-3">Export Format</h3>
<div class="flex flex-wrap gap-4">
<label class="flex items-center space-x-2 cursor-pointer">
<input type="radio" name="exportFormat" value="webm" class="text-primary" checked>
<span>WebM (Free)</span>
</label>
<label class="flex items-center space-x-2 cursor-pointer" id="mp4Option">
<input type="radio" name="exportFormat" value="mp4" class="text-primary" disabled>
<span class="text-gray-400">MP4 (Premium - Unlock Required)</span>
</label>
</div>
<div id="unlockSection" class="mt-4 p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-yellow-800 dark:text-yellow-200">Unlock MP4 Export</p>
<p class="text-xs text-yellow-600 dark:text-yellow-300">Get premium features with @Cheap-Unlock-Bot</p>
</div>
<button id="unlockBtn" onclick="unlockMP4Feature()" class="bg-yellow-600 text-white px-4 py-2 rounded hover:bg-yellow-700 transition-colors text-sm">
Unlock MP4
</button>
</div>
<div id="unlockStatus" class="mt-2 hidden">
<p class="text-sm text-yellow-700 dark:text-yellow-300">Contacting @Cheap-Unlock-Bot...</p>
</div>
</div>
<div id="unlockedSection" class="mt-4 p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg hidden">
<div class="flex items-center">
<svg class="w-5 h-5 text-green-600 dark:text-green-400 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
</svg>
<p class="text-sm font-medium text-green-800 dark:text-green-200">MP4 Export Unlocked!</p>
</div>
</div>
</div>
<div class="text-center">
<button id="processBtn" onclick="processVideo()" class="bg-primary text-white px-8 py-3 rounded-lg hover:bg-purple-600 transition-colors text-lg font-medium">
Add Watermark for free
</button>
<div id="progressContainer" class="mt-4 hidden">
<div class="bg-gray-200 dark:bg-gray-700 rounded-full h-2 mb-2">
<div id="progressBar" class="bg-primary h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
</div>
<p id="progressText" class="text-sm text-gray-600 dark:text-gray-400">Starting...</p>
</div>
<div id="downloadContainer" class="mt-4 hidden">
<a id="downloadLink" class="inline-block bg-green-600 text-white px-6 py-3 rounded-lg hover:bg-green-700 transition-colors">
Download Watermarked Video
</a>
</div>
</div>
</div>
</div>
<div id="modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg max-w-sm w-full mx-4">
<p id="modalText" class="text-gray-700 dark:text-gray-300 mb-4"></p>
<div class="flex justify-end">
<button onclick="closeModal()" class="px-4 py-2 bg-primary text-white hover:bg-purple-600 rounded">OK</button>
</div>
</div>
</div>
<script>
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
if (event.matches) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
});
let currentVideo = null;
let watermarkPosition = { x: 50, y: 50 };
let watermarkImage = null;
let previewCanvas = null;
let previewCtx = null;
let mp4Unlocked = false;
function showModal(message) {
document.getElementById('modalText').textContent = message;
document.getElementById('modal').classList.remove('hidden');
}
function closeModal() {
document.getElementById('modal').classList.add('hidden');
}
// Register handler for unlock bot response
if (window.Poe && window.Poe.registerHandler) {
window.Poe.registerHandler("unlock-handler", (result, context) => {
const unlockStatus = document.getElementById('unlockStatus');
const unlockBtn = document.getElementById('unlockBtn');
if (result.responses && result.responses.length > 0) {
const response = result.responses[0];
if (response.status === "complete") {
const responseText = response.content.toLowerCase();
if (responseText.includes('cheapunl0ck')) {
// Unlock successful
mp4Unlocked = true;
document.getElementById('unlockSection').classList.add('hidden');
document.getElementById('unlockedSection').classList.remove('hidden');
const mp4Radio = document.querySelector('input[name="exportFormat"][value="mp4"]');
const mp4Label = mp4Radio.parentElement.querySelector('span');
mp4Radio.disabled = false;
mp4Label.textContent = 'MP4 (Premium)';
mp4Label.classList.remove('text-gray-400');
mp4Label.classList.add('text-gray-900', 'dark:text-gray-100');
unlockStatus.innerHTML = '<p class="text-sm text-green-700 dark:text-green-300">✅ Successfully unlocked MP4 export!</p>';
} else {
// Unlock failed
unlockStatus.innerHTML = '<p class="text-sm text-red-700 dark:text-red-300">❌ Unlock failed. Please try again.</p>';
unlockBtn.disabled = false;
unlockBtn.textContent = 'Unlock MP4';
}
} else if (response.status === "error") {
unlockStatus.innerHTML = '<p class="text-sm text-red-700 dark:text-red-300">❌ Error contacting unlock bot.</p>';
unlockBtn.disabled = false;
unlockBtn.textContent = 'Unlock MP4';
}
} else {
unlockStatus.innerHTML = '<p class="text-sm text-red-700 dark:text-red-300">❌ No response from unlock bot.</p>';
unlockBtn.disabled = false;
unlockBtn.textContent = 'Unlock MP4';
}
});
}
async function unlockMP4Feature() {
const unlockBtn = document.getElementById('unlockBtn');
const unlockStatus = document.getElementById('unlockStatus');
if (!window.Poe || !window.Poe.sendUserMessage) {
showModal('Poe API not available. MP4 unlock feature requires the Poe environment.');
return;
}
unlockBtn.disabled = true;
unlockBtn.textContent = 'Unlocking...';
unlockStatus.classList.remove('hidden');
try {
await window.Poe.sendUserMessage(
"@Cheap-Unlock-Bot I want to unlock MP4 export feature for Video Watermarker",
{
handler: "unlock-handler",
stream: false,
openChat: false
}
);
} catch (error) {
console.error('Error contacting unlock bot:', error);
unlockStatus.innerHTML = '<p class="text-sm text-red-700 dark:text-red-300">❌ Failed to contact unlock bot.</p>';
unlockBtn.disabled = false;
unlockBtn.textContent = 'Unlock MP4';
}
}
document.getElementById('videoInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
currentVideo = file;
const url = URL.createObjectURL(file);
const video = document.getElementById('previewVideo');
video.src = url;
document.getElementById('videoInfo').classList.remove('hidden');
document.getElementById('videoInfo').querySelector('p').textContent =
`Selected: ${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)`;
document.getElementById('watermarkSection').style.display = 'block';
document.getElementById('previewSection').style.display = 'block';
document.getElementById('processSection').style.display = 'block';
previewCanvas = document.getElementById('previewCanvas');
previewCtx = previewCanvas.getContext('2d');
video.addEventListener('click', function(e) {
const rect = video.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
const y = ((e.clientY - rect.top) / rect.height) * 100;
watermarkPosition.x = Math.max(0, Math.min(95, x));
watermarkPosition.y = Math.max(0, Math.min(95, y));
drawWatermarkPreview();
});
video.addEventListener('loadedmetadata', function() {
resizeCanvas();
drawWatermarkPreview();
});
video.addEventListener('timeupdate', drawWatermarkPreview);
}
});
function resizeCanvas() {
const video = document.getElementById('previewVideo');
const canvas = document.getElementById('previewCanvas');
const rect = video.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
canvas.style.width = rect.width + 'px';
canvas.style.height = rect.height + 'px';
}
document.addEventListener('change', function(e) {
if (e.target.name === 'watermarkType') {
const textOptions = document.getElementById('textOptions');
const imageOptions = document.getElementById('imageOptions');
if (e.target.value === 'text') {
textOptions.classList.remove('hidden');
imageOptions.classList.add('hidden');
} else {
textOptions.classList.add('hidden');
imageOptions.classList.remove('hidden');
}
drawWatermarkPreview();
}
});
document.getElementById('imageInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const img = new Image();
img.onload = function() {
watermarkImage = img;
drawWatermarkPreview();
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
});
document.getElementById('fontSize').addEventListener('input', function(e) {
document.getElementById('fontSizeValue').textContent = e.target.value;
drawWatermarkPreview();
});
document.getElementById('textOpacity').addEventListener('input', function(e) {
document.getElementById('textOpacityValue').textContent = Math.round(e.target.value * 100);
drawWatermarkPreview();
});
document.getElementById('imageSize').addEventListener('input', function(e) {
document.getElementById('imageSizeValue').textContent = e.target.value;
drawWatermarkPreview();
});
document.getElementById('imageOpacity').addEventListener('input', function(e) {
document.getElementById('imageOpacityValue').textContent = Math.round(e.target.value * 100);
drawWatermarkPreview();
});
document.getElementById('watermarkText').addEventListener('input', drawWatermarkPreview);
document.getElementById('textColor').addEventListener('input', drawWatermarkPreview);
function setPosition(position) {
switch(position) {
case 'top-left':
watermarkPosition = { x: 5, y: 5 };
break;
case 'top-right':
watermarkPosition = { x: 85, y: 5 };
break;
case 'bottom-left':
watermarkPosition = { x: 5, y: 85 };
break;
case 'bottom-right':
watermarkPosition = { x: 85, y: 85 };
break;
case 'center':
watermarkPosition = { x: 45, y: 45 };
break;
}
drawWatermarkPreview();
}
function drawWatermarkPreview() {
if (!previewCtx) return;
const canvas = previewCanvas;
const ctx = previewCtx;
const watermarkType = document.querySelector('input[name="watermarkType"]:checked');
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (!watermarkType) return;
const x = (watermarkPosition.x / 100) * canvas.width;
const y = (watermarkPosition.y / 100) * canvas.height;
if (watermarkType.value === 'text') {
const text = document.getElementById('watermarkText').value || 'Sample Text';
const fontSize = parseInt(document.getElementById('fontSize').value);
const color = document.getElementById('textColor').value;
const opacity = parseFloat(document.getElementById('textOpacity').value);
const scaledFontSize = fontSize * (canvas.width / 800);
ctx.font = `bold ${scaledFontSize}px Arial`;
ctx.fillStyle = color;
ctx.globalAlpha = opacity;
ctx.shadowColor = 'black';
ctx.shadowBlur = 4;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.fillText(text, x, y + scaledFontSize);
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.globalAlpha = 1;
} else if (watermarkType.value === 'image' && watermarkImage) {
const size = parseInt(document.getElementById('imageSize').value);
const opacity = parseFloat(document.getElementById('imageOpacity').value);
const imgWidth = (size / 100) * canvas.width;
const imgHeight = (watermarkImage.height / watermarkImage.width) * imgWidth;
ctx.globalAlpha = opacity;
ctx.drawImage(watermarkImage, x, y, imgWidth, imgHeight);
ctx.globalAlpha = 1;
}
}
async function processVideo() {
if (!currentVideo) {
showModal('Please select a video file first.');
return;
}
const watermarkType = document.querySelector('input[name="watermarkType"]:checked');
if (!watermarkType) {
showModal('Please select a watermark type.');
return;
}
if (watermarkType.value === 'text' && !document.getElementById('watermarkText').value) {
showModal('Please enter watermark text.');
return;
}
if (watermarkType.value === 'image' && !watermarkImage) {
showModal('Please select a watermark image.');
return;
}
const exportFormat = document.querySelector('input[name="exportFormat"]:checked');
if (!exportFormat) {
showModal('Please select an export format.');
return;
}
if (exportFormat.value === 'mp4' && !mp4Unlocked) {
showModal('Please unlock MP4 export first using the unlock button.');
return;
}
const processBtn = document.getElementById('processBtn');
const progressContainer = document.getElementById('progressContainer');
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
processBtn.disabled = true;
processBtn.textContent = 'Processing...';
progressContainer.classList.remove('hidden');
try {
progressText.textContent = 'Preparing video...';
progressBar.style.width = '10%';
const video = document.createElement('video');
video.src = URL.createObjectURL(currentVideo);
video.crossOrigin = 'anonymous';
await new Promise((resolve) => {
video.addEventListener('loadedmetadata', resolve);
video.load();
});
progressText.textContent = 'Setting up recording...';
progressBar.style.width = '20%';
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const videoStream = canvas.captureStream(30);
let combinedStream = videoStream;
try {
const originalStream = video.captureStream();
const audioTracks = originalStream.getAudioTracks();
if (audioTracks.length > 0) {
combinedStream = new MediaStream([
...videoStream.getVideoTracks(),
...audioTracks
]);
progressText.textContent = 'Recording with audio...';
} else {
progressText.textContent = 'Recording video (no audio track found)...';
}
} catch (audioError) {
console.warn('Could not capture audio:', audioError);
progressText.textContent = 'Recording video (audio capture failed)...';
}
// Determine MIME type based on selected format
let mimeType;
let fileExtension;
if (exportFormat.value === 'mp4') {
// Try MP4 format if unlocked
if (MediaRecorder.isTypeSupported('video/mp4;codecs=h264')) {
mimeType = 'video/mp4;codecs=h264';
fileExtension = 'mp4';
} else if (MediaRecorder.isTypeSupported('video/mp4')) {
mimeType = 'video/mp4';
fileExtension = 'mp4';
} else {
// Fall back to WebM if MP4 not supported
mimeType = 'video/webm;codecs=vp9';
fileExtension = 'webm';
progressText.textContent = 'MP4 not supported by browser, using WebM...';
}
} else {
// Default to WebM
mimeType = 'video/webm;codecs=vp9';
fileExtension = 'webm';
}
const recorder = new MediaRecorder(combinedStream, { mimeType });
const chunks = [];
recorder.ondataavailable = (e) => chunks.push(e.data);
progressBar.style.width = '40%';
recorder.start();
video.play();
const drawFrame = () => {
if (video.ended || video.paused) {
recorder.stop();
return;
}
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const x = (watermarkPosition.x / 100) * canvas.width;
const y = (watermarkPosition.y / 100) * canvas.height;
if (watermarkType.value === 'text') {
const text = document.getElementById('watermarkText').value;
const fontSize = parseInt(document.getElementById('fontSize').value);
const color = document.getElementById('textColor').value;
const opacity = parseFloat(document.getElementById('textOpacity').value);
const scaledFontSize = fontSize * (canvas.width / 800);
ctx.font = `bold ${scaledFontSize}px Arial`;
ctx.fillStyle = color;
ctx.globalAlpha = opacity;
ctx.shadowColor = 'black';
ctx.shadowBlur = 4;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.fillText(text, x, y + scaledFontSize);
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.globalAlpha = 1;
} else if (watermarkType.value === 'image' && watermarkImage) {
const size = parseInt(document.getElementById('imageSize').value);
const opacity = parseFloat(document.getElementById('imageOpacity').value);
const imgWidth = (size / 100) * canvas.width;
const imgHeight = (watermarkImage.height / watermarkImage.width) * imgWidth;
ctx.globalAlpha = opacity;
ctx.drawImage(watermarkImage, x, y, imgWidth, imgHeight);
ctx.globalAlpha = 1;
}
const progress = (video.currentTime / video.duration) * 50;
progressBar.style.width = (40 + progress) + '%';
requestAnimationFrame(drawFrame);
};
drawFrame();
recorder.onstop = () => {
progressText.textContent = 'Finalizing video...';
progressBar.style.width = '95%';
const blob = new Blob(chunks, { type: mimeType });
const url = URL.createObjectURL(blob);
progressText.textContent = 'Complete!';
progressBar.style.width = '100%';
const downloadLink = document.getElementById('downloadLink');
downloadLink.href = url;
downloadLink.download = `watermarked_${currentVideo.name.replace(/\.[^/.]+$/, '')}.${fileExtension}`;
document.getElementById('downloadContainer').classList.remove('hidden');
};
} catch (error) {
console.error('Processing error:', error);
showModal('Error processing video: ' + error.message);
} finally {
processBtn.disabled = false;
processBtn.textContent = 'Add Watermark & Download';
}
}
window.addEventListener('resize', () => {
if (previewCanvas) {
setTimeout(() => {
resizeCanvas();
drawWatermarkPreview();
}, 100);
}
});
</script>
</html>