MogensR commited on
Commit
5fe7c80
·
verified ·
1 Parent(s): db66357

Update streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +115 -143
streamlit_app.py CHANGED
@@ -53,17 +53,19 @@ def check_gpu():
53
  logger.info(f"Device Name: {torch.cuda.get_device_name(0)}")
54
  logger.info(f"Device Capability: {torch.cuda.get_device_capability(0)}")
55
 
 
 
 
 
56
  try:
57
  test_tensor = torch.randn(100, 100).cuda()
58
- logger.info(f"GPU Test Tensor Created Successfully on: {test_tensor.device}")
59
  del test_tensor
60
  torch.cuda.empty_cache()
61
  except Exception as e:
62
  logger.error(f"GPU Test Failed: {e}")
63
  else:
64
  logger.warning("CUDA NOT AVAILABLE")
65
- logger.info(f"PyTorch Version: {torch.__version__}")
66
- logger.info(f"CUDA Built Version: {torch.version.cuda}")
67
 
68
  logger.info("=" * 60)
69
  return cuda_available
@@ -85,12 +87,6 @@ def check_gpu():
85
  .stButton>button:hover {
86
  background-color: #45a049;
87
  }
88
- .stProgress > div > div > div > div {
89
- background-color: #4CAF50;
90
- }
91
- .stAlert {
92
- border-radius: 10px;
93
- }
94
  .video-container {
95
  border: 2px dashed #4CAF50;
96
  border-radius: 10px;
@@ -106,36 +102,35 @@ def initialize_session_state():
106
  defaults = {
107
  'uploaded_video': None,
108
  'video_bytes_cache': None,
109
- 'video_preview_placeholder': None,
110
  'bg_image_cache': None,
111
- 'bg_preview_placeholder': None,
112
  'bg_color': "#00FF00",
113
- 'cached_color': None,
114
  'color_display_cache': None,
115
  'processed_video_bytes': None,
116
  'processing': False,
117
- 'process_complete': False,
118
- 'last_video_id': None,
119
- 'last_bg_image_id': None,
120
- 'gpu_available': None
121
  }
122
  for key, value in defaults.items():
123
  if key not in st.session_state:
124
  st.session_state[key] = value
125
 
126
- # Run GPU check on first init
127
  if st.session_state.gpu_available is None:
128
  st.session_state.gpu_available = check_gpu()
129
 
130
  # --- Video Processing ---
131
  def process_video(input_file, background, bg_type="image"):
132
- """Process video with the selected background using SAM2 and MatAnyone pipeline."""
133
  logger.info("=" * 60)
134
  logger.info("STARTING VIDEO PROCESSING")
135
  logger.info("=" * 60)
136
 
137
  try:
138
- # Create a persistent temporary directory
 
 
 
 
 
139
  temp_base = Path(tempfile.gettempdir()) / "video_processing"
140
  temp_base.mkdir(exist_ok=True)
141
  temp_dir = temp_base / f"session_{int(time.time())}"
@@ -143,19 +138,17 @@ def process_video(input_file, background, bg_type="image"):
143
 
144
  logger.info(f"Temp directory: {temp_dir}")
145
 
146
- # Save the uploaded video
147
  input_path = str(temp_dir / "input.mp4")
148
- logger.info(f"Writing video to {input_path}")
149
 
150
  input_file.seek(0)
 
151
  with open(input_path, "wb") as f:
152
- written = f.write(input_file.read())
153
- logger.info(f"Wrote {written/1e6:.2f}MB")
154
 
155
- if not os.path.exists(input_path) or os.path.getsize(input_path) == 0:
156
- raise FileNotFoundError(f"Input video not saved properly")
157
-
158
- logger.info(f"Video file verified: {os.path.getsize(input_path)} bytes")
159
 
160
  # Prepare background
161
  bg_path = None
@@ -164,7 +157,7 @@ def process_video(input_file, background, bg_type="image"):
164
  bg_cv = cv2.cvtColor(np.array(background), cv2.COLOR_RGB2BGR)
165
  bg_path = str(temp_dir / "background.jpg")
166
  cv2.imwrite(bg_path, bg_cv)
167
- logger.info(f"Background image written")
168
 
169
  elif bg_type == "color":
170
  logger.info(f"Processing background COLOR: {st.session_state.bg_color}")
@@ -172,91 +165,83 @@ def process_video(input_file, background, bg_type="image"):
172
  color_rgb = tuple(int(color_hex[i:i+2], 16) for i in (0, 2, 4))
173
  bg_path = str(temp_dir / "background.jpg")
174
  cv2.imwrite(bg_path, np.ones((100, 100, 3), dtype=np.uint8) * color_rgb[::-1])
175
- logger.info(f"Background color image written")
176
 
177
  # Progress tracking
178
- progress_placeholder = st.empty()
179
- status_placeholder = st.empty()
180
 
181
  def progress_callback(progress, message):
182
  progress = max(0, min(1, float(progress)))
183
  logger.info(f"Progress: {progress*100:.1f}% - {message}")
184
- progress_placeholder.progress(progress)
185
- status_placeholder.text(f"Status: {message}")
186
 
187
- # GPU check
188
  if torch.cuda.is_available():
189
- device = torch.cuda.get_device_name(0)
190
- mem_before = torch.cuda.memory_allocated()/1e9
191
- logger.info(f"CUDA Device: {device}")
192
- logger.info(f"GPU Memory Before: {mem_before:.2f}GB")
193
- else:
194
- logger.warning("CUDA NOT AVAILABLE - Processing on CPU")
195
 
196
- # Process the video
197
  output_path = str(temp_dir / "output.mp4")
198
  click_points = [[0.5, 0.5]]
199
 
200
  logger.info("Importing TwoStageProcessor...")
201
  from pipeline.integrated_pipeline import TwoStageProcessor
202
 
203
- @st.cache_resource
204
- def load_processor(temp_dir_str):
205
- logger.info(f"Loading processor")
206
- return TwoStageProcessor(temp_dir=temp_dir_str)
207
-
208
- processor = load_processor(str(temp_dir))
209
- logger.info("Processor loaded")
210
 
211
- try:
212
- logger.info("Calling process_video...")
213
-
214
- success = processor.process_video(
215
- input_video=input_path,
216
- background_video=bg_path if bg_type == "image" else "",
217
- click_points=click_points,
218
- output_path=output_path,
219
- use_matanyone=True,
220
- progress_callback=progress_callback
221
- )
222
-
223
- logger.info(f"Processing returned: {success}")
224
-
225
- except Exception as e:
226
- logger.error(f"Pipeline processing failed: {traceback.format_exc()}")
227
- raise
228
 
229
  if torch.cuda.is_available():
230
- mem_after = torch.cuda.memory_allocated()/1e9
231
- logger.info(f"GPU Memory After: {mem_after:.2f}GB")
232
  torch.cuda.empty_cache()
233
 
234
  if not success:
235
- raise RuntimeError("Video processing returned False")
236
 
237
- # Verify output
238
  if not os.path.exists(output_path):
239
- raise FileNotFoundError(f"Output video not created")
240
 
241
  output_size = os.path.getsize(output_path)
242
  logger.info(f"Output: {output_size} bytes ({output_size/1e6:.2f}MB)")
243
 
244
- # Read output into memory
245
- logger.info("Reading output into memory...")
246
  with open(output_path, 'rb') as f:
247
  video_bytes = f.read()
248
 
249
- logger.info(f"Read {len(video_bytes)/1e6:.2f}MB")
250
 
251
  # Cleanup
252
  try:
253
  shutil.rmtree(temp_dir)
254
- logger.info("Temp directory cleaned")
255
  except Exception as e:
256
- logger.warning(f"Could not clean temp: {e}")
257
 
 
 
 
258
  logger.info("=" * 60)
259
- logger.info("VIDEO PROCESSING COMPLETED")
260
  logger.info("=" * 60)
261
 
262
  return video_bytes
@@ -264,7 +249,7 @@ def load_processor(temp_dir_str):
264
  except Exception as e:
265
  logger.error(f"ERROR: {str(e)}")
266
  logger.error(traceback.format_exc())
267
- st.error(f"An error occurred: {str(e)}")
268
  return None
269
 
270
  # --- Main Application ---
@@ -272,10 +257,9 @@ def main():
272
  st.title("Advanced Video Background Replacer")
273
  st.markdown("---")
274
 
275
- # Initialize session state
276
  initialize_session_state()
277
 
278
- # GPU Status in sidebar
279
  with st.sidebar:
280
  st.subheader("System Status")
281
  if st.session_state.gpu_available:
@@ -285,7 +269,6 @@ def main():
285
 
286
  logger.info(f"Rerun - Processing: {st.session_state.processing}")
287
 
288
- # Main layout
289
  col1, col2 = st.columns([1, 1], gap="large")
290
 
291
  with col1:
@@ -297,32 +280,34 @@ def main():
297
  key="video_uploader"
298
  )
