TDN-M commited on
Commit
eb9b350
·
verified ·
1 Parent(s): f68f919

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +132 -706
index.html CHANGED
@@ -4,725 +4,151 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>OBS AI Background Remover</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.11.0/dist/tf.min.js"></script>
9
- <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/[email protected]/dist/body-pix.min.js"></script>
10
- <style>
11
- .video-container {
12
- position: relative;
13
- width: 100%;
14
- height: 0;
15
- padding-bottom: 56.25%; /* 16:9 aspect ratio */
16
- }
17
- .video-element {
18
- position: absolute;
19
- top: 0;
20
- left: 0;
21
- width: 100%;
22
- height: 100%;
23
- object-fit: cover;
24
- }
25
- .processing-overlay {
26
- position: absolute;
27
- top: 0;
28
- left: 0;
29
- width: 100%;
30
- height: 100%;
31
- background-color: rgba(0, 0, 0, 0.7);
32
- display: flex;
33
- justify-content: center;
34
- align-items: center;
35
- color: white;
36
- font-size: 1.5rem;
37
- z-index: 10;
38
- }
39
- .hidden {
40
- display: none;
41
- }
42
- .settings-panel {
43
- transition: all 0.3s ease;
44
- max-height: 0;
45
- overflow: hidden;
46
- }
47
- .settings-panel.open {
48
- max-height: 500px;
49
- }
50
- .preview-thumbnail {
51
- transition: all 0.2s ease;
52
- }
53
- .preview-thumbnail:hover {
54
- transform: scale(1.05);
55
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
56
- }
57
- .preview-thumbnail.active {
58
- border: 3px solid #3b82f6;
59
- }
60
- </style>
61
  </head>
