MaxonML commited on
Commit
3e997d4
·
verified ·
1 Parent(s): bba6d0a

Upload index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +263 -0
templates/index.html ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Pro Watermark Remover</title>
7
+ <style>
8
+ body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #1e1e2f; color: #fff; margin: 0; padding: 30px; display: flex; flex-direction: column; align-items: center; }
9
+ .container { background: #2a2a40; padding: 40px; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); max-width: 900px; width: 100%; text-align: center; }
10
+ .controls { display: flex; justify-content: center; align-items: center; gap: 15px; margin-bottom: 25px; flex-wrap: wrap; }
11
+ input[type="file"], select, button { padding: 12px; border-radius: 8px; border: none; font-size: 16px; outline: none; }
12
+ select { background: #fff; color: #333; cursor: pointer; min-width: 250px; }
13
+ button { background: #ff4757; color: white; cursor: pointer; font-weight: bold; transition: 0.2s; }
14
+ button:hover { background: #ff6b81; transform: scale(1.05); }
15
+ button:disabled { background: #57606f; cursor: not-allowed; transform: none; }
16
+
17
+ .tool-panel { display: none; background: #353b48; padding: 10px 20px; border-radius: 8px; margin-bottom: 15px; justify-content: center; gap: 20px; align-items: center;}
18
+ .tool-panel label { cursor: pointer; font-weight: bold; }
19
+ .tool-panel input[type="range"] { vertical-align: middle; }
20
+
21
+ .workspace-wrapper { display: none; justify-content: center; margin-bottom: 25px; }
22
+ .editor-container { display: grid; position: relative; max-width: 100%; border: 2px solid #ff4757; border-radius: 8px; overflow: hidden; }
23
+ #video-frame { grid-area: 1 / 1; max-width: 100%; max-height: 60vh; width: auto; height: auto; display: block; }
24
+ #overlay-canvas { grid-area: 1 / 1; width: 100%; height: 100%; cursor: crosshair; z-index: 10; }
25
+
26
+ #status { padding: 15px; border-radius: 8px; background: #3742fa; font-weight: bold; display: none; margin-bottom: 20px; }
27
+ .btn-success { background: #2ed573; } .btn-success:hover { background: #7bed9f; }
28
+ .btn-clear { background: #718093; padding: 8px 15px; font-size: 14px; } .btn-clear:hover { background: #fbc531; color: #000; }
29
+ </style>
30
+ </head>
31
+ <body>
32
+
33
+ <div class="container">
34
+ <h1 style="margin-top:0;">Pro Watermark Remover</h1>
35
+ <p style="color:#a4b0be;">Powered by FFmpeg. Draw a box or paint freely to remove watermarks.</p>
36
+
37
+ <div class="controls">
38
+ <input type="file" id="video-upload" multiple accept="video/*">
39
+ <button id="upload-btn">1. Upload File(s)</button>
40
+ </div>
41
+
42
+ <div class="tool-panel" id="tool-panel">
43
+ <label><input type="radio" name="tool" value="box" checked> 🟥 Rectangle</label>
44
+ <label><input type="radio" name="tool" value="brush"> 🖌️ Freehand Brush</label>
45
+
46
+ <span id="brush-size-container" style="display: none; margin-left: 10px;">
47
+ Size: <input type="range" id="brush-size" min="10" max="80" value="30">
48
+ </span>
49
+ <button class="btn-clear" id="clear-btn" style="margin-left: auto;">Undo / Clear</button>
50
+ </div>
51
+
52
+ <div class="workspace-wrapper" id="workspace">
53
+ <div class="editor-container">
54
+ <img id="video-frame" src="" draggable="false">
55
+ <canvas id="overlay-canvas"></canvas>
56
+ </div>
57
+ </div>
58
+
59
+ <div class="controls" id="process-controls" style="display: none;">
60
+ <select id="method-select">
61
+ <option value="blur_heavy">Heavy Blur (Best for text/faces)</option>
62
+ <option value="blur_light">Soft Frosted Blur (Subtle blend)</option>
63
+ <option value="pixelate">Mosaic / Pixelate (TV look)</option>
64
+ <option value="delogo">AI Interpolation (Best for see-through logos)</option>
65
+ <option value="black_box">Solid Black Box (Total redaction)</option>
66
+ </select>
67
+ <select id="upscale-select">
68
+ <option value="none">📐 No Upscale (Original Quality)</option>
69
+ <option value="1.5x">🔺 1.5× Upscale (Sharper)</option>
70
+ <option value="2x">🔺 2× Upscale (Double Resolution)</option>
71
+ <option value="4k">🎬 4K Upscale (3840×2160)</option>
72
+ </select>
73
+ <button id="process-btn">2. Process Video</button>
74
+ </div>
75
+
76
+ <div id="status"></div>
77
+ <a id="download-link" style="display: none;"><button class="btn-success">3. Download Clean Video</button></a>
78
+ </div>
79
+
80
+ <script>
81
+ const uploadBtn = document.getElementById('upload-btn');
82
+ const fileInput = document.getElementById('video-upload');
83
+ const workspace = document.getElementById('workspace');
84
+ const toolPanel = document.getElementById('tool-panel');
85
+ const processControls = document.getElementById('process-controls');
86
+ const img = document.getElementById('video-frame');
87
+ const canvas = document.getElementById('overlay-canvas');
88
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
89
+ const processBtn = document.getElementById('process-btn');
90
+ const statusBox = document.getElementById('status');
91
+ const downloadLink = document.getElementById('download-link');
92
+ const clearBtn = document.getElementById('clear-btn');
93
+ const brushSizeSlider = document.getElementById('brush-size');
94
+ const brushSizeContainer = document.getElementById('brush-size-container');
95
+
96
+ let currentFilenames = [];
97
+ let origVideoW = 0, origVideoH = 0;
98
+ let isDrawing = false;
99
+ let startX = 0, startY = 0, rectW = 0, rectH = 0;
100
+ let currentTool = 'box';
101
+ let hasPainted = false;
102
+
103
+ // Tool toggle logic
104
+ document.querySelectorAll('input[name="tool"]').forEach(radio => {
105
+ radio.addEventListener('change', (e) => {
106
+ currentTool = e.target.value;
107
+ brushSizeContainer.style.display = currentTool === 'brush' ? 'inline-block' : 'none';
108
+ clearCanvas();
109
+ });
110
+ });
111
+
112
+ clearBtn.addEventListener('click', clearCanvas);
113
+
114
+ function clearCanvas() {
115
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
116
+ rectW = rectH = 0;
117
+ hasPainted = false;
118
+ }
119
+
120
+ function showStatus(msg) {
121
+ statusBox.style.display = 'block';
122
+ statusBox.innerText = msg;
123
+ }
124
+
125
+ uploadBtn.addEventListener('click', async () => {
126
+ if (!fileInput.files.length) return alert('Select videos first.');
127
+
128
+ const formData = new FormData();
129
+ for (let i = 0; i < fileInput.files.length; i++) { formData.append('videos', fileInput.files[i]); }
130
+
131
+ showStatus('Uploading and analyzing video...');
132
+ uploadBtn.disabled = true;
133
+
134
+ try {
135
+ const response = await fetch('/upload', { method: 'POST', body: formData });
136
+ const data = await response.json();
137
+
138
+ if (data.filenames) {
139
+ currentFilenames = data.filenames;
140
+ origVideoW = data.orig_w;
141
+ origVideoH = data.orig_h;
142
+
143
+ img.onload = () => {
144
+ workspace.style.display = 'flex';
145
+ processControls.style.display = 'flex';
146
+ toolPanel.style.display = 'flex';
147
+ canvas.width = img.clientWidth;
148
+ canvas.height = img.clientHeight;
149
+ showStatus('Draw over the watermark, then click Process.');
150
+ };
151
+ img.src = data.frame_url;
152
+ }
153
+ } catch (err) {
154
+ showStatus('Upload failed.');
155
+ }
156
+ uploadBtn.disabled = false;
157
+ });
158
+
159
+ // Mouse logic handles BOTH Rectangle and Brush
160
+ canvas.addEventListener('mousedown', (e) => {
161
+ const rect = canvas.getBoundingClientRect();
162
+ startX = e.clientX - rect.left;
163
+ startY = e.clientY - rect.top;
164
+ isDrawing = true;
165
+
166
+ if (currentTool === 'brush') {
167
+ ctx.beginPath();
168
+ ctx.moveTo(startX, startY);
169
+ ctx.lineCap = 'round';
170
+ ctx.lineJoin = 'round';
171
+ ctx.lineWidth = brushSizeSlider.value;
172
+ ctx.strokeStyle = 'rgba(255, 71, 87, 0.7)';
173
+ }
174
+ });
175
+
176
+ canvas.addEventListener('mousemove', (e) => {
177
+ if (!isDrawing) return;
178
+ const rect = canvas.getBoundingClientRect();
179
+ const currentX = e.clientX - rect.left;
180
+ const currentY = e.clientY - rect.top;
181
+
182
+ if (currentTool === 'box') {
183
+ rectW = currentX - startX;
184
+ rectH = currentY - startY;
185
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
186
+ ctx.fillStyle = 'rgba(255, 71, 87, 0.3)';
187
+ ctx.fillRect(startX, startY, rectW, rectH);
188
+ ctx.strokeStyle = '#ff4757';
189
+ ctx.lineWidth = 2;
190
+ ctx.strokeRect(startX, startY, rectW, rectH);
191
+ } else if (currentTool === 'brush') {
192
+ ctx.lineTo(currentX, currentY);
193
+ ctx.stroke();
194
+ hasPainted = true;
195
+ }
196
+ });
197
+
198
+ canvas.addEventListener('mouseup', () => { isDrawing = false; });
199
+ canvas.addEventListener('mouseleave', () => { isDrawing = false; });
200
+
201
+ window.addEventListener('resize', () => {
202
+ if (workspace.style.display === 'flex') {
203
+ canvas.width = img.clientWidth;
204
+ canvas.height = img.clientHeight;
205
+ clearCanvas();
206
+ }
207
+ });
208
+
209
+ processBtn.addEventListener('click', async () => {
210
+ const isBoxEmpty = (currentTool === 'box' && rectW === 0 && rectH === 0);
211
+ const isBrushEmpty = (currentTool === 'brush' && !hasPainted);
212
+
213
+ if (isBoxEmpty || isBrushEmpty) {
214
+ return alert('You must mark the area on the video first.');
215
+ }
216
+
217
+ const x = rectW < 0 ? startX + rectW : startX;
218
+ const y = rectH < 0 ? startY + rectH : startY;
219
+
220
+ const payload = {
221
+ filenames: currentFilenames,
222
+ method: document.getElementById('method-select').value,
223
+ tool: currentTool,
224
+ upscale: document.getElementById('upscale-select').value,
225
+ orig_w: origVideoW,
226
+ orig_h: origVideoH,
227
+ // Only used if tool = box
228
+ px: x / canvas.width,
229
+ py: y / canvas.height,
230
+ pw: Math.abs(rectW) / canvas.width,
231
+ ph: Math.abs(rectH) / canvas.height,
232
+ // Only used if tool = brush
233
+ mask_b64: currentTool === 'brush' ? canvas.toDataURL('image/png') : ''
234
+ };
235
+
236
+ showStatus('Rendering video... This will be fast.');
237
+ processBtn.disabled = true;
238
+ downloadLink.style.display = 'none';
239
+
240
+ try {
241
+ const response = await fetch('/process', {
242
+ method: 'POST',
243
+ headers: { 'Content-Type': 'application/json' },
244
+ body: JSON.stringify(payload)
245
+ });
246
+ const data = await response.json();
247
+
248
+ if (data.download_url) {
249
+ showStatus('Success! Download is ready.');
250
+ const name = data.download_name || '';
251
+ downloadLink.href = data.download_url + (name ? '?name=' + encodeURIComponent(name) : '');
252
+ downloadLink.style.display = 'inline-block';
253
+ } else {
254
+ showStatus('Error processing video: ' + (data.error || 'Unknown'));
255
+ }
256
+ } catch (err) {
257
+ showStatus('Processing failed. Please try again.');
258
+ }
259
+ processBtn.disabled = false;
260
+ });
261
+ </script>
262
+ </body>
263
+ </html>