299
 
300
- # Check if video actually changed using id()
301
- current_video_id = id(uploaded)
302
- if current_video_id != st.session_state.last_video_id:
303
- logger.info(f"New video: {uploaded.name if uploaded else 'None'}")
304
- st.session_state.uploaded_video = uploaded
305
- st.session_state.last_video_id = current_video_id
306
- st.session_state.video_bytes_cache = None
307
- st.session_state.processed_video_bytes = None
308
- st.session_state.process_complete = False
 
 
309
 
310
- # Video preview with placeholder
311
  st.markdown("### Video Preview")
312
- if st.session_state.video_preview_placeholder is None:
313
- st.session_state.video_preview_placeholder = st.empty()
314
-
315
  if st.session_state.uploaded_video is not None:
316
- if st.session_state.video_bytes_cache is None:
317
- logger.info("Caching video...")
318
- st.session_state.uploaded_video.seek(0)
319
- st.session_state.video_bytes_cache = st.session_state.uploaded_video.read()
320
- logger.info(f"Cached {len(st.session_state.video_bytes_cache)/1e6:.2f}MB")
321
-
322
- with st.session_state.video_preview_placeholder.container():
323
  st.video(st.session_state.video_bytes_cache)
 
 
 
324
  else:
325
- st.session_state.video_preview_placeholder.empty()
326
 