62
- <body class="bg-gray-900 text-white">
63
- <div class="container mx-auto px-4 py-8">
64
- <div class="flex flex-col md:flex-row gap-8">
65
- <!-- Main Content -->
66
- <div class="flex-1">
67
- <div class="bg-gray-800 rounded-lg shadow-xl p-6">
68
- <h1 class="text-3xl font-bold mb-6 text-blue-400">OBS AI Background Remover</h1>
69
-
70
- <!-- Video Preview Section -->
71
- <div class="mb-8">
72
- <h2 class="text-xl font-semibold mb-4">Live Preview</h2>
73
- <div class="video-container bg-gray-700 rounded-lg overflow-hidden relative">
74
- <video id="videoInput" class="video-element" autoplay muted></video>
75
- <canvas id="outputCanvas" class="video-element hidden"></canvas>
76
- <div id="processingOverlay" class="processing-overlay hidden">
77
- <div class="text-center">
78
- <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-400 mx-auto mb-4"></div>
79
- <p>Loading AI Model...</p>
80
- </div>
81
- </div>
82
- <div id="noVideoOverlay" class="processing-overlay">
83
- <p>No video source selected</p>
84
- </div>
85
- </div>
86
- </div>
87
-
88
- <!-- Background Replacement Section -->
89
- <div class="mb-8">
90
- <div class="flex justify-between items-center mb-4">
91
- <h2 class="text-xl font-semibold">Background Replacement</h2>
92
- <button id="settingsToggle" class="text-blue-400 hover:text-blue-300">
93
- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
94
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
95
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
96
- </svg>
97
- </button>
98
- </div>
99
-
100
- <!-- Settings Panel -->
101
- <div id="settingsPanel" class="settings-panel bg-gray-700 rounded-lg p-4 mb-4">
102
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
103
- <div>
104
- <label class="block text-sm font-medium mb-1">AI Model Quality</label>
105
- <select id="modelQuality" class="w-full bg-gray-600 border border-gray-500 rounded-md px-3 py-2">
106
- <option value="0.5">Fast (Low Quality)</option>
107
- <option value="0.75" selected>Balanced</option>
108
- <option value="1.0">High Quality (Slow)</option>
109
- </select>
110
- </div>
111
- <div>
112
- <label class="block text-sm font-medium mb-1">Edge Smoothness</label>
113
- <select id="edgeSmoothness" class="w-full bg-gray-600 border border-gray-500 rounded-md px-3 py-2">
114
- <option value="1">Low</option>
115
- <option value="2" selected>Medium</option>
116
- <option value="3">High</option>
117
- </select>
118
- </div>
119
- <div>
120
- <label class="block text-sm font-medium mb-1">Background Blur</label>
121
- <input id="bgBlur" type="range" min="0" max="10" value="0" class="w-full">
122
- <div class="flex justify-between text-xs text-gray-400">
123
- <span>Off</span>
124
- <span>Max</span>
125
- </div>
126
- </div>
127
- <div>
128
- <label class="block text-sm font-medium mb-1">Foreground Brightness</label>
129
- <input id="fgBrightness" type="range" min="80" max="120" value="100" class="w-full">
130
- <div class="flex justify-between text-xs text-gray-400">
131
- <span>Darker</span>
132
- <span>Brighter</span>
133
- </div>
134
- </div>
135
- </div>
136
- </div>
137
-
138
- <!-- Background Selection -->
139
- <div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
140
- <!-- Transparent Background -->
141
- <div class="preview-thumbnail bg-gray-700 rounded-lg overflow-hidden cursor-pointer active" id="bgTransparent">
142
- <div class="aspect-w-16 aspect-h-9 bg-gradient-to-br from-gray-600 to-gray-800 flex items-center justify-center">
143
- <div class="text-center p-2">
144
- <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mx-auto text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
145
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
146
- </svg>
147
- <span class="text-xs mt-1">Transparent</span>
148
- </div>
149
- </div>
150
- </div>
151
-
152
- <!-- Color Backgrounds -->
153
- <div class="preview-thumbnail bg-gray-700 rounded-lg overflow-hidden cursor-pointer" id="bgBlack">
154
- <div class="aspect-w-16 aspect-h-9 bg-black flex items-center justify-center">
155
- <span class="text-xs">Solid Black</span>
156
- </div>
157
- </div>
158
- <div class="preview-thumbnail bg-gray-700 rounded-lg overflow-hidden cursor-pointer" id="bgGray">
159
- <div class="aspect-w-16 aspect-h-9 bg-gray-600 flex items-center justify-center">
160
- <span class="text-xs">Solid Gray</span>
161
- </div>
162
- </div>
163
- <div class="preview-thumbnail bg-gray-700 rounded-lg overflow-hidden cursor-pointer" id="bgGreen">
164
- <div class="aspect-w-16 aspect-h-9 bg-green-600 flex items-center justify-center">
165
- <span class="text-xs">Solid Green</span>
166
- </div>
167
- </div>
168
-
169
- <!-- Custom Background Upload -->
170
- <div class="preview-thumbnail bg-gray-700 rounded-lg overflow-hidden cursor-pointer relative" id="bgCustom">
171
- <div class="aspect-w-16 aspect-h-9 bg-gradient-to-br from-purple-600 to-blue-600 flex items-center justify-center">
172
- <div class="text-center p-2">
173
- <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mx-auto text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
174
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
175
- </svg>
176
- <span class="text-xs mt-1">Custom Image</span>
177
- </div>
178
- </div>
179
- <input type="file" id="customBgInput" accept="image/*, video/*" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer">
180
- </div>
181
-
182
- <!-- Video Background Upload -->
183
- <div class="preview-thumbnail bg-gray-700 rounded-lg overflow-hidden cursor-pointer relative" id="bgVideo">
184
- <div class="aspect-w-16 aspect-h-9 bg-gradient-to-br from-red-600 to-yellow-600 flex items-center justify-center">
185
- <div class="text-center p-2">
186
- <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mx-auto text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
187
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
188
- </svg>
189
- <span class="text-xs mt-1">Custom Video</span>
190
- </div>
191
- </div>
192
- <input type="file" id="customVideoInput" accept="video/*" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer">
193
- </div>
194
-
195
- <!-- Blur Background -->
196
- <div class="preview-thumbnail bg-gray-700 rounded-lg overflow-hidden cursor-pointer" id="bgBlurred">
197
- <div class="aspect-w-16 aspect-h-9 bg-gray-600 flex items-center justify-center relative overflow-hidden">
198
- <div class="absolute inset-0 bg-gray-400 filter blur-md"></div>
199
- <span class="text-xs relative z-10">Blurred</span>
200
- </div>
201
- </div>
202
- </div>
203
- </div>
204
- </div>
205
- </div>
206
-
207
- <!-- Controls Sidebar -->
208
- <div class="w-full md:w-80">
209
- <div class="bg-gray-800 rounded-lg shadow-xl p-6 sticky top-6">
210
- <h2 class="text-xl font-semibold mb-4">Controls</h2>
211
-
212
- <!-- Video Source Selection -->
213
- <div class="mb-6">
214
- <label class="block text-sm font-medium mb-2">Video Source</label>
215
- <select id="videoSource" class="w-full bg-gray-600 border border-gray-500 rounded-md px-3 py-2">
216
- <option value="">Select a video source</option>
217
- </select>
218
- </div>
219
-
220
- <!-- Toggle Buttons -->
221
- <div class="space-y-4 mb-6">
222
- <div>
223
- <label class="inline-flex items-center">
224
- <input type="checkbox" id="toggleEffect" class="form-checkbox h-5 w-5 text-blue-400">
225
- <span class="ml-2">Enable Background Removal</span>
226
- </label>
227
- </div>
228
- <div>
229
- <label class="inline-flex items-center">
230
- <input type="checkbox" id="togglePreview" class="form-checkbox h-5 w-5 text-blue-400" checked>
231
- <span class="ml-2">Show Preview</span>
232
- </label>
233
- </div>
234
- </div>
235
-
236
- <!-- Performance Stats -->
237
- <div class="bg-gray-700 rounded-lg p-4 mb-6">
238
- <h3 class="text-sm font-medium mb-2 text-gray-400">Performance</h3>
239
- <div class="grid grid-cols-2 gap-2 text-sm">
240
- <div>
241
- <div class="text-gray-400">Processing Time:</div>
242
- <div id="processingTime">0 ms</div>
243
- </div>
244
- <div>
245
- <div class="text-gray-400">FPS:</div>
246
- <div id="fpsCounter">0</div>
247
- </div>
248
- </div>
249
- </div>
250
-
251
- <!-- OBS Integration -->
252
- <div class="bg-blue-900 bg-opacity-20 rounded-lg p-4 border border-blue-800">
253
- <h3 class="text-sm font-medium mb-2 text-blue-400">OBS Integration</h3>
254
- <p class="text-xs text-gray-400 mb-3">Add this as a Browser Source in OBS with these settings:</p>
255
- <div class="text-xs space-y-1">
256
- <div class="flex justify-between">
257
- <span class="text-gray-400">Width:</span>
258
- <span>1920</span>
259
- </div>
260
- <div class="flex justify-between">
261
- <span class="text-gray-400">Height:</span>
262
- <span>1080</span>
263
- </div>
264
- <div class="flex justify-between">
265
- <span class="text-gray-400">Custom CSS:</span>
266
- <span>None</span>
267
- </div>
268
- </div>
269
- <button id="copyOBSLink" class="mt-3 w-full bg-blue-600 hover:bg-blue-500 text-white py-2 px-4 rounded-md text-sm flex items-center justify-center">
270
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
271
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
272
- </svg>
273
- Copy OBS Browser Source URL
274
- </button>
275
- </div>
276
- </div>
277
- </div>
278
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  </div>
280
 
