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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +717 -77
index.html CHANGED
@@ -4,85 +4,725 @@
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
  </head>
9
- <body>
10
- <div class="card">
11
- <h1>OBS AI Background Remover</h1>
12
- <p>Live Preview</p>
13
- <div>
14
- <video id="live-preview" autoplay style="width: 100%; border-radius: 8px;"></video>
15
- <canvas id="output-canvas" style="display: none;"></canvas>
16
- <p id="video-status">No video source selected</p>
17
- </div>
18
-
19
- <h1>Background Replacement</h1>
20
- <div>
21
- <label><input type="radio" name="background" value="transparent" checked> Transparent</label>
22
- <label><input type="radio" name="background" value="solid-black"> Solid Black</label>
23
- <label><input type="radio" name="background" value="solid-gray"> Solid Gray</label>
24
- <label><input type="radio" name="background" value="solid-green"> Solid Green</label>
25
- <label><input type="radio" name="background" value="custom-image"> Custom Image
26
- <input type="file" id="custom-image-input" accept="image/*">
27
- </label>
28
- <label><input type="radio" name="background" value="custom-video"> Custom Video
29
- <input type="file" id="custom-video-input" accept="video/*">
30
- </label>
31
- <label><input type="radio" name="background" value="blurred"> Blurred</label>
32
- </div>
33
-
34
- <h1>Controls</h1>
35
- <div>
36
- <label>Video Source</label>
37
- <select id="video-source">
38
- <option value="">Select a video source</option>
39
- </select>
40
- </div>
41
- <div>
42
- <label><input type="checkbox" id="enable-bg-removal"> Enable Background Removal</label>
43
- <label><input type="checkbox" id="show-preview" checked> Show Preview</label>
44
- </div>
45
-
46
- <h1>Settings</h1>
47
- <div>
48
- <label>AI Model Quality</label>
49
- <select id="model-quality">
50
- <option value="fast">Fast (Low Quality)</option>
51
- <option value="balanced" selected>Balanced</option>
52
- <option value="high">High Quality (Slow)</option>
53
- </select>
54
- </div>
55
- <div>
56
- <label>Edge Smoothness</label>
57
- <input type="range" id="edge-smoothness" min="0" max="10" value="5">
58
- </div>
59
- <div>
60
- <label>Background Blur</label>
61
- <input type="range" id="background-blur" min="0" max="20" value="0">
62
- </div>
63
- <div>
64
- <label>Foreground Brightness</label>
65
- <input type="range" id="foreground-brightness" min="0.5" max="1.5" step="0.1" value="1">
66
- </div>
67
-
68
- <h1>Performance</h1>
69
- <div>
70
- <p>Processing Time: <span id="processing-time">0 ms</span></p>
71
- <p>FPS: <span id="fps">0</span></p>
72
- </div>
73
-
74
- <h1>OBS Integration</h1>
75
- <p>Add this as a Browser Source in OBS with these settings:</p>
76
- <div>
77
- <p>Width: <span>1920</span></p>
78
- <p>Height: <span>1080</span></p>
79
- <p>Custom CSS: <span>None</span></p>
80
- </div>
81
- <div>
82
- <button id="copy-url">Copy OBS Browser Source URL</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  </div>
84
  </div>
85
 
86
- <script src="script.js"></script>
87
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  </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
+ <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>