327
  with col2:
328
  st.header("2. Background Settings")
@@ -341,25 +326,18 @@ def main():
341
  key="bg_image_uploader"
342
  )
343
 
344
- # Check if image actually changed using id()
345
- current_bg_id = id(bg_image)
346
- if current_bg_id != st.session_state.last_bg_image_id:
347
- logger.info(f"New background: {bg_image.name if bg_image else 'None'}")
348
- st.session_state.last_bg_image_id = current_bg_id
349
- if bg_image is not None:
350
- st.session_state.bg_image_cache = Image.open(bg_image)
351
- else:
352
- st.session_state.bg_image_cache = None
353
-
354
- # Background preview with placeholder
355
- if st.session_state.bg_preview_placeholder is None:
356
- st.session_state.bg_preview_placeholder = st.empty()
357
-
358
- if st.session_state.bg_image_cache is not None:
359
- with st.session_state.bg_preview_placeholder.container():
360
- st.image(st.session_state.bg_image_cache, caption="Selected Background", use_container_width=True)
361
  else:
362
- st.session_state.bg_preview_placeholder.empty()
363
 
364
  elif bg_type == "Color":
365
  selected_color = st.color_picker(
@@ -368,44 +346,38 @@ def main():
368
  key="color_picker"
369
  )
370
 