281
  <script>
282
- // DOM Elements
283
- const videoInput = document.getElementById('videoInput');
284
- const outputCanvas = document.getElementById('outputCanvas');
285
- const processingOverlay = document.getElementById('processingOverlay');
286
- const noVideoOverlay = document.getElementById('noVideoOverlay');
287
- const videoSourceSelect = document.getElementById('videoSource');
288
- const toggleEffect = document.getElementById('toggleEffect');
289
- const togglePreview = document.getElementById('togglePreview');
290
- const modelQuality = document.getElementById('modelQuality');
291
- const edgeSmoothness = document.getElementById('edgeSmoothness');
292
- const bgBlur = document.getElementById('bgBlur');
293
- const fgBrightness = document.getElementById('fgBrightness');
294
- const settingsToggle = document.getElementById('settingsToggle');
295
- const settingsPanel = document.getElementById('settingsPanel');
296
- const processingTimeElement = document.getElementById('processingTime');
297
- const fpsCounterElement = document.getElementById('fpsCounter');
298
- const copyOBSLink = document.getElementById('copyOBSLink');
299
-
300
- // Background selection elements
301
- const bgThumbnails = document.querySelectorAll('.preview-thumbnail');
302
- const bgTransparent = document.getElementById('bgTransparent');
303
- const bgBlack = document.getElementById('bgBlack');
304
- const bgGray = document.getElementById('bgGray');
305
- const bgGreen = document.getElementById('bgGreen');
306
- const bgCustom = document.getElementById('bgCustom');
307
- const bgVideo = document.getElementById('bgVideo');
308
- const bgBlurred = document.getElementById('bgBlurred');
309
- const customBgInput = document.getElementById('customBgInput');
310
- const customVideoInput = document.getElementById('customVideoInput');
311
-
312
- // Variables
313
- let net;
314
- let isProcessing = false;
315
- let lastTimestamp = 0;
316
- let frameCount = 0;
317
- let fps = 0;
318
- let currentBackground = 'transparent';
319
- let customBackgroundImage = null;
320
- let customBackgroundVideo = null;
321
- let backgroundVideoElement = null;
322
-
323
- // Initialize
324
- document.addEventListener('DOMContentLoaded', async () => {
325
- // Load video sources
326
- loadVideoSources();
327
-
328
- // Initialize settings panel toggle
329
- settingsToggle.addEventListener('click', () => {
330
- settingsPanel.classList.toggle('open');
331
- });
332
-
333
- // Initialize background selection
334
- bgThumbnails.forEach(thumb => {
335
- thumb.addEventListener('click', () => {
336
- bgThumbnails.forEach(t => t.classList.remove('active'));
337
- thumb.classList.add('active');
338
-
339
- if (thumb === bgTransparent) {
340
- currentBackground = 'transparent';
341
- } else if (thumb === bgBlack) {
342
- currentBackground = 'black';
343
- } else if (thumb === bgGray) {
344
- currentBackground = 'gray';
345
- } else if (thumb === bgGreen) {
346
- currentBackground = 'green';
347
- } else if (thumb === bgCustom) {
348
- currentBackground = 'customImage';
349
- customBgInput.click();
350
- } else if (thumb === bgVideo) {
351
- currentBackground = 'customVideo';
352
- customVideoInput.click();
353
- } else if (thumb === bgBlurred) {
354
- currentBackground = 'blurred';
355
- }
356
- });
357
- });
358
-
359
- // Handle custom background upload
360
- customBgInput.addEventListener('change', (e) => {
361
- const file = e.target.files[0];
362
- if (file) {
363
- const reader = new FileReader();
364
- reader.onload = (event) => {
365
- customBackgroundImage = new Image();
366
- customBackgroundImage.src = event.target.result;
367
- customBackgroundImage.onload = () => {
368
- bgCustom.classList.add('active');
369
- };
370
- };
371
- reader.readAsDataURL(file);
372
- }
373
- });
374
-
375
- // Handle custom video background upload
376
- customVideoInput.addEventListener('change', (e) => {
377
- const file = e.target.files[0];
378
- if (file) {
379
- if (backgroundVideoElement) {
380
- backgroundVideoElement.pause();
381
- backgroundVideoElement.remove();
382
- }
383
-
384
- backgroundVideoElement = document.createElement('video');
385
- backgroundVideoElement.autoplay = true;
386
- backgroundVideoElement.loop = true;
387
- backgroundVideoElement.muted = true;
388
- backgroundVideoElement.src = URL.createObjectURL(file);
389
- backgroundVideoElement.style.display = 'none';
390
- document.body.appendChild(backgroundVideoElement);
391
-
392
- bgVideo.classList.add('active');
393
- }
394
- });
395
-
396
- // Toggle effect
397
- toggleEffect.addEventListener('change', async () => {
398
- if (toggleEffect.checked) {
399
- await initBodyPix();
400
- } else {
401
- stopProcessing();
402
- }
403
- });
404
-
405
- // Toggle preview
406
- togglePreview.addEventListener('change', () => {
407
- if (togglePreview.checked) {
408
- if (toggleEffect.checked) {
409
- outputCanvas.classList.remove('hidden');
410
- videoInput.classList.add('hidden');
411
- } else {
412
- videoInput.classList.remove('hidden');
413
- }
414
- } else {
415
- outputCanvas.classList.add('hidden');
416
- videoInput.classList.add('hidden');
417
- }
418
- });
419
-
420
- // Video source selection
421
- videoSourceSelect.addEventListener('change', () => {
422
- const deviceId = videoSourceSelect.value;
423
- if (deviceId) {
424
- startVideo(deviceId);
425
- } else {
426
- stopVideo();
427
- }
428
- });
429
-
430
- // Copy OBS link
431
- copyOBSLink.addEventListener('click', () => {
432
- const currentUrl = window.location.href;
433
- navigator.clipboard.writeText(currentUrl).then(() => {
434
- const originalText = copyOBSLink.textContent;
435
- copyOBSLink.innerHTML = `
436
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
437
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
438
- </svg>
439
- Copied!
440
- `;
441
- setTimeout(() => {
442
- copyOBSLink.innerHTML = originalText;
443
- }, 2000);
444
- });
445
- });
446
-
447
- // Start FPS counter
448
- setInterval(updateFPS, 1000);
449
- });
450
-
451
- // Load available video sources
452
- async function loadVideoSources() {
453
- try {
454
- const devices = await navigator.mediaDevices.enumerateDevices();
455
- const videoDevices = devices.filter(device => device.kind === 'videoinput');
456
-
457
- videoDevices.forEach(device => {
458
- const option = document.createElement('option');
459
- option.value = device.deviceId;
460
- option.text = device.label || `Camera ${videoSourceSelect.length + 1}`;
461
- videoSourceSelect.appendChild(option);
462
- });
463
-
464
- // Select the first camera by default if available
465
- if (videoDevices.length > 0) {
466
- videoSourceSelect.value = videoDevices[0].deviceId;
467
- startVideo(videoDevices[0].deviceId);
468
- }
469
- } catch (err) {
470
- console.error('Error enumerating devices:', err);
471
- }
472
- }
473
-
474
- // Start video stream
475
- async function startVideo(deviceId) {
476
  try {
477
- const constraints = {
478
- video: {
479
- deviceId: { exact: deviceId },
480
- width: { ideal: 1280 },
481
- height: { ideal: 720 }
482
- }
483
- };
484
-
485
- const stream = await navigator.mediaDevices.getUserMedia(constraints);
486
- videoInput.srcObject = stream;
487
- noVideoOverlay.classList.add('hidden');
488
-
489
- // Show video input by default
490
- if (!toggleEffect.checked) {
491
- videoInput.classList.remove('hidden');
492
- outputCanvas.classList.add('hidden');
493
- }
494
-
495
- // Start processing if effect is enabled
496
- if (toggleEffect.checked) {
497
- await initBodyPix();
498
- videoInput.classList.add('hidden');
499
- outputCanvas.classList.remove('hidden');
500
- }
501
  } catch (err) {
502
- console.error('Error starting video:', err);
503
- noVideoOverlay.textContent = 'Error accessing camera';
504
  }
505
  }
