openfree commited on
Commit
d680ec8
Β·
verified Β·
1 Parent(s): d16c3be

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +445 -44
app.py CHANGED
@@ -23,12 +23,313 @@ birefnet.to(device)
23
  birefnet_lite = AutoModelForImageSegmentation.from_pretrained("ZhengPeng7/BiRefNet_lite", trust_remote_code=True)
24
  birefnet_lite.to(device)
25
 
 
 
 
 
 
26
  transform_image = transforms.Compose([
27
- transforms.Resize((768, 768)),
28
  transforms.ToTensor(),
29
  transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
30
  ])
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  # Function to process a single frame
33
  def process_frame(frame, bg_type, bg, fast_mode, bg_frame_index, background_frames, color):
34
  try:
@@ -38,21 +339,21 @@ def process_frame(frame, bg_type, bg, fast_mode, bg_frame_index, background_fram
38
  elif bg_type == "Image":
39
  processed_image = process(pil_image, bg, fast_mode)
40
  elif bg_type == "Video":
41
- background_frame = background_frames[bg_frame_index] # Access the correct background frame
42
  bg_frame_index += 1
43
  background_image = Image.fromarray(background_frame)
44
  processed_image = process(pil_image, background_image, fast_mode)
45
  else:
46
- processed_image = pil_image # Default to original image if no background is selected
47
  return np.array(processed_image), bg_frame_index
48
  except Exception as e:
49
  print(f"Error processing frame: {e}")
50
  return frame, bg_frame_index
51
 
52
  @spaces.GPU
53
- def fn(vid, bg_type="Color", bg_image=None, bg_video=None, color="#00FF00", fps=0, video_handling="slow_down", fast_mode=True, max_workers=10):
54
  try:
55
- start_time = time.time() # Start the timer
56
  video = VideoFileClip(vid)
57
  if fps == 0:
58
  fps = video.fps
@@ -61,46 +362,55 @@ def fn(vid, bg_type="Color", bg_image=None, bg_video=None, color="#00FF00", fps=
61
  frames = list(video.iter_frames(fps=fps))
62
 
63
  processed_frames = []
64
- yield gr.update(visible=True), gr.update(visible=False), f"Processing started... Elapsed time: 0 seconds"
65
 
66
  if bg_type == "Video":
67
  background_video = VideoFileClip(bg_video)
68
  if background_video.duration < video.duration:
69
  if video_handling == "slow_down":
70
  background_video = background_video.fx(vfx.speedx, factor=video.duration / background_video.duration)
71
- else: # video_handling == "loop"
72
  background_video = concatenate_videoclips([background_video] * int(video.duration / background_video.duration + 1))
73
  background_frames = list(background_video.iter_frames(fps=fps))
74
  else:
75
  background_frames = None
76
 
77
- bg_frame_index = 0 # Initialize background frame index
78
 
79
  with ThreadPoolExecutor(max_workers=max_workers) as executor:
80
- # Pass bg_frame_index as part of the function arguments
81
  futures = [executor.submit(process_frame, frames[i], bg_type, bg_image, fast_mode, bg_frame_index + i, background_frames, color) for i in range(len(frames))]
82
  for i, future in enumerate(futures):
83
- result, _ = future.result() # No need to update bg_frame_index here
84
  processed_frames.append(result)
85
  elapsed_time = time.time() - start_time
86
- yield result, None, f"Processing frame {i+1}/{len(frames)}... Elapsed time: {elapsed_time:.2f} seconds"
 
87
 
88
  processed_video = ImageSequenceClip(processed_frames, fps=fps)
89
  processed_video = processed_video.with_audio(audio)
90
 
91
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_file:
92
  temp_filepath = temp_file.name
93
- processed_video.write_videofile(temp_filepath, codec="libx264")
 
 
 
 
 
 
 
 
 
94
 
95
  elapsed_time = time.time() - start_time
96
- yield gr.update(visible=False), gr.update(visible=True), f"Processing complete! Elapsed time: {elapsed_time:.2f} seconds"
97
- yield processed_frames[-1], temp_filepath, f"Processing complete! Elapsed time: {elapsed_time:.2f} seconds"
98
 
99
  except Exception as e:
100
  print(f"Error: {e}")
101
  elapsed_time = time.time() - start_time
102
- yield gr.update(visible=False), gr.update(visible=True), f"Error processing video: {e}. Elapsed time: {elapsed_time:.2f} seconds"
103
- yield None, f"Error processing video: {e}", f"Error processing video: {e}. Elapsed time: {elapsed_time:.2f} seconds"
104
 
105
  def process(image, bg, fast_mode=False):
106
  image_size = image.size
@@ -111,50 +421,129 @@ def process(image, bg, fast_mode=False):
111
  preds = model(input_images)[-1].sigmoid().cpu()
112
  pred = preds[0].squeeze()
113
  pred_pil = transforms.ToPILImage()(pred)
114
- mask = pred_pil.resize(image_size)
115
 
116
  if isinstance(bg, str) and bg.startswith("#"):
117
  color_rgb = tuple(int(bg[i:i+2], 16) for i in (1, 3, 5))
118
  background = Image.new("RGBA", image_size, color_rgb + (255,))
119
  elif isinstance(bg, Image.Image):
120
- background = bg.convert("RGBA").resize(image_size)
121
  else:
122
- background = Image.open(bg).convert("RGBA").resize(image_size)
123
 
124
  image = Image.composite(image, background, mask)
125
  return image
126
 
127
- with gr.Blocks(theme=gr.themes.Ocean()) as demo:
128
- gr.Markdown("# Video Background Remover & Changer\n### You can replace image background with any color, image or video.\nNOTE: As this Space is running on ZERO GPU it has limit. It can handle approx 200 frames at once. So, if you have a big video than use small chunks or Duplicate this space.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
  with gr.Row():
131
- in_video = gr.Video(label="Input Video", interactive=True)
132
- stream_image = gr.Image(label="Streaming Output", visible=False)
133
- out_video = gr.Video(label="Final Output Video")
 
 
 
 
 
 
 
 
 
 
 
134
 
135
- submit_button = gr.Button("Change Background", interactive=True)
 
 
 
 
 
136
 
137
  with gr.Row():
138
- fps_slider = gr.Slider(
139
- minimum=0,
140
- maximum=60,
141
- step=1,
142
- value=0,
143
- label="Output FPS (0 will inherit the original fps value)",
144
- interactive=True
145
- )
146
- bg_type = gr.Radio(["Color", "Image", "Video"], label="Background Type", value="Color", interactive=True)
147
- color_picker = gr.ColorPicker(label="Background Color", value="#00FF00", visible=True, interactive=True)
148
- bg_image = gr.Image(label="Background Image", type="filepath", visible=False, interactive=True)
149
- bg_video = gr.Video(label="Background Video", visible=False, interactive=True)
150
-
151
- with gr.Column(visible=False) as video_handling_options:
152
- video_handling_radio = gr.Radio(["slow_down", "loop"], label="Video Handling", value="slow_down", interactive=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
- fast_mode_checkbox = gr.Checkbox(label="Fast Mode (Use BiRefNet_lite)", value=True, interactive=True)
155
- max_workers_slider = gr.Slider( minimum=1, maximum=32, step=1, value=10, label="Max Workers", info="Determines how many frames to process in parallel", interactive=True )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
- time_textbox = gr.Textbox(label="Time Elapsed", interactive=False)
 
 
 
 
158
 
159
  def update_visibility(bg_type):
160
  if bg_type == "Color":
@@ -166,7 +555,11 @@ with gr.Blocks(theme=gr.themes.Ocean()) as demo:
166
  else:
167
  return gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
168
 
169
- bg_type.change(update_visibility, inputs=bg_type, outputs=[color_picker, bg_image, bg_video, video_handling_options])
 
 
 
 
170
 
171
  examples = gr.Examples(
172
  [
@@ -186,6 +579,14 @@ with gr.Blocks(theme=gr.themes.Ocean()) as demo:
186
  inputs=[in_video, bg_type, bg_image, bg_video, color_picker, fps_slider, video_handling_radio, fast_mode_checkbox, max_workers_slider],
187
  outputs=[stream_image, out_video, time_textbox],
188
  )
 
 
 
 
 
 
 
 
189
 
190
  if __name__ == "__main__":
191
  demo.launch(show_error=True)
 
23
  birefnet_lite = AutoModelForImageSegmentation.from_pretrained("ZhengPeng7/BiRefNet_lite", trust_remote_code=True)
24
  birefnet_lite.to(device)
25
 
26
+ # ═══════════════════════════════════════════════════════════
27
+ # πŸ”§ 고퀄리티 μ„€μ • - 해상도 μ—…κ·Έλ ˆμ΄λ“œ
28
+ # ═══════════════════════════════════════════════════════════
29
+ HIGH_QUALITY_SIZE = 1024 # 768 β†’ 1024둜 μ—…κ·Έλ ˆμ΄λ“œ
30
+
31
  transform_image = transforms.Compose([
32
+ transforms.Resize((HIGH_QUALITY_SIZE, HIGH_QUALITY_SIZE)),
33
  transforms.ToTensor(),
34
  transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
35
  ])
36
 
37
+ # ═══════════════════════════════════════════════════════════
38
+ # πŸ”˜ NEUMORPHISM CSS μŠ€νƒ€μΌ
39
+ # ═══════════════════════════════════════════════════════════
40
+ NEUMORPHISM_CSS = """
41
+ /* 🎨 핡심 색상 νŒ”λ ˆνŠΈ */
42
+ :root {
43
+ --neu-bg: #e0e5ec;
44
+ --neu-shadow-dark: #a3b1c6;
45
+ --neu-shadow-light: #ffffff;
46
+ --neu-text: #4a5568;
47
+ --neu-text-dark: #2d3748;
48
+ --neu-accent: #667eea;
49
+ --neu-accent-light: #7c91f0;
50
+ --neu-success: #48bb78;
51
+ --neu-warning: #ed8936;
52
+ }
53
+
54
+ /* πŸ“¦ 전체 λ°°κ²½ */
55
+ body, .gradio-container {
56
+ background: linear-gradient(145deg, #e2e8ec, #d8dde4) !important;
57
+ min-height: 100vh;
58
+ }
59
+
60
+ .gradio-container {
61
+ max-width: 1400px !important;
62
+ margin: 0 auto !important;
63
+ padding: 30px !important;
64
+ }
65
+
66
+ /* πŸ”² 메인 μ»¨ν…Œμ΄λ„ˆ 볼둝 효과 */
67
+ .main, .contain {
68
+ background: var(--neu-bg) !important;
69
+ border-radius: 30px !important;
70
+ box-shadow:
71
+ 12px 12px 24px var(--neu-shadow-dark),
72
+ -12px -12px 24px var(--neu-shadow-light) !important;
73
+ padding: 25px !important;
74
+ }
75
+
76
+ /* πŸ“ 타이틀 μŠ€νƒ€μΌ */
77
+ h1, .markdown h1 {
78
+ color: var(--neu-text-dark) !important;
79
+ text-shadow:
80
+ 3px 3px 6px var(--neu-shadow-light),
81
+ -2px -2px 4px rgba(0,0,0,0.08) !important;
82
+ font-weight: 800 !important;
83
+ letter-spacing: -0.5px !important;
84
+ }
85
+
86
+ h3, .markdown h3 {
87
+ color: var(--neu-text) !important;
88
+ font-weight: 600 !important;
89
+ }
90
+
91
+ /* 🎬 λΉ„λ””μ˜€/이미지 μ»΄ν¬λ„ŒνŠΈ 였λͺ© 효과 */
92
+ .video-container, .image-container,
93
+ [data-testid="video"], [data-testid="image"],
94
+ .upload-container, .svelte-1uvlhfp {
95
+ background: var(--neu-bg) !important;
96
+ border-radius: 20px !important;
97
+ box-shadow:
98
+ inset 8px 8px 16px var(--neu-shadow-dark),
99
+ inset -8px -8px 16px var(--neu-shadow-light) !important;
100
+ border: none !important;
101
+ padding: 15px !important;
102
+ transition: all 0.3s ease !important;
103
+ }
104
+
105
+ /* πŸ”˜ λ²„νŠΌ 뉴λͺ¨ν”Όμ¦˜ μŠ€νƒ€μΌ */
106
+ button, .gr-button,
107
+ button.primary, button.secondary,
108
+ .gr-button-primary, .gr-button-secondary {
109
+ background: linear-gradient(145deg, #e8edf4, #d4d9e0) !important;
110
+ border: none !important;
111
+ border-radius: 50px !important;
112
+ padding: 18px 45px !important;
113
+ color: var(--neu-text-dark) !important;
114
+ font-weight: 700 !important;
115
+ font-size: 16px !important;
116
+ box-shadow:
117
+ 10px 10px 20px var(--neu-shadow-dark),
118
+ -10px -10px 20px var(--neu-shadow-light) !important;
119
+ transition: all 0.25s ease !important;
120
+ cursor: pointer !important;
121
+ }
122
+
123
+ button:hover, .gr-button:hover {
124
+ background: linear-gradient(145deg, #ecf1f8, #d8dde4) !important;
125
+ box-shadow:
126
+ 6px 6px 12px var(--neu-shadow-dark),
127
+ -6px -6px 12px var(--neu-shadow-light) !important;
128
+ transform: translateY(-2px) !important;
129
+ }
130
+
131
+ button:active, .gr-button:active {
132
+ box-shadow:
133
+ inset 6px 6px 12px var(--neu-shadow-dark),
134
+ inset -6px -6px 12px var(--neu-shadow-light) !important;
135
+ transform: translateY(0) !important;
136
+ }
137
+
138
+ /* 🎚️ μŠ¬λΌμ΄λ” μŠ€νƒ€μΌ */
139
+ input[type="range"] {
140
+ background: var(--neu-bg) !important;
141
+ border-radius: 15px !important;
142
+ box-shadow:
143
+ inset 4px 4px 8px var(--neu-shadow-dark),
144
+ inset -4px -4px 8px var(--neu-shadow-light) !important;
145
+ height: 12px !important;
146
+ }
147
+
148
+ input[type="range"]::-webkit-slider-thumb {
149
+ background: linear-gradient(145deg, #f0f5fa, #d4d9e0) !important;
150
+ border-radius: 50% !important;
151
+ width: 28px !important;
152
+ height: 28px !important;
153
+ box-shadow:
154
+ 6px 6px 12px var(--neu-shadow-dark),
155
+ -6px -6px 12px var(--neu-shadow-light) !important;
156
+ cursor: pointer !important;
157
+ }
158
+
159
+ /* πŸ”˜ λΌλ””μ˜€ λ²„νŠΌ & μ²΄ν¬λ°•μŠ€ */
160
+ .gr-radio, .gr-checkbox,
161
+ input[type="radio"], input[type="checkbox"] {
162
+ background: var(--neu-bg) !important;
163
+ border-radius: 12px !important;
164
+ box-shadow:
165
+ inset 4px 4px 8px var(--neu-shadow-dark),
166
+ inset -4px -4px 8px var(--neu-shadow-light) !important;
167
+ border: none !important;
168
+ }
169
+
170
+ .gr-radio-label, .gr-checkbox-label {
171
+ color: var(--neu-text) !important;
172
+ font-weight: 600 !important;
173
+ }
174
+
175
+ /* λΌλ””μ˜€/μ²΄ν¬λ°•μŠ€ κ·Έλ£Ή μ»¨ν…Œμ΄λ„ˆ */
176
+ .gr-radio-group, .gr-checkbox-group,
177
+ .radio-group, .checkbox-group {
178
+ background: var(--neu-bg) !important;
179
+ border-radius: 20px !important;
180
+ padding: 15px 20px !important;
181
+ box-shadow:
182
+ 8px 8px 16px var(--neu-shadow-dark),
183
+ -8px -8px 16px var(--neu-shadow-light) !important;
184
+ }
185
+
186
+ /* 🎨 컬러 피컀 */
187
+ input[type="color"] {
188
+ background: var(--neu-bg) !important;
189
+ border-radius: 50% !important;
190
+ width: 60px !important;
191
+ height: 60px !important;
192
+ box-shadow:
193
+ 8px 8px 16px var(--neu-shadow-dark),
194
+ -8px -8px 16px var(--neu-shadow-light) !important;
195
+ border: none !important;
196
+ cursor: pointer !important;
197
+ padding: 8px !important;
198
+ }
199
+
200
+ /* πŸ“Š Row μ»¨ν…Œμ΄λ„ˆ */
201
+ .gr-row, .row {
202
+ background: transparent !important;
203
+ gap: 25px !important;
204
+ }
205
+
206
+ /* πŸ“ ν…μŠ€νŠΈλ°•μŠ€ 였λͺ© 효과 */
207
+ textarea, input[type="text"], .gr-textbox {
208
+ background: var(--neu-bg) !important;
209
+ border-radius: 15px !important;
210
+ box-shadow:
211
+ inset 6px 6px 12px var(--neu-shadow-dark),
212
+ inset -6px -6px 12px var(--neu-shadow-light) !important;
213
+ border: none !important;
214
+ padding: 15px 20px !important;
215
+ color: var(--neu-text-dark) !important;
216
+ font-weight: 500 !important;
217
+ }
218
+
219
+ textarea:focus, input[type="text"]:focus {
220
+ outline: none !important;
221
+ box-shadow:
222
+ inset 8px 8px 16px var(--neu-shadow-dark),
223
+ inset -8px -8px 16px var(--neu-shadow-light),
224
+ 0 0 0 3px rgba(102, 126, 234, 0.3) !important;
225
+ }
226
+
227
+ /* 🏷️ λ ˆμ΄λΈ” μŠ€νƒ€μΌ */
228
+ label, .gr-label {
229
+ color: var(--neu-text-dark) !important;
230
+ font-weight: 700 !important;
231
+ font-size: 14px !important;
232
+ text-transform: uppercase !important;
233
+ letter-spacing: 0.5px !important;
234
+ margin-bottom: 10px !important;
235
+ }
236
+
237
+ /* πŸ“¦ 블둝 μ»¨ν…Œμ΄λ„ˆ */
238
+ .gr-block, .block {
239
+ background: var(--neu-bg) !important;
240
+ border-radius: 25px !important;
241
+ box-shadow:
242
+ 10px 10px 20px var(--neu-shadow-dark),
243
+ -10px -10px 20px var(--neu-shadow-light) !important;
244
+ padding: 20px !important;
245
+ margin: 15px 0 !important;
246
+ }
247
+
248
+ /* πŸ”² νŒ¨λ„ ꡬ뢄선 제거 */
249
+ .gr-panel, .panel {
250
+ border: none !important;
251
+ background: transparent !important;
252
+ }
253
+
254
+ /* ℹ️ 정보 ν…μŠ€νŠΈ */
255
+ .gr-info, .info {
256
+ color: var(--neu-text) !important;
257
+ background: var(--neu-bg) !important;
258
+ border-radius: 12px !important;
259
+ padding: 12px 18px !important;
260
+ box-shadow:
261
+ inset 4px 4px 8px var(--neu-shadow-dark),
262
+ inset -4px -4px 8px var(--neu-shadow-light) !important;
263
+ }
264
+
265
+ /* 🎯 예제 μ„Ήμ…˜ */
266
+ .gr-examples, .examples {
267
+ background: var(--neu-bg) !important;
268
+ border-radius: 20px !important;
269
+ padding: 20px !important;
270
+ box-shadow:
271
+ 8px 8px 16px var(--neu-shadow-dark),
272
+ -8px -8px 16px var(--neu-shadow-light) !important;
273
+ }
274
+
275
+ /* 🌟 ν˜Έλ²„ 효과 κ°•ν™” */
276
+ .gr-block:hover {
277
+ box-shadow:
278
+ 12px 12px 24px var(--neu-shadow-dark),
279
+ -12px -12px 24px var(--neu-shadow-light) !important;
280
+ }
281
+
282
+ /* πŸ“± λ°˜μ‘ν˜• μ‘°μ • */
283
+ @media (max-width: 768px) {
284
+ .gradio-container {
285
+ padding: 15px !important;
286
+ }
287
+
288
+ button, .gr-button {
289
+ padding: 14px 30px !important;
290
+ font-size: 14px !important;
291
+ }
292
+
293
+ .gr-block {
294
+ border-radius: 18px !important;
295
+ padding: 15px !important;
296
+ }
297
+ }
298
+
299
+ /* ✨ μ• λ‹ˆλ©”μ΄μ…˜ */
300
+ @keyframes neuPulse {
301
+ 0%, 100% {
302
+ box-shadow:
303
+ 10px 10px 20px var(--neu-shadow-dark),
304
+ -10px -10px 20px var(--neu-shadow-light);
305
+ }
306
+ 50% {
307
+ box-shadow:
308
+ 14px 14px 28px var(--neu-shadow-dark),
309
+ -14px -14px 28px var(--neu-shadow-light);
310
+ }
311
+ }
312
+
313
+ .processing {
314
+ animation: neuPulse 1.5s ease-in-out infinite !important;
315
+ }
316
+
317
+ /* 🎨 ν”„λ‘œκ·Έλ ˆμŠ€ ν‘œμ‹œ */
318
+ .progress-bar {
319
+ background: var(--neu-bg) !important;
320
+ border-radius: 10px !important;
321
+ box-shadow:
322
+ inset 4px 4px 8px var(--neu-shadow-dark),
323
+ inset -4px -4px 8px var(--neu-shadow-light) !important;
324
+ overflow: hidden !important;
325
+ }
326
+
327
+ .progress-bar-fill {
328
+ background: linear-gradient(90deg, var(--neu-accent), var(--neu-accent-light)) !important;
329
+ border-radius: 10px !important;
330
+ }
331
+ """
332
+
333
  # Function to process a single frame
334
  def process_frame(frame, bg_type, bg, fast_mode, bg_frame_index, background_frames, color):
335
  try:
 
339
  elif bg_type == "Image":
340
  processed_image = process(pil_image, bg, fast_mode)
341
  elif bg_type == "Video":
342
+ background_frame = background_frames[bg_frame_index]
343
  bg_frame_index += 1
344
  background_image = Image.fromarray(background_frame)
345
  processed_image = process(pil_image, background_image, fast_mode)
346
  else:
347
+ processed_image = pil_image
348
  return np.array(processed_image), bg_frame_index
349
  except Exception as e:
350
  print(f"Error processing frame: {e}")
351
  return frame, bg_frame_index
352
 
353
  @spaces.GPU
354
+ def fn(vid, bg_type="Color", bg_image=None, bg_video=None, color="#00FF00", fps=0, video_handling="slow_down", fast_mode=False, max_workers=16):
355
  try:
356
+ start_time = time.time()
357
  video = VideoFileClip(vid)
358
  if fps == 0:
359
  fps = video.fps
 
362
  frames = list(video.iter_frames(fps=fps))
363
 
364
  processed_frames = []
365
+ yield gr.update(visible=True), gr.update(visible=False), f"πŸš€ Processing started... Elapsed time: 0 seconds"
366
 
367
  if bg_type == "Video":
368
  background_video = VideoFileClip(bg_video)
369
  if background_video.duration < video.duration:
370
  if video_handling == "slow_down":
371
  background_video = background_video.fx(vfx.speedx, factor=video.duration / background_video.duration)
372
+ else:
373
  background_video = concatenate_videoclips([background_video] * int(video.duration / background_video.duration + 1))
374
  background_frames = list(background_video.iter_frames(fps=fps))
375
  else:
376
  background_frames = None
377
 
378
+ bg_frame_index = 0
379
 
380
  with ThreadPoolExecutor(max_workers=max_workers) as executor:
 
381
  futures = [executor.submit(process_frame, frames[i], bg_type, bg_image, fast_mode, bg_frame_index + i, background_frames, color) for i in range(len(frames))]
382
  for i, future in enumerate(futures):
383
+ result, _ = future.result()
384
  processed_frames.append(result)
385
  elapsed_time = time.time() - start_time
386
+ progress_pct = ((i + 1) / len(frames)) * 100
387
+ yield result, None, f"⚑ Processing frame {i+1}/{len(frames)} ({progress_pct:.1f}%)... Elapsed: {elapsed_time:.2f}s"
388
 
389
  processed_video = ImageSequenceClip(processed_frames, fps=fps)
390
  processed_video = processed_video.with_audio(audio)
391
 
392
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_file:
393
  temp_filepath = temp_file.name
394
+ # 고퀄리티 λΉ„λ””μ˜€ 인코딩 μ„€μ •
395
+ processed_video.write_videofile(
396
+ temp_filepath,
397
+ codec="libx264",
398
+ bitrate="8000k", # λΉ„νŠΈλ ˆμ΄νŠΈ 증가
399
+ audio_codec="aac",
400
+ audio_bitrate="192k",
401
+ preset="slow", # 더 λ‚˜μ€ μ••μΆ• ν’ˆμ§ˆ
402
+ ffmpeg_params=["-crf", "18"] # κ³ ν’ˆμ§ˆ CRF κ°’
403
+ )
404
 
405
  elapsed_time = time.time() - start_time
406
+ yield gr.update(visible=False), gr.update(visible=True), f"βœ… Processing complete! Total time: {elapsed_time:.2f} seconds"
407
+ yield processed_frames[-1], temp_filepath, f"βœ… Processing complete! Total time: {elapsed_time:.2f} seconds"
408
 
409
  except Exception as e:
410
  print(f"Error: {e}")
411
  elapsed_time = time.time() - start_time
412
+ yield gr.update(visible=False), gr.update(visible=True), f"❌ Error: {e}. Elapsed: {elapsed_time:.2f}s"
413
+ yield None, f"Error processing video: {e}", f"❌ Error: {e}. Elapsed: {elapsed_time:.2f}s"
414
 
415
  def process(image, bg, fast_mode=False):
416
  image_size = image.size
 
421
  preds = model(input_images)[-1].sigmoid().cpu()
422
  pred = preds[0].squeeze()
423
  pred_pil = transforms.ToPILImage()(pred)
424
+ mask = pred_pil.resize(image_size, Image.LANCZOS) # κ³ ν’ˆμ§ˆ 리사이징
425
 
426
  if isinstance(bg, str) and bg.startswith("#"):
427
  color_rgb = tuple(int(bg[i:i+2], 16) for i in (1, 3, 5))
428
  background = Image.new("RGBA", image_size, color_rgb + (255,))
429
  elif isinstance(bg, Image.Image):
430
+ background = bg.convert("RGBA").resize(image_size, Image.LANCZOS)
431
  else:
432
+ background = Image.open(bg).convert("RGBA").resize(image_size, Image.LANCZOS)
433
 
434
  image = Image.composite(image, background, mask)
435
  return image
436
 
437
+ # ═══════════════════════════════════════════════════════════
438
+ # 🎨 GRADIO UI with Neumorphism
439
+ # ═══════════════════════════════════════════════════════════
440
+ with gr.Blocks(
441
+ css=NEUMORPHISM_CSS,
442
+ title="🎬 Video Background Remover Pro",
443
+ theme=gr.themes.Soft(
444
+ primary_hue="slate",
445
+ secondary_hue="blue",
446
+ neutral_hue="slate",
447
+ font=gr.themes.GoogleFont("Inter")
448
+ )
449
+ ) as demo:
450
+
451
+ gr.Markdown("""
452
+ # 🎬 Video Background Remover & Changer Pro
453
+ ### ✨ AI-powered background replacement with high-quality processing
454
+ **Features:** Color, Image, or Video backgrounds β€’ High-resolution processing (1024px) β€’ Fast & Quality modes
455
+ """)
456
 
457
  with gr.Row():
458
+ in_video = gr.Video(
459
+ label="πŸ“₯ Input Video",
460
+ interactive=True,
461
+ height=400
462
+ )
463
+ stream_image = gr.Image(
464
+ label="⚑ Live Preview",
465
+ visible=False,
466
+ height=400
467
+ )
468
+ out_video = gr.Video(
469
+ label="πŸ“€ Output Video",
470
+ height=400
471
+ )
472
 
473
+ submit_button = gr.Button(
474
+ "πŸš€ Change Background",
475
+ interactive=True,
476
+ variant="primary",
477
+ size="lg"
478
+ )
479
 
480
  with gr.Row():
481
+ with gr.Column(scale=1):
482
+ bg_type = gr.Radio(
483
+ ["Color", "Image", "Video"],
484
+ label="🎨 Background Type",
485
+ value="Color",
486
+ interactive=True
487
+ )
488
+ color_picker = gr.ColorPicker(
489
+ label="🎨 Background Color",
490
+ value="#00FF00",
491
+ visible=True,
492
+ interactive=True
493
+ )
494
+ bg_image = gr.Image(
495
+ label="πŸ–ΌοΈ Background Image",
496
+ type="filepath",
497
+ visible=False,
498
+ interactive=True
499
+ )
500
+ bg_video = gr.Video(
501
+ label="🎬 Background Video",
502
+ visible=False,
503
+ interactive=True
504
+ )
505
+
506
+ with gr.Column(visible=False) as video_handling_options:
507
+ video_handling_radio = gr.Radio(
508
+ ["slow_down", "loop"],
509
+ label="πŸ”„ Video Sync Mode",
510
+ value="slow_down",
511
+ interactive=True
512
+ )
513
 
514
+ with gr.Column(scale=1):
515
+ gr.Markdown("### βš™οΈ Quality Settings")
516
+
517
+ fps_slider = gr.Slider(
518
+ minimum=0,
519
+ maximum=120, # 60 β†’ 120 증가
520
+ step=1,
521
+ value=0,
522
+ label="🎞️ Output FPS (0 = Original)",
523
+ interactive=True
524
+ )
525
+
526
+ fast_mode_checkbox = gr.Checkbox(
527
+ label="⚑ Fast Mode (BiRefNet_lite) - Uncheck for highest quality",
528
+ value=False, # 기본값을 False둜 λ³€κ²½ (κ³ ν’ˆμ§ˆ λͺ¨λ“œ)
529
+ interactive=True
530
+ )
531
+
532
+ max_workers_slider = gr.Slider(
533
+ minimum=1,
534
+ maximum=64, # 32 β†’ 64 증가
535
+ step=1,
536
+ value=16, # 10 β†’ 16 증가
537
+ label="πŸ”§ Parallel Workers",
538
+ info="Higher = Faster (requires more VRAM)",
539
+ interactive=True
540
+ )
541
 
542
+ time_textbox = gr.Textbox(
543
+ label="πŸ“Š Processing Status",
544
+ interactive=False,
545
+ placeholder="Status will appear here..."
546
+ )
547
 
548
  def update_visibility(bg_type):
549
  if bg_type == "Color":
 
555
  else:
556
  return gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
557
 
558
+ bg_type.change(
559
+ update_visibility,
560
+ inputs=bg_type,
561
+ outputs=[color_picker, bg_image, bg_video, video_handling_options]
562
+ )
563
 
564
  examples = gr.Examples(
565
  [
 
579
  inputs=[in_video, bg_type, bg_image, bg_video, color_picker, fps_slider, video_handling_radio, fast_mode_checkbox, max_workers_slider],
580
  outputs=[stream_image, out_video, time_textbox],
581
  )
582
+
583
+ gr.Markdown("""
584
+ ---
585
+ ### πŸ“‹ Tips for Best Results
586
+ - **High Quality Mode**: Uncheck 'Fast Mode' for best edge detection
587
+ - **4K Videos**: Use higher worker count (32-64) for faster processing
588
+ - **Green Screen**: Use `#00FF00` for classic chroma key compatibility
589
+ """)
590
 
591
  if __name__ == "__main__":
592
  demo.launch(show_error=True)