371
- if selected_color != st.session_state.cached_color:
372
- logger.info(f"Color: {selected_color}")
373
  st.session_state.bg_color = selected_color
374
- st.session_state.cached_color = selected_color
375
-
376
  color_rgb = tuple(int(selected_color.lstrip('#')[i:i+2], 16) for i in (0, 2, 4))
377
  color_display = np.zeros((100, 100, 3), dtype=np.uint8)
378
  color_display[:, :] = color_rgb[::-1]
379
  st.session_state.color_display_cache = color_display
380
 
381
- if st.session_state.bg_preview_placeholder is None:
382
- st.session_state.bg_preview_placeholder = st.empty()
383
-
384
  if st.session_state.color_display_cache is not None:
385
- with st.session_state.bg_preview_placeholder.container():
386
- st.image(st.session_state.color_display_cache, caption="Selected Color", width=200)
387
- else:
388
- st.session_state.bg_preview_placeholder.empty()
389
 
390
  st.header("3. Process & Download")
391
 
392
- if st.button(
393
- "Process Video",
394
- disabled=st.session_state.uploaded_video is None or st.session_state.processing,
395
- use_container_width=True
396
- ):
397
  logger.info("PROCESS BUTTON CLICKED")
 
 
398
  st.session_state.processing = True
399
- st.session_state.process_complete = False
400
  st.session_state.processed_video_bytes = None
401
 
402
- with st.spinner("Processing video..."):
403
  try:
404
  background = None
405
  if bg_type == "Image" and st.session_state.bg_image_cache is not None:
406
  background = st.session_state.bg_image_cache
 
407
  elif bg_type == "Color":
408
  background = st.session_state.bg_color
 
409
 
410
  video_bytes = process_video(
411
  st.session_state.uploaded_video,
@@ -415,21 +387,21 @@ def main():
415
 
416
  if video_bytes and len(video_bytes) > 0:
417
  st.session_state.processed_video_bytes = video_bytes
418
- st.session_state.process_complete = True
419
- logger.info(f"Complete! {len(video_bytes)/1e6:.2f}MB")
420
  st.success("Video processing complete!")
421
  else:
422
  logger.error("Processing returned empty")
423
- st.error("Failed to process video")
424
 
425
  except Exception as e:
426
  logger.error(f"Exception: {str(e)}")
 
427
  st.error(f"Error: {str(e)}")
428
  finally:
429
  st.session_state.processing = False
430
 
431
  # Show processed video
432
- if st.session_state.processed_video_bytes is not None and len(st.session_state.processed_video_bytes) > 0:
433
  st.markdown("---")
434
  st.markdown("### Processed Video")
435
 
@@ -443,8 +415,8 @@ def main():
443
  use_container_width=True
444
  )
445
  except Exception as e:
446
- logger.error(f"Display error: {str(e)}")
447
- st.error(f"Error: {str(e)}")
448
 
449
  if __name__ == "__main__":
450
  main()
 
53
  logger.info(f"Device Name: {torch.cuda.get_device_name(0)}")
54
  logger.info(f"Device Capability: {torch.cuda.get_device_capability(0)}")
55
 
56
+ # Set as default device
57
+ torch.cuda.set_device(0)
58
+ logger.info("Set CUDA device 0 as default")
59
+
60
  try:
61
  test_tensor = torch.randn(100, 100).cuda()
62
+ logger.info(f"GPU Test Tensor Created on: {test_tensor.device}")
63
  del test_tensor
64
  torch.cuda.empty_cache()
65
  except Exception as e:
66
  logger.error(f"GPU Test Failed: {e}")
67
  else:
68
  logger.warning("CUDA NOT AVAILABLE")
 
 
69
 
70
  logger.info("=" * 60)
71
  return cuda_available
 
87
  .stButton>button:hover {
88
  background-color: #45a049;
89
  }
 
 
 
 
 
 
