TDN-M commited on
Commit
9b97325
·
verified ·
1 Parent(s): c04dbce

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +216 -691
index.html CHANGED
@@ -4,725 +4,250 @@
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/[email protected]/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/@mediapipe/selfie_segmentation/selfie_segmentation.js" crossorigin="anonymous"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  </head>
10
+ <body>
11
+ <div class="card">
12
+ <h1>OBS AI Background Remover</h1>
13
+ <div>
14
+ <h2>Live Preview</h2>
15
+ <video id="videoPreview" autoplay playsinline></video>
16
+ <canvas id="canvasOutput" width="1920" height="1080" style="display: none;"></canvas>
17
+ <p id="status">Loading AI Model...</p>
18
+ </div>
19
+
20
+ <div>
21
+ <h2>Background Replacement</h2>
22
+ <div>
23
+ <input type="radio" name="bgType" id="bgTransparent" value="transparent" checked>
24
+ <label for="bgTransparent">Transparent</label>
25
+ </div>
26
+ <div>
27
+ <input type="radio" name="bgType" id="bgBlack" value="black">
28
+ <label for="bgBlack">Solid Black</label>
29
+ </div>
30
+ <div>
31
+ <input type="radio" name="bgType" id="bgGray" value="gray">
32
+ <label for="bgGray">Solid Gray</label>
33
+ </div>
34
+ <div>
35
+ <input type="radio" name="bgType" id="bgGreen" value="green">
36
+ <label for="bgGreen">Solid Green</label>
37
+ </div>
38
+ <div>
39
+ <input type="radio" name="bgType" id="bgCustomImage" value="customImage">
40
+ <label for="bgCustomImage">Custom Image</label>
41
+ <input type pigeonhole="file" id="customImageInput" accept="image/*">
42
+ </div>
43
+ <div>
44
+ <input type="radio" name="bgType" id="bgCustomVideo" value="customVideo">
45
+ <label for="bgCustomVideo">Custom Video</label>
46
+ <input type="file" id="customVideoInput" accept="video/*">
47
+ </div>
48
+ <div>
49
+ <input type="radio" name="bgType" id="bgBlur" value="blur">
50
+ <label for="bgBlur">Blurred</label>
51
+ </div>
52
+ </div>
53
+
54
+ <div>
55
+ <h2>Controls</h2>
56
+ <div>
57
+ <label for="videoSource">Video Source</label>
58
+ <select id="videoSource">
59
+ <option value="">Select a video source</option>
60
+ </select>
61
+ </div>
62
+ <div>
63
+ <input type="checkbox" id="enableBgRemoval">
64
+ <label for="enableBgRemoval">Enable Background Removal</label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  </div>
66
+ <div>
67
+ <input type="checkbox" id="showPreview" checked>
68
+ <label for="showPreview">Show Preview</label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  </div>
70
  </div>
71
+
72
+ <div>
73
+ <h2>Performance</h2>
74
+ <p>Processing Time: <span id="processingTime">0</span> ms</p>
75
+ <p>FPS: <span id="fps">0</span></p>
76
+ </div>
77
+
78
+ <div>
79
+ <h2>OBS Integration</h2>
80
+ <p>Add this as a Browser Source in OBS with these settings:</p>
81
+ <p>Width: 1920</p>
82
+ <p>Height: 1080</p>
83
+ <p>Custom CSS: None</p>
84
+ <button id="copyUrl">Copy OBS Browser Source URL</button>
85
+ </div>
86
  </div>
87
 
88
  <script>
89
+ const videoElement = document.getElementById('videoPreview');
90
+ const canvasElement = document.getElementById('canvasOutput');
91
+ const canvasCtx = canvasElement.getContext('2d');
92
+ const statusElement = document.getElementById('status');
 
93
  const videoSourceSelect = document.getElementById('videoSource');
94
+ const enableBgRemoval = document.getElementById('enableBgRemoval');
95
+ const showPreview = document.getElementById('showPreview');
 
 
 
 
 
 
96
  const processingTimeElement = document.getElementById('processingTime');