506
-
507
- // Stop video stream
508
- function stopVideo() {
509
- if (videoInput.srcObject) {
510
- videoInput.srcObject.getTracks().forEach(track => track.stop());
511
- videoInput.srcObject = null;
 
 
 
 
 
512
  }
513
- videoInput.classList.add('hidden');
514
- outputCanvas.classList.add('hidden');
515
- noVideoOverlay.classList.remove('hidden');
516
- stopProcessing();
517
- }
518
-
519
- // Initialize BodyPix model
520
- async function initBodyPix() {
521
- if (net) return;
522
-
523
- processingOverlay.classList.remove('hidden');
524
- outputCanvas.classList.remove('hidden');
525
-
526
- try {
527
- // Load the BodyPix model with selected quality
528
- const multiplier = parseFloat(modelQuality.value);
529
- net = await bodyPix.load({
530
- architecture: 'MobileNetV1',
531
- outputStride: 16,
532
- multiplier: multiplier,
533
- quantBytes: 2
534
- });
535
-
536
- processingOverlay.classList.add('hidden');
537
- startProcessing();
538
- } catch (err) {
539
- console.error('Error loading BodyPix model:', err);
540
- processingOverlay.textContent = 'Error loading AI model';
541
- toggleEffect.checked = false;
542
  }
543
- }
544
-
545
- // Start background removal processing
546
- function startProcessing() {
547
- if (!net || isProcessing) return;
548
-
549
- isProcessing = true;
550
- outputCanvas.width = videoInput.videoWidth;
551
- outputCanvas.height = videoInput.videoHeight;
552
-
553
- processFrame();
554
- }
555
-
556
- // Stop background removal processing
557
- function stopProcessing() {
558
- isProcessing = false;
559
- if (net) {
560
- // net.dispose(); // Don't dispose if we might reuse it
561
- net = null;
 
 
 
 
 
 
 
 
 
 
 
 
562
  }
563
- }
564
-
565
- // Process each video frame
566
- async function processFrame() {
567
- if (!isProcessing || !net || videoInput.readyState < 2) {
568
- if (isProcessing) {
569
- requestAnimationFrame(processFrame);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
  }
571
- return;
572
- }
573
-
574
- const startTime = performance.now();
575
-
576
- try {
577
- // Perform segmentation
578
- const segmentation = await net.segmentPerson(videoInput, {
579
- flipHorizontal: false,
580
- internalResolution: 'medium',
581
- segmentationThreshold: 0.7
582
  });
583
-
584
- // Get canvas context
585
- const ctx = outputCanvas.getContext('2d');
586
-
587
- // Draw the background based on selection
588
- ctx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
589
-
590
- if (currentBackground === 'transparent') {
591
- // For transparent, we just leave it clear
592
- } else if (currentBackground === 'black') {
593
- ctx.fillStyle = 'black';
594
- ctx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
595
- } else if (currentBackground === 'gray') {
596
- ctx.fillStyle = '#4B5563';
597
- ctx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
598
- } else if (currentBackground === 'green') {
599
- ctx.fillStyle = '#059669';
600
- ctx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
601
- } else if (currentBackground === 'customImage' && customBackgroundImage) {
602
- // Calculate aspect ratio and draw custom background
603
- const imgAspect = customBackgroundImage.width / customBackgroundImage.height;
604
- const canvasAspect = outputCanvas.width / outputCanvas.height;
605
-
606
- if (imgAspect > canvasAspect) {
607
- // Image is wider than canvas
608
- const scale = outputCanvas.width / customBackgroundImage.width;
609
- const scaledHeight = customBackgroundImage.height * scale;
610
- const y = (outputCanvas.height - scaledHeight) / 2;
611
- ctx.drawImage(customBackgroundImage, 0, y, outputCanvas.width, scaledHeight);
612
- } else {
613
- // Image is taller than canvas
614
- const scale = outputCanvas.height / customBackgroundImage.height;
615
- const scaledWidth = customBackgroundImage.width * scale;
616
- const x = (outputCanvas.width - scaledWidth) / 2;
617
- ctx.drawImage(customBackgroundImage, x, 0, scaledWidth, outputCanvas.height);
618
- }
619
- } else if (currentBackground === 'customVideo' && backgroundVideoElement) {
620
- // Draw video background
621
- const videoAspect = backgroundVideoElement.videoWidth / backgroundVideoElement.videoHeight;
622
- const canvasAspect = outputCanvas.width / outputCanvas.height;
623
-
624
- if (videoAspect > canvasAspect) {
625
- // Video is wider than canvas
626
- const scale = outputCanvas.width / backgroundVideoElement.videoWidth;
627
- const scaledHeight = backgroundVideoElement.videoHeight * scale;
628
- const y = (outputCanvas.height - scaledHeight) / 2;
629
- ctx.drawImage(backgroundVideoElement, 0, y, outputCanvas.width, scaledHeight);
630
- } else {
631
- // Video is taller than canvas
632
- const scale = outputCanvas.height / backgroundVideoElement.videoHeight;
633
- const scaledWidth = backgroundVideoElement.videoWidth * scale;
634
- const x = (outputCanvas.width - scaledWidth) / 2;
635
- ctx.drawImage(backgroundVideoElement, x, 0, scaledWidth, outputCanvas.height);
636
- }
637
- } else if (currentBackground === 'blurred') {
638
- // Draw blurred background
639
- ctx.drawImage(videoInput, 0, 0, outputCanvas.width, outputCanvas.height);
640
- applyBlurEffect(ctx, outputCanvas.width, outputCanvas.height, parseInt(bgBlur.value));
641
- }
642
-
643
- // Draw the foreground (person) with alpha mask
644
- const foregroundColor = { r: 255, g: 255, b: 255, a: 255 };
645
- const backgroundColor = { r: 0, g: 0, b: 0, a: 0 };
646
- const edgeBlurAmount = parseInt(edgeSmoothness.value);
647
- const brightness = parseInt(fgBrightness.value) / 100;
648
-
649
- bodyPix.drawBokehEffect(
650
- outputCanvas,
651
- videoInput,
652
- segmentation,
653
- parseFloat(bgBlur.value),
654
- foregroundColor,
655
- backgroundColor,
656
- edgeBlurAmount,
657
- brightness
658
- );
659
-
660
- // Update performance stats
661
- const endTime = performance.now();
662
- const processingTime = endTime - startTime;
663
- processingTimeElement.textContent = `${processingTime.toFixed(1)} ms`;
664
-
665
- frameCount++;
666
-
667
- // Process next frame
668
- requestAnimationFrame(processFrame);
669
- } catch (err) {
670
- console.error('Error processing frame:', err);
671
- isProcessing = false;
672
- }
673
- }
674
-
675
- // Apply blur effect to context
676
- function applyBlurEffect(ctx, width, height, radius) {
677
- if (radius <= 0) return;
678
-
679
- // Create temporary canvas for blur effect
680
- const tempCanvas = document.createElement('canvas');
681
- tempCanvas.width = width;
682
- tempCanvas.height = height;
683
- const tempCtx = tempCanvas.getContext('2d');
684
-
685
- // Draw the current content to temp canvas
686
- tempCtx.drawImage(outputCanvas, 0, 0, width, height);
687
-
688
- // Apply blur by drawing scaled down and up
689
- const iterations = Math.min(radius, 10);
690
- const scale = 1 / (iterations * 0.5 + 1);
691
-
692
- for (let i = 0; i < iterations; i++) {
693
- // Scale down
694
- const scaledWidth = width * scale;
695
- const scaledHeight = height * scale;
696
-
697
- tempCtx.drawImage(
698
- tempCanvas,
699
- 0, 0, width, height,
700
- 0, 0, scaledWidth, scaledHeight
701
- );
702
-
703
- // Scale up
704
- tempCtx.drawImage(
705
- tempCanvas,
706
- 0, 0, scaledWidth, scaledHeight,
707
- 0, 0, width, height
708
- );
709
- }
710
-
711
- // Draw the blurred content back to main canvas
712
- ctx.drawImage(tempCanvas, 0, 0, width, height);
713
- }
714
-
715
- // Update FPS counter
716
- function updateFPS() {
717
- const now = performance.now();
718
- if (lastTimestamp) {
719
- const delta = (now - lastTimestamp) / 1000;
720
- fps = Math.round(frameCount / delta);
721
- fpsCounterElement.textContent = fps;
722
- }
723
- lastTimestamp = now;
724
- frameCount = 0;
725
- }
726
  </script>