90
  .video-container {
91
  border: 2px dashed #4CAF50;
92
  border-radius: 10px;
 
102
  defaults = {
103
  'uploaded_video': None,
104
  'video_bytes_cache': None,
 
105
  'bg_image_cache': None,
 
106
  'bg_color': "#00FF00",
 
107
  'color_display_cache': None,
108
  'processed_video_bytes': None,
109
  'processing': False,
110
+ 'gpu_available': None,
111
+ 'files_loaded': False
 
 
112
  }
113
  for key, value in defaults.items():
114
  if key not in st.session_state:
115
  st.session_state[key] = value
116
 
 
117
  if st.session_state.gpu_available is None:
118
  st.session_state.gpu_available = check_gpu()
119
 
120
  # --- Video Processing ---
121
  def process_video(input_file, background, bg_type="image"):
122
+ """Process video with GPU acceleration"""
123
  logger.info("=" * 60)
124
  logger.info("STARTING VIDEO PROCESSING")
125
  logger.info("=" * 60)
126
 
127
  try:
128
+ # Ensure GPU is default device
129
+ if torch.cuda.is_available():
130
+ torch.cuda.set_device(0)
131
+ logger.info("GPU set as default device for processing")
132
+
133
+ # Create temp directory
134
  temp_base = Path(tempfile.gettempdir()) / "video_processing"
135
  temp_base.mkdir(exist_ok=True)
136
  temp_dir = temp_base / f"session_{int(time.time())}"
 
138
 
139
  logger.info(f"Temp directory: {temp_dir}")
140
 
141
+ # Save video
142
  input_path = str(temp_dir / "input.mp4")
143
+ logger.info(f"Saving video to {input_path}")
144
 
145
  input_file.seek(0)
146
+ video_data = input_file.read()
147
  with open(input_path, "wb") as f:
148
+ f.write(video_data)
 
149
 
150
+ file_size = os.path.getsize(input_path)
151
+ logger.info(f"Video saved: {file_size} bytes ({file_size/1e6:.2f}MB)")
 
 
152
 
153
  # Prepare background
154
  bg_path = None
 
157
  bg_cv = cv2.cvtColor(np.array(background), cv2.COLOR_RGB2BGR)
158
  bg_path = str(temp_dir / "background.jpg")
159
  cv2.imwrite(bg_path, bg_cv)
160
+ logger.info(f"Background saved")
161
 
162
  elif bg_type == "color":
163
  logger.info(f"Processing background COLOR: {st.session_state.bg_color}")
 
165
  color_rgb = tuple(int(color_hex[i:i+2], 16) for i in (0, 2, 4))
166
  bg_path = str(temp_dir / "background.jpg")
167
  cv2.imwrite(bg_path, np.ones((100, 100, 3), dtype=np.uint8) * color_rgb[::-1])
168
+ logger.info(f"Background color saved")
169
 
170
  # Progress tracking
171
+ progress_bar = st.progress(0)
172
+ status_text = st.empty()
173
 
174
  def progress_callback(progress, message):
175
  progress = max(0, min(1, float(progress)))
176
  logger.info(f"Progress: {progress*100:.1f}% - {message}")
177
+ progress_bar.progress(progress)
178
+ status_text.text(f"{message} ({progress*100:.0f}%)")
179
 
180
+ # GPU status before processing
181
  if torch.cuda.is_available():
182
+ logger.info(f"GPU Memory Before: {torch.cuda.memory_allocated()/1e9:.2f}GB")
 
 
 
 
 
183
 
184
+ # Process video
185
  output_path = str(temp_dir / "output.mp4")
186
  click_points = [[0.5, 0.5]]
187
 
188
  logger.info("Importing TwoStageProcessor...")
189
  from pipeline.integrated_pipeline import TwoStageProcessor
190
 
191
+ # Don't cache processor - causes GPU issues
192
+ logger.info("Creating processor instance...")
193
+ processor = TwoStageProcessor(temp_dir=str(temp_dir))
194
+ logger.info("Processor created")
 
 
 
195
 
196
+ logger.info("Starting process_video call...")
197
+ logger.info(f" Input: {input_path}")
198
+ logger.info(f" Background: {bg_path}")
199
+ logger.info(f" Output: {output_path}")
200
+
201
+ success = processor.process_video(
202
+ input_video=input_path,
203
+ background_video=bg_path if bg_type == "image" else "",
204
+ click_points=click_points,
205
+ output_path=output_path,
206
+ use_matanyone=True,
207
+ progress_callback=progress_callback
208
+ )
209
+
210
+ logger.info(f"process_video returned: {success}")
 
 
211
 
212
  if torch.cuda.is_available():
213
+ logger.info(f"GPU Memory After: {torch.cuda.memory_allocated()/1e9:.2f}GB")
 
214
  torch.cuda.empty_cache()
215
 
216
  if not success:
217
+ raise RuntimeError("Processing returned False")
218
 
219
+ # Check output
220
  if not os.path.exists(output_path):
221
+ raise FileNotFoundError("Output video not created")
222
 
223
  output_size = os.path.getsize(output_path)
224
  logger.info(f"Output: {output_size} bytes ({output_size/1e6:.2f}MB)")
225
 
226
+ # Read output
227
+ logger.info("Reading output...")
228
  with open(output_path, 'rb') as f:
229
  video_bytes = f.read()
230
 
231
+ logger.info(f"Output loaded: {len(video_bytes)/1e6:.2f}MB")
232
 
233
  # Cleanup
234
  try:
235
  shutil.rmtree(temp_dir)
236
+ logger.info("Temp cleaned")
237
  except Exception as e:
238
+ logger.warning(f"Cleanup warning: {e}")
239
 
240
+ progress_bar.empty()
241
+ status_text.empty()
242
+
243
  logger.info("=" * 60)
244
+ logger.info("PROCESSING COMPLETED SUCCESSFULLY")
245
  logger.info("=" * 60)
246
 
247
  return video_bytes
 
249
  except Exception as e:
250
  logger.error(f"ERROR: {str(e)}")
251
  logger.error(traceback.format_exc())
252
+ st.error(f"Processing error: {str(e)}")
253
  return None
254
 
255
  # --- Main Application ---
 
257
  st.title("Advanced Video Background Replacer")
258
  st.markdown("---")
259
 
 
260
  initialize_session_state()
261
 
262
+ # GPU Status
263
  with st.sidebar:
264
  st.subheader("System Status")
265
  if st.session_state.gpu_available:
 
269
 
270
  logger.info(f"Rerun - Processing: {st.session_state.processing}")
271
 
 
272
  col1, col2 = st.columns([1, 1], gap="large")
273
 
274
  with col1:
 
280
  key="video_uploader"
281
  )
