CoolFace
Apppublic

Open-Hat-Lab/Add-Video-Watermark

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
index.html665 linesDownload Raw Back to root
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>