97
+ const fpsElement = document.getElementById('fps');
98
+ const copyUrlButton = document.getElementById('copyUrl');
99
+ let selfieSegmentation;
100
+ let lastFrameTime = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  let frameCount = 0;
102
+ let lastFpsUpdate = 0;
103
+
104
+ // Initialize MediaPipe Selfie Segmentation
105
+ async function initModel() {
106
+ selfieSegmentation = new SelfieSegmentation({
107
+ locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/${file}`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  });
109
+ selfieSegmentation.setOptions({
110
+ modelSelection: 1, // 0 for general, 1 for landscape
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  });
112
+ selfieSegmentation.onResults(onResults);
113
+ await selfieSegmentation.initialize();
114
+ statusElement.textContent = 'AI Model Loaded';
115
+ }
116
+
117
+ // Populate video sources
118
+ async function populateVideoSources() {
119
+ const devices = await navigator.mediaDevices.enumerateDevices();
120
+ videoSourceSelect.innerHTML = '<option value="">Select a video source</option>';
121
+ devices.forEach(device => {
122
+ if (device.kind === 'videoinput') {
 
123
  const option = document.createElement('option');
124
  option.value = device.deviceId;
125
+ option.text = device.label || `Camera ${videoSourceSelect.options.length + 1}`;
126
  videoSourceSelect.appendChild(option);
 
 
 
 
 
 
127
  }
128
+ });
 
 
129
  }
130
+
131
  // Start video stream
132
  async function startVideo(deviceId) {
133
+ const stream = await navigator.mediaDevices.getUserMedia({
134
+ video: { deviceId: deviceId ? { exact: deviceId } : undefined }
135
+ });
136
+ videoElement.srcObject = stream;
137
+ videoElement.play();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  }
139
+
140
+ // Process video frames
141
+ function onResults(results) {
142
+ if (!enableBgRemoval.checked) {
143
+ canvasCtx.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
144
+ updatePerformance();
 
145
  return;
146
  }
147
+
148
+ canvasCtx.save();
149
+ canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
150
+
151
+ // Draw segmented background
152
+ const bgType = document.querySelector('input[name="bgType"]:checked').value;
153
+ if (bgType === 'transparent') {
154
+ canvasCtx.globalCompositeOperation = 'destination-over';
155
+ canvasCtx.fillStyle = 'rgba(0, 0, 0, 0)';
156
+ canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height);
157
+ } else if (bgType === 'black') {
158
+ canvasCtx.fillStyle = 'black';
159
+ canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height);
160
+ } else if (bgType === 'gray') {
161
+ canvasCtx.fillStyle = 'gray';
162
+ canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height);
163
+ } else if (bgType === 'green') {
164
+ canvasCtx.fillStyle = 'green';
165
+ canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height);
166
+ } else if (bgType === 'customImage') {
167
+ const img = document.getElementById('customImageInput').files[0];
168
+ if (img) {
169
+ const image = new Image();
170
+ image.src = URL.createObjectURL(img);
171
+ canvasCtx.drawImage(image, 0, 0, canvasElement.width, canvasElement.height);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  }
173
+ } else if (bgType === 'customVideo') {
174
+ const vid = document.getElementById('customVideoInput').files[0];
175
+ if (vid) {
176
+ const video = document.createElement('video');
177
+ video.src = URL.createObjectURL(vid);
178
+ video.loop = true;
179
+ video.play();
180
+ canvasCtx.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
181
+ }
182
+ } else if (bgType === 'blur') {
183
+ canvasCtx.filter = 'blur(10px)';
184
+ canvasCtx.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
185
+ canvasCtx.filter = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  }
187
+
188
+ // Draw foreground (person)
189
+ canvasCtx.globalCompositeOperation = 'destination-atop';
190
+ canvasCtx.drawImage(results.segmentationMask, 0, 0, canvasElement.width, canvasElement.height);
191
+ canvasCtx.globalCompositeOperation = 'source-over';
192
+ canvasCtx.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
193
+ canvasCtx.restore();
194
+
195
+ updatePerformance();
196
  }
197
+
198
+ // Update performance metrics
199
+ function updatePerformance() {
200
+ const now = performance.now();
201
+ frameCount++;
202
+ if (now - lastFpsUpdate > 1000) {
203
+ const fps = frameCount / ((now - lastFpsUpdate) / 1000);
204
+ fpsElement.textContent = fps.toFixed(1);
205
+ frameCount = 0;
206
+ lastFpsUpdate = now;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  }
208
+ processingTimeElement.textContent = (now - lastFrameTime).toFixed(1);
209
+ lastFrameTime = now;
 
210
  }
211
+
212
+ // Copy URL
213
+ copyUrlButton.addEventListener('click', () => {
214
+ const url = window.location.href;
215
+ navigator.clipboard.writeText(url).then(() => {
216
+ alert('URL copied to clipboard!');
217
+ });
218
+ });
219
+
220
+ // Event listeners
221
+ videoSourceSelect.addEventListener('change', () => {
222
+ startVideo(videoSourceSelect.value);
223
+ });
224
+
225
+ enableBgRemoval.addEventListener('change', () => {
226
+ canvasElement.style.display = enableBgRemoval.checked ? 'block' : 'none';
227
+ videoElement.style.display = !enableBgRemoval.checked ? 'block' : 'none';
228
+ });
229
+
230
+ showPreview.addEventListener('change', () => {
231
+ document.querySelector('.card').style.display = showPreview.checked ? 'block' : 'none';
232
+ });
233
+
234
+ // Process video frames
235
+ async function processFrame() {
236
+ if (enableBgRemoval.checked) {
237
+ await selfieSegmentation.send({ image: videoElement });
238
  }
239
+ requestAnimationFrame(processFrame);
 
240
  }
241
+
242
+ // Initialize
243
+ async function init() {
244
+ await initModel();
245
+ await populateVideoSources();
246
+ await startVideo();
247
+ processFrame();
248
+ }
249
+
250
+ init();
251
  </script>
252
+ </body>
253
  </html>