727
- <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=TDN-M/plugin" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
728
  </html>
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>OBS AI Background Remover</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <script src="https://cdn.jsdelivr.net/npm/obs-websocket-js@5"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  </head>
10
+ <body>
11
+ <div class="card">
12
+ <h1>Live Preview</h1>
13
+ <div style="position: relative;">
14
+ <video id="preview" autoplay style="width: 100%; height: auto;"></video>
15
+ <p id="loading" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);">Loading AI Model...</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  </div>
17
+ <p id="no-video">No video source selected</p>
18
+ </div>
19
+
20
+ <div class="card">
21
+ <h1>Background Replacement</h1>
22
+ <label><input type="radio" name="bg-type" value="transparent"> Transparent</input></label>
23
+ <label><input type="radio" name="bg-type" value="black"> Solid Black</input></label>
24
+ <label><input type="radio" name="bg-type" value="gray"> Solid Gray</input></label>
25
+ <label><input type="radio" name="bg-type" value="green"> Solid Green</input></label>
26
+ <label><input type="radio" name="bg-type" value="custom-image"> Custom Image <input type="file" id="custom-image-input" accept="image/*"></input></label>
27
+ <label><input type="radio" name="bg-type" value="custom-video"> Custom Video <input type="file" id="custom-video-input" accept="video/*"></input></label>
28
+ <label><input type="radio" name="bg-type" value="blurred" id="blur-checkbox"> Blurred</input></label>
29
+ </div>
30
+
31
+ <div class="card">
32
+ <h1>Controls</h1>
33
+ <label>Video Source: <select id="video-source"><option>Select a video source</option></select></label>
34
+ <label><input type="checkbox" id="enable-bg-removal"> Enable Background Removal</input></label>
35
+ <label><input type="checkbox" id="show-preview"> Show Preview</input></label>
36
+ </div>
37
+
38
+ <div class="card">
39
+ <h1>Performance</h1>
40
+ <p>Processing Time: <span id="processing-time">0 ms</span></p>
41
+ <p>FPS: <span id="fps">0</span></p>
42
+ </div>
43
+
44
+ <div class="card">
45
+ <h1>OBS Integration</h1>
46
+ <p>Add this as a Browser Source in OBS with these settings:</p>
47
+ <p>Width: 1920</p>
48
+ <p>Height: 1080</p>
49
+ <p>Custom CSS: None</p>
50
+ <button id="copy-url">Copy OBS Browser Source URL</button>
51
  </div>