282
 
283
+ # Handle file upload
284
+ if uploaded is not None:
285
+ # Always refresh if file exists (handles Streamlit restore bug)
286
+ if (st.session_state.uploaded_video is None or
287
+ not hasattr(st.session_state.uploaded_video, 'name') or
288
+ st.session_state.uploaded_video.name != uploaded.name):
289
+ logger.info(f"Loading video: {uploaded.name} ({uploaded.size} bytes)")
290
+ st.session_state.uploaded_video = uploaded
291
+ st.session_state.video_bytes_cache = None
292
+ st.session_state.processed_video_bytes = None
293
+ st.session_state.files_loaded = True
294
 
295
+ # Video preview
296
  st.markdown("### Video Preview")
 
 
 
297
  if st.session_state.uploaded_video is not None:
298
+ try:
299
+ if st.session_state.video_bytes_cache is None:
300
+ logger.info("Caching video...")
301
+ st.session_state.uploaded_video.seek(0)
302
+ st.session_state.video_bytes_cache = st.session_state.uploaded_video.read()
303
+ logger.info(f"Cached {len(st.session_state.video_bytes_cache)/1e6:.2f}MB")
304
+
305
  st.video(st.session_state.video_bytes_cache)
306
+ except Exception as e:
307
+ logger.error(f"Video preview error: {e}")
308
+ st.error(f"Cannot display video: {e}")
309
  else:
310
+ st.info("No video uploaded")
311
 
312
  with col2:
313
  st.header("2. Background Settings")
 
326
  key="bg_image_uploader"
327
  )
328
 
329
+ if bg_image is not None:
330
+ if (st.session_state.bg_image_cache is None or
331
+ not hasattr(st.session_state.bg_image_cache, 'filename') or
332
+ getattr(st.session_state.bg_image_cache, 'filename', None) != bg_image.name):
333
+ logger.info(f"Loading background: {bg_image.name}")
334
+ img = Image.open(bg_image)
335
+ img.filename = bg_image.name
336
+ st.session_state.bg_image_cache = img
337
+
338
+ st.image(st.session_state.bg_image_cache, caption="Selected Background", use_container_width=True)
 
 
 
 
 
 
 
339
  else:
340
+ st.info("No background image")
341
 
342
  elif bg_type == "Color":
343
  selected_color = st.color_picker(
 
346
  key="color_picker"
347
  )
348
 
349
+ if selected_color != st.session_state.bg_color:
 
350
  st.session_state.bg_color = selected_color
 
 
351
  color_rgb = tuple(int(selected_color.lstrip('#')[i:i+2], 16) for i in (0, 2, 4))
352
  color_display = np.zeros((100, 100, 3), dtype=np.uint8)
353
  color_display[:, :] = color_rgb[::-1]
354
  st.session_state.color_display_cache = color_display
355
 
 
 
 
356
  if st.session_state.color_display_cache is not None:
357
+ st.image(st.session_state.color_display_cache, caption="Selected Color", width=200)
 
 
 
358
 
359
  st.header("3. Process & Download")
360
 
361
+ can_process = (st.session_state.uploaded_video is not None and
362
+ not st.session_state.processing)
363
+
364
+ if st.button("Process Video", disabled=not can_process, use_container_width=True):
365
+ logger.info("=" * 60)
366
  logger.info("PROCESS BUTTON CLICKED")
367
+ logger.info("=" * 60)
368
+
369
  st.session_state.processing = True
 
370
  st.session_state.processed_video_bytes = None
371
 
372
+ with st.spinner("Processing video (this may take several minutes)..."):
373
  try:
374
  background = None
375
  if bg_type == "Image" and st.session_state.bg_image_cache is not None:
376
  background = st.session_state.bg_image_cache
377
+ logger.info("Using IMAGE background")
378
  elif bg_type == "Color":
379
  background = st.session_state.bg_color
380
+ logger.info(f"Using COLOR background: {background}")
381
 
382
  video_bytes = process_video(
383
  st.session_state.uploaded_video,
 
387
 
388
  if video_bytes and len(video_bytes) > 0:
389
  st.session_state.processed_video_bytes = video_bytes
390
+ logger.info(f"SUCCESS: {len(video_bytes)/1e6:.2f}MB")
 
391
  st.success("Video processing complete!")
392
  else:
393
  logger.error("Processing returned empty")
394
+ st.error("Processing failed")
395
 
396
  except Exception as e:
397
  logger.error(f"Exception: {str(e)}")
398
+ logger.error(traceback.format_exc())
399
  st.error(f"Error: {str(e)}")
400
  finally:
401
  st.session_state.processing = False
402
 
403
  # Show processed video
404
+ if st.session_state.processed_video_bytes is not None:
405
  st.markdown("---")
406
  st.markdown("### Processed Video")
407
 
 
415
  use_container_width=True
416
  )
417
  except Exception as e:
418
+ logger.error(f"Display error: {e}")
419
+ st.error(f"Display error: {e}")
420
 
421
  if __name__ == "__main__":
422
  main()