Open-Hat-Lab/Add-Video-Watermark
0
1<!-- https://poe.com/AddVideoWatermark-01 2https://huggingface.co/spaces/Selfit/Remove-Watermark3https://huggingface.co/spaces/abdul9999/NoWatermark4-->5<html>6<script src="https://cdn.tailwindcss.com"></script>7 <script>8 tailwind.config = {9 darkMode: 'class',10 theme: {11 extend: {12 colors: {13 primary: '#5D5CDE'14 }15 }16 }17 }18 </script>19</head>20<body class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen">21 <div class="container mx-auto px-4 py-8 max-w-6xl">22 <div class="text-center mb-8">23 <h1 class="text-4xl font-bold mb-2 bg-gradient-to-r from-primary to-purple-600 bg-clip-text text-transparent">24 Add-Video-Watermark25 </h1>26 <p class="text-gray-600 dark:text-gray-400">Add custom text or image watermarks to your videos using canvas technology</p>27 </div>28 29 <div class="bg-gray-50 dark:bg-gray-800 rounded-xl p-6 mb-6">30 <h2 class="text-xl font-semibold mb-4">1. Upload Video</h2>31 <div class="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-8 text-center">32 <input type="file" id="videoInput" accept="video/*" class="hidden">33 <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">34 Choose Video File35 </button>36 <p class="text-sm text-gray-500 dark:text-gray-400">Supports MP4, WebM and other web-compatible formats</p>37 <div id="videoInfo" class="mt-4 hidden">38 <p class="text-sm font-medium text-green-600 dark:text-green-400"></p>39 </div>40 </div>41 </div>42 43 <div class="bg-gray-50 dark:bg-gray-800 rounded-xl p-6 mb-6" id="watermarkSection" style="display: none;">44 <h2 class="text-xl font-semibold mb-4">2. Watermark Type</h2>45 <div class="flex flex-wrap gap-4 mb-4">46 <label class="flex items-center space-x-2 cursor-pointer">47 <input type="radio" name="watermarkType" value="text" class="text-primary" checked>48 <span>Text Watermark</span>49 </label>50 <label class="flex items-center space-x-2 cursor-pointer">51 <input type="radio" name="watermarkType" value="image" class="text-primary">52 <span>Image Watermark</span>53 </label>54 </div>55 56 <div id="textOptions">57 <div class="grid grid-cols-1 md:grid-cols-2 gap-4">58 <div>59 <label class="block text-sm font-medium mb-2">Watermark Text</label>60 <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">61 </div>62 <div>63 <label class="block text-sm font-medium mb-2">Font Size</label>64 <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">65 <div class="text-sm text-gray-500 dark:text-gray-400 text-center mt-1">66 <span id="fontSizeValue">24</span>px67 </div>68 </div>69 <div>70 <label class="block text-sm font-medium mb-2">Text Color</label>71 <input type="color" id="textColor" value="#ffffff" class="w-16 h-10 border border-gray-300 dark:border-gray-600 rounded cursor-pointer">72 </div>73 <div>74 <label class="block text-sm font-medium mb-2">Opacity</label>75 <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">76 <div class="text-sm text-gray-500 dark:text-gray-400 text-center mt-1">77 <span id="textOpacityValue">80</span>%78 </div>79 </div>80 </div>81 </div>82 83 <div id="imageOptions" class="hidden">84 <div class="grid grid-cols-1 md:grid-cols-2 gap-4">85 <div>86 <label class="block text-sm font-medium mb-2">Watermark Image</label>87 <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">88 <p class="text-xs text-gray-500 dark:text-gray-400 mt-1">PNG recommended for transparency</p>89 </div>90 <div>91 <label class="block text-sm font-medium mb-2">Size (%)</label>92 <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">93 <div class="text-sm text-gray-500 dark:text-gray-400 text-center mt-1">94 <span id="imageSizeValue">15</span>%95 </div>96 </div>97 <div>98 <label class="block text-sm font-medium mb-2">Opacity</label>99 <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">100 <div class="text-sm text-gray-500 dark:text-gray-400 text-center mt-1">101 <span id="imageOpacityValue">80</span>%102 </div>103 </div>104 </div>105 </div>106 </div>107 108 <div class="bg-gray-50 dark:bg-gray-800 rounded-xl p-6 mb-6" id="previewSection" style="display: none;">109 <h2 class="text-xl font-semibold mb-4">3. Position Preview</h2>110 <div class="relative max-w-4xl mx-auto">111 <video id="previewVideo" class="w-full rounded-lg shadow-lg" controls crossorigin="anonymous"></video>112 <canvas id="previewCanvas" class="absolute top-0 left-0 w-full h-full pointer-events-none rounded-lg"></canvas>113 </div>114 <div class="mt-4 text-center">115 <p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Click on the video to position your watermark</p>116 <div class="flex flex-wrap justify-center gap-2">117 <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>118 <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>119 <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>120 <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>121 <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>122 </div>123 </div>124 </div>125 126 <div class="bg-gray-50 dark:bg-gray-800 rounded-xl p-6" id="processSection" style="display: none;">127 <h2 class="text-xl font-semibold mb-4">4. Process Video</h2>128 129 <!-- Export Format Selection -->130 <div class="mb-6">131 <h3 class="text-lg font-medium mb-3">Export Format</h3>132 <div class="flex flex-wrap gap-4">133 <label class="flex items-center space-x-2 cursor-pointer">134 <input type="radio" name="exportFormat" value="webm" class="text-primary" checked>135 <span>WebM (Free)</span>136 </label>137 <label class="flex items-center space-x-2 cursor-pointer" id="mp4Option">138 <input type="radio" name="exportFormat" value="mp4" class="text-primary" disabled>139 <span class="text-gray-400">MP4 (Premium - Unlock Required)</span>140 </label>141 </div>142 <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">143 <div class="flex items-center justify-between">144 <div>145 <p class="text-sm font-medium text-yellow-800 dark:text-yellow-200">Unlock MP4 Export</p>146 <p class="text-xs text-yellow-600 dark:text-yellow-300">Get premium features with @Cheap-Unlock-Bot</p>147 </div>148 <button id="unlockBtn" onclick="unlockMP4Feature()" class="bg-yellow-600 text-white px-4 py-2 rounded hover:bg-yellow-700 transition-colors text-sm">149 Unlock MP4150 </button>151 </div>152 <div id="unlockStatus" class="mt-2 hidden">153 <p class="text-sm text-yellow-700 dark:text-yellow-300">Contacting @Cheap-Unlock-Bot...</p>154 </div>155 </div>156 <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">157 <div class="flex items-center">158 <svg class="w-5 h-5 text-green-600 dark:text-green-400 mr-2" fill="currentColor" viewBox="0 0 20 20">159 <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>160 </svg>161 <p class="text-sm font-medium text-green-800 dark:text-green-200">MP4 Export Unlocked!</p>162 </div>163 </div>164 </div>165 166 <div class="text-center">167 <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">168 Add Watermark for free169 </button>170 <div id="progressContainer" class="mt-4 hidden">171 <div class="bg-gray-200 dark:bg-gray-700 rounded-full h-2 mb-2">172 <div id="progressBar" class="bg-primary h-2 rounded-full transition-all duration-300" style="width: 0%"></div>173 </div>174 <p id="progressText" class="text-sm text-gray-600 dark:text-gray-400">Starting...</p>175 </div>176 <div id="downloadContainer" class="mt-4 hidden">177 <a id="downloadLink" class="inline-block bg-green-600 text-white px-6 py-3 rounded-lg hover:bg-green-700 transition-colors">178 Download Watermarked Video179 </a>180 </div>181 </div>182 </div>183 </div>184 185 <div id="modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">186 <div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg max-w-sm w-full mx-4">187 <p id="modalText" class="text-gray-700 dark:text-gray-300 mb-4"></p>188 <div class="flex justify-end">189 <button onclick="closeModal()" class="px-4 py-2 bg-primary text-white hover:bg-purple-600 rounded">OK</button>190 </div>191 </div>192 </div>193 194 <script>195 if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {196 document.documentElement.classList.add('dark');197 }198 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {199 if (event.matches) {200 document.documentElement.classList.add('dark');201 } else {202 document.documentElement.classList.remove('dark');203 }204 });205 206 let currentVideo = null;207 let watermarkPosition = { x: 50, y: 50 };208 let watermarkImage = null;209 let previewCanvas = null;210 let previewCtx = null;211 let mp4Unlocked = false;212 213 function showModal(message) {214 document.getElementById('modalText').textContent = message;215 document.getElementById('modal').classList.remove('hidden');216 }217 218 function closeModal() {219 document.getElementById('modal').classList.add('hidden');220 }221 222 // Register handler for unlock bot response223 if (window.Poe && window.Poe.registerHandler) {224 window.Poe.registerHandler("unlock-handler", (result, context) => {225 const unlockStatus = document.getElementById('unlockStatus');226 const unlockBtn = document.getElementById('unlockBtn');227 228 if (result.responses && result.responses.length > 0) {229 const response = result.responses[0];230 231 if (response.status === "complete") {232 const responseText = response.content.toLowerCase();233 234 if (responseText.includes('cheapunl0ck')) {235 // Unlock successful236 mp4Unlocked = true;237 document.getElementById('unlockSection').classList.add('hidden');238 document.getElementById('unlockedSection').classList.remove('hidden');239 240 const mp4Radio = document.querySelector('input[name="exportFormat"][value="mp4"]');241 const mp4Label = mp4Radio.parentElement.querySelector('span');242 mp4Radio.disabled = false;243 mp4Label.textContent = 'MP4 (Premium)';244 mp4Label.classList.remove('text-gray-400');245 mp4Label.classList.add('text-gray-900', 'dark:text-gray-100');246 247 unlockStatus.innerHTML = '<p class="text-sm text-green-700 dark:text-green-300">✅ Successfully unlocked MP4 export!</p>';248 } else {249 // Unlock failed250 unlockStatus.innerHTML = '<p class="text-sm text-red-700 dark:text-red-300">❌ Unlock failed. Please try again.</p>';251 unlockBtn.disabled = false;252 unlockBtn.textContent = 'Unlock MP4';253 }254 } else if (response.status === "error") {255 unlockStatus.innerHTML = '<p class="text-sm text-red-700 dark:text-red-300">❌ Error contacting unlock bot.</p>';256 unlockBtn.disabled = false;257 unlockBtn.textContent = 'Unlock MP4';258 }259 } else {260 unlockStatus.innerHTML = '<p class="text-sm text-red-700 dark:text-red-300">❌ No response from unlock bot.</p>';261 unlockBtn.disabled = false;262 unlockBtn.textContent = 'Unlock MP4';263 }264 });265 }266 267 async function unlockMP4Feature() {268 const unlockBtn = document.getElementById('unlockBtn');269 const unlockStatus = document.getElementById('unlockStatus');270 271 if (!window.Poe || !window.Poe.sendUserMessage) {272 showModal('Poe API not available. MP4 unlock feature requires the Poe environment.');273 return;274 }275 276 unlockBtn.disabled = true;277 unlockBtn.textContent = 'Unlocking...';278 unlockStatus.classList.remove('hidden');279 280 try {281 await window.Poe.sendUserMessage(282 "@Cheap-Unlock-Bot I want to unlock MP4 export feature for Video Watermarker", 283 {284 handler: "unlock-handler",285 stream: false,286 openChat: false287 }288 );289 } catch (error) {290 console.error('Error contacting unlock bot:', error);291 unlockStatus.innerHTML = '<p class="text-sm text-red-700 dark:text-red-300">❌ Failed to contact unlock bot.</p>';292 unlockBtn.disabled = false;293 unlockBtn.textContent = 'Unlock MP4';294 }295 }296 297 document.getElementById('videoInput').addEventListener('change', function(e) {298 const file = e.target.files[0];299 if (file) {300 currentVideo = file;301 const url = URL.createObjectURL(file);302 const video = document.getElementById('previewVideo');303 video.src = url;304 305 document.getElementById('videoInfo').classList.remove('hidden');306 document.getElementById('videoInfo').querySelector('p').textContent = 307 `Selected: ${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)`;308 309 document.getElementById('watermarkSection').style.display = 'block';310 document.getElementById('previewSection').style.display = 'block';311 document.getElementById('processSection').style.display = 'block';312 313 previewCanvas = document.getElementById('previewCanvas');314 previewCtx = previewCanvas.getContext('2d');315 316 video.addEventListener('click', function(e) {317 const rect = video.getBoundingClientRect();318 const x = ((e.clientX - rect.left) / rect.width) * 100;319 const y = ((e.clientY - rect.top) / rect.height) * 100;320 watermarkPosition.x = Math.max(0, Math.min(95, x));321 watermarkPosition.y = Math.max(0, Math.min(95, y));322 drawWatermarkPreview();323 });324 325 video.addEventListener('loadedmetadata', function() {326 resizeCanvas();327 drawWatermarkPreview();328 });329 330 video.addEventListener('timeupdate', drawWatermarkPreview);331 }332 });333 334 function resizeCanvas() {335 const video = document.getElementById('previewVideo');336 const canvas = document.getElementById('previewCanvas');337 const rect = video.getBoundingClientRect();338 canvas.width = rect.width;339 canvas.height = rect.height;340 canvas.style.width = rect.width + 'px';341 canvas.style.height = rect.height + 'px';342 }343 344 document.addEventListener('change', function(e) {345 if (e.target.name === 'watermarkType') {346 const textOptions = document.getElementById('textOptions');347 const imageOptions = document.getElementById('imageOptions');348 if (e.target.value === 'text') {349 textOptions.classList.remove('hidden');350 imageOptions.classList.add('hidden');351 } else {352 textOptions.classList.add('hidden');353 imageOptions.classList.remove('hidden');354 }355 drawWatermarkPreview();356 }357 });358 359 document.getElementById('imageInput').addEventListener('change', function(e) {360 const file = e.target.files[0];361 if (file) {362 const reader = new FileReader();363 reader.onload = function(e) {364 const img = new Image();365 img.onload = function() {366 watermarkImage = img;367 drawWatermarkPreview();368 };369 img.src = e.target.result;370 };371 reader.readAsDataURL(file);372 }373 });374 375 document.getElementById('fontSize').addEventListener('input', function(e) {376 document.getElementById('fontSizeValue').textContent = e.target.value;377 drawWatermarkPreview();378 });379 380 document.getElementById('textOpacity').addEventListener('input', function(e) {381 document.getElementById('textOpacityValue').textContent = Math.round(e.target.value * 100);382 drawWatermarkPreview();383 });384 385 document.getElementById('imageSize').addEventListener('input', function(e) {386 document.getElementById('imageSizeValue').textContent = e.target.value;387 drawWatermarkPreview();388 });389 390 document.getElementById('imageOpacity').addEventListener('input', function(e) {391 document.getElementById('imageOpacityValue').textContent = Math.round(e.target.value * 100);392 drawWatermarkPreview();393 });394 395 document.getElementById('watermarkText').addEventListener('input', drawWatermarkPreview);396 document.getElementById('textColor').addEventListener('input', drawWatermarkPreview);397 398 function setPosition(position) {399 switch(position) {400 case 'top-left':401 watermarkPosition = { x: 5, y: 5 };402 break;403 case 'top-right':404 watermarkPosition = { x: 85, y: 5 };405 break;406 case 'bottom-left':407 watermarkPosition = { x: 5, y: 85 };408 break;409 case 'bottom-right':410 watermarkPosition = { x: 85, y: 85 };411 break;412 case 'center':413 watermarkPosition = { x: 45, y: 45 };414 break;415 }416 drawWatermarkPreview();417 }418 419 function drawWatermarkPreview() {420 if (!previewCtx) return;421 422 const canvas = previewCanvas;423 const ctx = previewCtx;424 const watermarkType = document.querySelector('input[name="watermarkType"]:checked');425 426 ctx.clearRect(0, 0, canvas.width, canvas.height);427 428 if (!watermarkType) return;429 430 const x = (watermarkPosition.x / 100) * canvas.width;431 const y = (watermarkPosition.y / 100) * canvas.height;432 433 if (watermarkType.value === 'text') {434 const text = document.getElementById('watermarkText').value || 'Sample Text';435 const fontSize = parseInt(document.getElementById('fontSize').value);436 const color = document.getElementById('textColor').value;437 const opacity = parseFloat(document.getElementById('textOpacity').value);438 439 const scaledFontSize = fontSize * (canvas.width / 800);440 ctx.font = `bold ${scaledFontSize}px Arial`;441 ctx.fillStyle = color;442 ctx.globalAlpha = opacity;443 ctx.shadowColor = 'black';444 ctx.shadowBlur = 4;445 ctx.shadowOffsetX = 2;446 ctx.shadowOffsetY = 2;447 ctx.fillText(text, x, y + scaledFontSize);448 ctx.shadowColor = 'transparent';449 ctx.shadowBlur = 0;450 ctx.shadowOffsetX = 0;451 ctx.shadowOffsetY = 0;452 ctx.globalAlpha = 1;453 } else if (watermarkType.value === 'image' && watermarkImage) {454 const size = parseInt(document.getElementById('imageSize').value);455 const opacity = parseFloat(document.getElementById('imageOpacity').value);456 457 const imgWidth = (size / 100) * canvas.width;458 const imgHeight = (watermarkImage.height / watermarkImage.width) * imgWidth;459 460 ctx.globalAlpha = opacity;461 ctx.drawImage(watermarkImage, x, y, imgWidth, imgHeight);462 ctx.globalAlpha = 1;463 }464 }465 466 async function processVideo() {467 if (!currentVideo) {468 showModal('Please select a video file first.');469 return;470 }471 472 const watermarkType = document.querySelector('input[name="watermarkType"]:checked');473 if (!watermarkType) {474 showModal('Please select a watermark type.');475 return;476 }477 478 if (watermarkType.value === 'text' && !document.getElementById('watermarkText').value) {479 showModal('Please enter watermark text.');480 return;481 }482 483 if (watermarkType.value === 'image' && !watermarkImage) {484 showModal('Please select a watermark image.');485 return;486 }487 488 const exportFormat = document.querySelector('input[name="exportFormat"]:checked');489 if (!exportFormat) {490 showModal('Please select an export format.');491 return;492 }493 494 if (exportFormat.value === 'mp4' && !mp4Unlocked) {495 showModal('Please unlock MP4 export first using the unlock button.');496 return;497 }498 499 const processBtn = document.getElementById('processBtn');500 const progressContainer = document.getElementById('progressContainer');501 const progressBar = document.getElementById('progressBar');502 const progressText = document.getElementById('progressText');503 504 processBtn.disabled = true;505 processBtn.textContent = 'Processing...';506 progressContainer.classList.remove('hidden');507 508 try {509 progressText.textContent = 'Preparing video...';510 progressBar.style.width = '10%';511 512 const video = document.createElement('video');513 video.src = URL.createObjectURL(currentVideo);514 video.crossOrigin = 'anonymous';515 516 await new Promise((resolve) => {517 video.addEventListener('loadedmetadata', resolve);518 video.load();519 });520 521 progressText.textContent = 'Setting up recording...';522 progressBar.style.width = '20%';523 524 const canvas = document.createElement('canvas');525 const ctx = canvas.getContext('2d');526 canvas.width = video.videoWidth;527 canvas.height = video.videoHeight;528 529 const videoStream = canvas.captureStream(30);530 let combinedStream = videoStream;531 532 try {533 const originalStream = video.captureStream();534 const audioTracks = originalStream.getAudioTracks();535 if (audioTracks.length > 0) {536 combinedStream = new MediaStream([537 ...videoStream.getVideoTracks(),538 ...audioTracks539 ]);540 progressText.textContent = 'Recording with audio...';541 } else {542 progressText.textContent = 'Recording video (no audio track found)...';543 }544 } catch (audioError) {545 console.warn('Could not capture audio:', audioError);546 progressText.textContent = 'Recording video (audio capture failed)...';547 }548 549 // Determine MIME type based on selected format550 let mimeType;551 let fileExtension;552 553 if (exportFormat.value === 'mp4') {554 // Try MP4 format if unlocked555 if (MediaRecorder.isTypeSupported('video/mp4;codecs=h264')) {556 mimeType = 'video/mp4;codecs=h264';557 fileExtension = 'mp4';558 } else if (MediaRecorder.isTypeSupported('video/mp4')) {559 mimeType = 'video/mp4';560 fileExtension = 'mp4';561 } else {562 // Fall back to WebM if MP4 not supported563 mimeType = 'video/webm;codecs=vp9';564 fileExtension = 'webm';565 progressText.textContent = 'MP4 not supported by browser, using WebM...';566 }567 } else {568 // Default to WebM569 mimeType = 'video/webm;codecs=vp9';570 fileExtension = 'webm';571 }572 573 const recorder = new MediaRecorder(combinedStream, { mimeType });574 const chunks = [];575 recorder.ondataavailable = (e) => chunks.push(e.data);576 577 progressBar.style.width = '40%';578 recorder.start();579 video.play();580 581 const drawFrame = () => {582 if (video.ended || video.paused) {583 recorder.stop();584 return;585 }586 587 ctx.drawImage(video, 0, 0, canvas.width, canvas.height);588 589 const x = (watermarkPosition.x / 100) * canvas.width;590 const y = (watermarkPosition.y / 100) * canvas.height;591 592 if (watermarkType.value === 'text') {593 const text = document.getElementById('watermarkText').value;594 const fontSize = parseInt(document.getElementById('fontSize').value);595 const color = document.getElementById('textColor').value;596 const opacity = parseFloat(document.getElementById('textOpacity').value);597 598 const scaledFontSize = fontSize * (canvas.width / 800);599 ctx.font = `bold ${scaledFontSize}px Arial`;600 ctx.fillStyle = color;601 ctx.globalAlpha = opacity;602 ctx.shadowColor = 'black';603 ctx.shadowBlur = 4;604 ctx.shadowOffsetX = 2;605 ctx.shadowOffsetY = 2;606 ctx.fillText(text, x, y + scaledFontSize);607 ctx.shadowColor = 'transparent';608 ctx.shadowBlur = 0;609 ctx.shadowOffsetX = 0;610 ctx.shadowOffsetY = 0;611 ctx.globalAlpha = 1;612 } else if (watermarkType.value === 'image' && watermarkImage) {613 const size = parseInt(document.getElementById('imageSize').value);614 const opacity = parseFloat(document.getElementById('imageOpacity').value);615 616 const imgWidth = (size / 100) * canvas.width;617 const imgHeight = (watermarkImage.height / watermarkImage.width) * imgWidth;618 619 ctx.globalAlpha = opacity;620 ctx.drawImage(watermarkImage, x, y, imgWidth, imgHeight);621 ctx.globalAlpha = 1;622 }623 624 const progress = (video.currentTime / video.duration) * 50;625 progressBar.style.width = (40 + progress) + '%';626 requestAnimationFrame(drawFrame);627 };628 629 drawFrame();630 631 recorder.onstop = () => {632 progressText.textContent = 'Finalizing video...';633 progressBar.style.width = '95%';634 635 const blob = new Blob(chunks, { type: mimeType });636 const url = URL.createObjectURL(blob);637 638 progressText.textContent = 'Complete!';639 progressBar.style.width = '100%';640 641 const downloadLink = document.getElementById('downloadLink');642 downloadLink.href = url;643 downloadLink.download = `watermarked_${currentVideo.name.replace(/\.[^/.]+$/, '')}.${fileExtension}`;644 document.getElementById('downloadContainer').classList.remove('hidden');645 };646 647 } catch (error) {648 console.error('Processing error:', error);649 showModal('Error processing video: ' + error.message);650 } finally {651 processBtn.disabled = false;652 processBtn.textContent = 'Add Watermark & Download';653 }654 }655 656 window.addEventListener('resize', () => {657 if (previewCanvas) {658 setTimeout(() => {659 resizeCanvas();660 drawWatermarkPreview();661 }, 100);662 }663 });664 </script>665</html>