52
 
53
  <script>
54
+ const obs = new OBSWebSocket();
55
+
56
+ async function connectToOBS() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  try {
58
+ await obs.connect('ws://localhost:4455', 'your-password');
59
+ console.log('Connected to OBS WebSocket');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  } catch (err) {
61
+ console.error('Failed to connect to OBS:', err);
 
62
  }
63
  }
64
+
65
+ connectToOBS();
66
+
67
+ // Stream video
68
+ obs.on('SourceFilterVideoFrame', (data) => {
69
+ if (data.sourceName === 'AI_remove_bg') {
70
+ const video = document.getElementById('preview');
71
+ const blob = new Blob([data.frameData], { type: 'video/webm' });
72
+ video.src = URL.createObjectURL(blob);
73
+ document.getElementById('loading').style.display = 'none';
74
+ document.getElementById('no-video').style.display = 'none';
75
  }
76
+ });
77
+
78
+ // Update performance metrics
79
+ obs.on('CustomEvent', (data) => {
80
+ if (data.event === 'AI_remove_bg_metrics') {
81
+ document.getElementById('processing-time').textContent = `${data.processingTime} ms`;
82
+ document.getElementById('fps').textContent = data.fps;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  }
84
+ });
85
+
86
+ // Handle controls
87
+ document.getElementById('enable-bg-removal').addEventListener('change', async (e) => {
88
+ await obs.call('SetSourceFilterSettings', {
89
+ sourceName: 'AI_remove_bg',
90
+ filterName: 'AI_remove_bg',
91
+ filterSettings: { seg_disen: !e.target.checked }
92
+ });
93
+ });
94
+
95
+ document.getElementById('blur-checkbox').addEventListener('change', async (e) => {
96
+ await obs.call('SetSourceFilterSettings', {
97
+ sourceName: 'AI_remove_bg',
98
+ filterName: 'AI_remove_bg',
99
+ filterSettings: { blurbgbool: e.target.checked }
100
+ });
101
+ });
102
+
103
+ document.getElementById('custom-image-input').addEventListener('change', async (e) => {
104
+ const file = e.target.files[0];
105
+ if (file) {
106
+ const reader = new FileReader();
107
+ reader.onload = async () => {
108
+ await obs.call('SetSourceFilterSettings', {
109
+ sourceName: 'AI_remove_bg',
110
+ filterName: 'AI_remove_bg',
111
+ filterSettings: { setbg: true, bgpic: reader.result }
112
+ });
113
+ };
114
+ reader.readAsDataURL(file);
115
  }
116
+ });
117
+
118
+ document.querySelectorAll('input[name="bg-type"]').forEach((input) => {
119
+ input.addEventListener('change', async (e) => {
120
+ let settings = {};
121
+ switch (e.target.value) {
122
+ case 'transparent':
123
+ settings = { setbg: false, setcolor: false, blurbgbool: false };
124
+ break;
125
+ case 'black':
126
+ settings = { setcolor: true, bgColor: 0x000000 };
127
+ break;
128
+ case 'gray':
129
+ settings = { setcolor: true, bgColor: 0x808080 };
130
+ break;
131
+ case 'green':
132
+ settings = { setcolor: true, bgColor: 0x00FF00 };
133
+ break;
134
+ case 'blurred':
135
+ settings = { blurbgbool: true, setbg: false, setcolor: false };
136
+ break;
137
  }
138
+ await obs.call('SetSourceFilterSettings', {
139
+ sourceName: 'AI_remove_bg',
140
+ filterName: 'AI_remove_bg',
141
+ filterSettings: settings
 
 
 
 
 
 
 
142
  });
143
+ });
144
+ });
145
+
146
+ document.getElementById('copy-url').addEventListener('click', () => {
147
+ const url = window.location.href;
148
+ navigator.clipboard.writeText(url).then(() => {
149
+ alert('URL copied to clipboard!');
150
+ });
151
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  </script>
153
+ </body>
154
  </html>