MogensR commited on
Commit
e1ccac2
Β·
1 Parent(s): 0015f8f
Files changed (1) hide show
  1. ui_core_functionality.py +150 -35
ui_core_functionality.py CHANGED
@@ -2,6 +2,7 @@
2
  """
3
  BackgroundFX Pro β€” Core Functionality
4
  All processing logic, utilities, background generators, and handlers
 
5
  """
6
 
7
  import os
@@ -121,6 +122,65 @@ def startup_probe():
121
  logger.error(f"πŸ“Š Disk stats: {_disk_stats(APP_ROOT)}")
122
  raise RuntimeError(f"Startup probe failed - system not ready: {e}") from e
123
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  # ===============================================================================
125
  # SYSTEM UTILITIES
126
  # ===============================================================================
@@ -330,6 +390,13 @@ def extract_frame(video_path: str, frame_number: int) -> Optional[np.ndarray]:
330
  logger.error(f"Failed to extract frame: {e}")
331
  return None
332
 
 
 
 
 
 
 
 
333
  # ===============================================================================
334
  # PROGRESS TRACKING
335
  # ===============================================================================
@@ -379,7 +446,7 @@ def get_status(self) -> Dict[str, Any]:
379
  progress_tracker = ProgressTracker()
380
 
381
  # ===============================================================================
382
- # SAFE FILE OPERATIONS
383
  # ===============================================================================
384
 
385
  def create_job_directory() -> Path:
@@ -437,7 +504,7 @@ def safe_download(url: str, filepath: Path, max_size: int = 500 * 1024 * 1024):
437
  raise
438
 
439
  # ===============================================================================
440
- # PIPELINE INTEGRATION
441
  # ===============================================================================
442
 
443
  def process_video_pipeline(
@@ -448,46 +515,77 @@ def process_video_pipeline(
448
  job_dir: Path,
449
  progress_callback: Optional[Callable] = None
450
  ) -> str:
451
- """Process video using the two-stage pipeline with detailed monitoring"""
452
- try:
 
 
453
  logger.info("=" * 60)
454
- logger.info("=== STARTING TWO-STAGE PIPELINE DEBUG SESSION ===")
455
  logger.info("=" * 60)
456
 
457
- # Pre-flight checks
458
  logger.info(f"DEBUG: Video path: {video_path}")
459
  logger.info(f"DEBUG: Video exists: {Path(video_path).exists()}")
 
460
  logger.info(f"DEBUG: Job directory: {job_dir}")
 
461
  logger.info(f"DEBUG: Background image size: {background_image.size if background_image else 'None'}")
462
  logger.info(f"DEBUG: Background type: {background_type}")
 
 
 
 
 
 
 
 
 
463
 
464
- # Lazy import to avoid startup issues
465
  logger.info("DEBUG: Attempting to import two-stage pipeline...")
466
- from two_stage_pipeline import process_two_stage as pipeline_process
467
- logger.info("βœ“ Two-stage pipeline imported successfully")
 
 
 
 
468
 
469
- progress_tracker.update("Initializing two-stage pipeline...")
470
 
471
- # Enhanced progress callback with stage monitoring
472
- current_stage = {"stage": "init"}
473
 
474
  def safe_progress_callback(step: str, progress: float = None):
475
  try:
476
- # Stage detection
 
 
 
477
  if "Stage 1" in step:
478
  if current_stage["stage"] != "stage1":
479
  current_stage["stage"] = "stage1"
 
480
  logger.info("πŸ”„ STAGE TRANSITION: Entering Stage 1 (SAM2)")
 
 
481
  elif "Stage 2" in step:
482
  if current_stage["stage"] != "stage2":
 
483
  current_stage["stage"] = "stage2"
 
484
  logger.info("πŸ”„ STAGE TRANSITION: Entering Stage 2 (Composition)")
 
 
 
485
  elif "Done" in step:
486
  if current_stage["stage"] != "complete":
 
487
  current_stage["stage"] = "complete"
488
  logger.info("πŸ”„ STAGE TRANSITION: Pipeline Complete")
 
 
489
 
490
- logger.info(f"PROGRESS [{current_stage['stage'].upper()}]: {step} ({progress})")
491
  progress_tracker.update(step, progress)
492
 
493
  if progress_callback:
@@ -495,22 +593,25 @@ def safe_progress_callback(step: str, progress: float = None):
495
  progress_callback(f"Progress: {progress:.1%} - {step}")
496
  else:
497
  progress_callback(step)
 
 
 
 
 
498
  except Exception as e:
499
  logger.error(f"Progress callback error: {e}")
500
 
501
- # Validation checks
502
  if background_image is None:
503
  raise ValueError("Background image is required")
504
 
505
  logger.info("DEBUG: Pre-pipeline validation complete")
506
- logger.info("DEBUG: About to call two-stage pipeline...")
507
-
508
- # Monitor job directory before processing
509
  logger.info(f"DEBUG: Job dir contents before: {list(job_dir.iterdir()) if job_dir.exists() else 'does not exist'}")
510
 
511
- # Call two-stage pipeline with correct parameters
 
512
  result_path = pipeline_process(
513
- video_path=video_path,
514
  background_image=background_image,
515
  workdir=job_dir,
516
  progress=safe_progress_callback,
@@ -520,32 +621,46 @@ def safe_progress_callback(step: str, progress: float = None):
520
  logger.info(f"DEBUG: Pipeline returned: {result_path}")
521
  logger.info(f"DEBUG: Result path type: {type(result_path)}")
522
 
523
- # Post-processing validation
524
  if result_path:
525
  result_file = Path(result_path)
526
  logger.info(f"DEBUG: Result file exists: {result_file.exists()}")
527
  if result_file.exists():
528
- logger.info(f"DEBUG: Result file size: {result_file.stat().st_size} bytes")
 
 
 
529
  logger.info(f"DEBUG: Job dir contents after: {list(job_dir.iterdir())}")
 
 
 
 
 
 
 
 
 
 
 
 
530
 
531
  if not result_path or not Path(result_path).exists():
532
  raise RuntimeError("Two-stage pipeline processing failed - no output produced")
533
 
534
  logger.info("=" * 60)
535
- logger.info(f"βœ… TWO-STAGE PIPELINE COMPLETED: {result_path}")
536
  logger.info("=" * 60)
537
  return result_path
538
-
 
 
 
539
  except Exception as e:
540
- logger.error("=" * 60)
541
- logger.error(f"❌ DETAILED ERROR ANALYSIS:")
542
- logger.error(f" Error type: {type(e).__name__}")
543
- logger.error(f" Error message: {e}")
544
- logger.error(f" Current stage: {current_stage.get('stage', 'unknown')}")
545
- logger.error(f" Job directory exists: {job_dir.exists() if 'job_dir' in locals() else 'unknown'}")
546
- if 'job_dir' in locals() and job_dir.exists():
547
- logger.error(f" Job directory contents: {list(job_dir.iterdir())}")
548
- logger.error(" Full traceback:")
549
- logger.error(f"{traceback.format_exc()}")
550
- logger.error("=" * 60)
551
  raise
 
2
  """
3
  BackgroundFX Pro β€” Core Functionality
4
  All processing logic, utilities, background generators, and handlers
5
+ Enhanced with ChatGPT's file safety and error handling improvements
6
  """
7
 
8
  import os
 
122
  logger.error(f"πŸ“Š Disk stats: {_disk_stats(APP_ROOT)}")
123
  raise RuntimeError(f"Startup probe failed - system not ready: {e}") from e
124
 
125
+ # ===============================================================================
126
+ # CHATGPT ENHANCED FILE SAFETY UTILITIES
127
+ # ===============================================================================
128
+
129
+ def new_tmp_path(suffix: str) -> Path:
130
+ """Generate safe temporary path within TMP_ROOT"""
131
+ return TMP_ROOT / f"{uuid.uuid4().hex}{suffix}"
132
+
133
+ def atomic_write_bytes(dst: Path, data: bytes):
134
+ """Atomic file write to prevent corruption"""
135
+ tmp = new_tmp_path(dst.suffix + ".part")
136
+ try:
137
+ with open(tmp, "wb") as f:
138
+ f.write(data)
139
+ tmp.replace(dst) # atomic on same filesystem
140
+ logger.debug(f"βœ… Atomic write completed: {dst}")
141
+ except Exception as e:
142
+ if tmp.exists():
143
+ tmp.unlink(missing_ok=True)
144
+ raise e
145
+
146
+ def safe_name(name: str, default="file") -> str:
147
+ """Sanitize filename to prevent traversal/unicode issues"""
148
+ import re
149
+ base = re.sub(r"[^A-Za-z0-9._-]+", "_", (name or default))
150
+ return base[:120] or default
151
+
152
+ def place_uploaded(in_path: str, sub="uploads") -> Path:
153
+ """Safely handle uploaded files with sanitized names"""
154
+ target_dir = DATA_ROOT / sub
155
+ target_dir.mkdir(exist_ok=True, parents=True)
156
+ out = target_dir / safe_name(Path(in_path).name)
157
+ shutil.copy2(in_path, out)
158
+ logger.info(f"πŸ“ Uploaded file placed: {out}")
159
+ return out
160
+
161
+ def tmp_video_path(ext=".mp4") -> Path:
162
+ """Generate temporary video path"""
163
+ return new_tmp_path(ext)
164
+
165
+ def tmp_image_path(ext=".png") -> Path:
166
+ """Generate temporary image path"""
167
+ return new_tmp_path(ext)
168
+
169
+ def run_safely(fn: Callable, *args, **kwargs):
170
+ """Execute function with comprehensive error logging"""
171
+ try:
172
+ return fn(*args, **kwargs)
173
+ except Exception as e:
174
+ logger.error("PROCESSING FAILED\n%s", "".join(traceback.format_exc()))
175
+ logger.error("CWD=%s | DATA_ROOT=%s | TMP_ROOT=%s | %s",
176
+ os.getcwd(), DATA_ROOT, TMP_ROOT, _disk_stats(APP_ROOT))
177
+ logger.error("Env: OMP_NUM_THREADS=%s | CUDA=%s | torch=%s | cu=%s",
178
+ os.environ.get("OMP_NUM_THREADS"),
179
+ os.environ.get("CUDA_VISIBLE_DEVICES", "default"),
180
+ torch.__version__,
181
+ torch.version.cuda)
182
+ raise
183
+
184
  # ===============================================================================
185
  # SYSTEM UTILITIES
186
  # ===============================================================================
 
390
  logger.error(f"Failed to extract frame: {e}")
391
  return None
392
 
393
+ def ffmpeg_safe_call(inp: Path, out: Path, extra=()):
394
+ """Safe FFmpeg call with proper path quoting"""
395
+ cmd = ["ffmpeg", "-y", "-hide_banner", "-loglevel", "error",
396
+ "-i", str(inp), *extra, str(out)]
397
+ logger.info("FFMPEG: %s", " ".join(cmd))
398
+ subprocess.run(cmd, check=True, timeout=300)
399
+
400
  # ===============================================================================
401
  # PROGRESS TRACKING
402
  # ===============================================================================
 
446
  progress_tracker = ProgressTracker()
447
 
448
  # ===============================================================================
449
+ # SAFE FILE OPERATIONS (ENHANCED)
450
  # ===============================================================================
451
 
452
  def create_job_directory() -> Path:
 
504
  raise
505
 
506
  # ===============================================================================
507
+ # ENHANCED PIPELINE INTEGRATION
508
  # ===============================================================================
509
 
510
  def process_video_pipeline(
 
515
  job_dir: Path,
516
  progress_callback: Optional[Callable] = None
517
  ) -> str:
518
+ """Process video using the two-stage pipeline with enhanced safety and monitoring"""
519
+
520
+ # Wrap entire function with ChatGPT's safe execution wrapper
521
+ def _inner_process():
522
  logger.info("=" * 60)
523
+ logger.info("=== ENHANCED TWO-STAGE PIPELINE (WITH CHATGPT SAFETY) ===")
524
  logger.info("=" * 60)
525
 
526
+ # Pre-flight checks with enhanced validation
527
  logger.info(f"DEBUG: Video path: {video_path}")
528
  logger.info(f"DEBUG: Video exists: {Path(video_path).exists()}")
529
+ logger.info(f"DEBUG: Video file size: {Path(video_path).stat().st_size if Path(video_path).exists() else 'N/A'} bytes")
530
  logger.info(f"DEBUG: Job directory: {job_dir}")
531
+ logger.info(f"DEBUG: Job directory writable: {os.access(job_dir, os.W_OK)}")
532
  logger.info(f"DEBUG: Background image size: {background_image.size if background_image else 'None'}")
533
  logger.info(f"DEBUG: Background type: {background_type}")
534
+ logger.info(f"DEBUG: Disk space: {_disk_stats(APP_ROOT)}")
535
+
536
+ # Safely handle uploaded video
537
+ if not Path(video_path).exists():
538
+ raise FileNotFoundError(f"Video file not found: {video_path}")
539
+
540
+ # Create safe video path within our controlled environment
541
+ safe_video_path = place_uploaded(video_path, "videos")
542
+ logger.info(f"DEBUG: Safe video path: {safe_video_path}")
543
 
544
+ # Import with error context
545
  logger.info("DEBUG: Attempting to import two-stage pipeline...")
546
+ try:
547
+ from two_stage_pipeline import process_two_stage as pipeline_process
548
+ logger.info("βœ“ Two-stage pipeline imported successfully")
549
+ except ImportError as e:
550
+ logger.error(f"Failed to import two-stage pipeline: {e}")
551
+ raise
552
 
553
+ progress_tracker.update("Initializing enhanced two-stage pipeline...")
554
 
555
+ # Enhanced progress callback with stage monitoring and memory tracking
556
+ current_stage = {"stage": "init", "start_time": time.time()}
557
 
558
  def safe_progress_callback(step: str, progress: float = None):
559
  try:
560
+ current_time = time.time()
561
+ elapsed = current_time - current_stage["start_time"]
562
+
563
+ # Stage detection with timing
564
  if "Stage 1" in step:
565
  if current_stage["stage"] != "stage1":
566
  current_stage["stage"] = "stage1"
567
+ current_stage["start_time"] = current_time
568
  logger.info("πŸ”„ STAGE TRANSITION: Entering Stage 1 (SAM2)")
569
+ logger.info(f"Memory before Stage 1: {_disk_stats(APP_ROOT)}")
570
+
571
  elif "Stage 2" in step:
572
  if current_stage["stage"] != "stage2":
573
+ stage1_duration = current_time - current_stage["start_time"]
574
  current_stage["stage"] = "stage2"
575
+ current_stage["start_time"] = current_time
576
  logger.info("πŸ”„ STAGE TRANSITION: Entering Stage 2 (Composition)")
577
+ logger.info(f"Stage 1 completed in {stage1_duration:.1f}s")
578
+ logger.info(f"Memory after Stage 1: {_disk_stats(APP_ROOT)}")
579
+
580
  elif "Done" in step:
581
  if current_stage["stage"] != "complete":
582
+ stage2_duration = current_time - current_stage["start_time"]
583
  current_stage["stage"] = "complete"
584
  logger.info("πŸ”„ STAGE TRANSITION: Pipeline Complete")
585
+ logger.info(f"Stage 2 completed in {stage2_duration:.1f}s")
586
+ logger.info(f"Final memory: {_disk_stats(APP_ROOT)}")
587
 
588
+ logger.info(f"PROGRESS [{current_stage['stage'].upper()}] ({elapsed:.1f}s): {step} ({progress})")
589
  progress_tracker.update(step, progress)
590
 
591
  if progress_callback:
 
593
  progress_callback(f"Progress: {progress:.1%} - {step}")
594
  else:
595
  progress_callback(step)
596
+
597
+ # Memory warning if Stage 1 takes too long
598
+ if current_stage["stage"] == "stage1" and elapsed > 15:
599
+ logger.warning(f"⚠️ Stage 1 running for {elapsed:.1f}s - monitoring for memory issues")
600
+
601
  except Exception as e:
602
  logger.error(f"Progress callback error: {e}")
603
 
604
+ # Validation with enhanced error context
605
  if background_image is None:
606
  raise ValueError("Background image is required")
607
 
608
  logger.info("DEBUG: Pre-pipeline validation complete")
 
 
 
609
  logger.info(f"DEBUG: Job dir contents before: {list(job_dir.iterdir()) if job_dir.exists() else 'does not exist'}")
610
 
611
+ # Call two-stage pipeline with safe paths
612
+ logger.info("DEBUG: Calling two-stage pipeline with enhanced monitoring...")
613
  result_path = pipeline_process(
614
+ video_path=str(safe_video_path),
615
  background_image=background_image,
616
  workdir=job_dir,
617
  progress=safe_progress_callback,
 
621
  logger.info(f"DEBUG: Pipeline returned: {result_path}")
622
  logger.info(f"DEBUG: Result path type: {type(result_path)}")
623
 
624
+ # Post-processing validation with enhanced checks
625
  if result_path:
626
  result_file = Path(result_path)
627
  logger.info(f"DEBUG: Result file exists: {result_file.exists()}")
628
  if result_file.exists():
629
+ file_size = result_file.stat().st_size
630
+ logger.info(f"DEBUG: Result file size: {file_size} bytes")
631
+ if file_size == 0:
632
+ raise RuntimeError("Pipeline produced empty output file")
633
  logger.info(f"DEBUG: Job dir contents after: {list(job_dir.iterdir())}")
634
+
635
+ # Verify the output is a valid video
636
+ try:
637
+ cap = cv2.VideoCapture(str(result_file))
638
+ if cap.isOpened():
639
+ frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
640
+ logger.info(f"DEBUG: Output video frame count: {frame_count}")
641
+ cap.release()
642
+ else:
643
+ logger.warning("⚠️ Output file may not be a valid video")
644
+ except Exception as e:
645
+ logger.warning(f"⚠️ Could not verify output video: {e}")
646
 
647
  if not result_path or not Path(result_path).exists():
648
  raise RuntimeError("Two-stage pipeline processing failed - no output produced")
649
 
650
  logger.info("=" * 60)
651
+ logger.info(f"βœ… ENHANCED TWO-STAGE PIPELINE COMPLETED: {result_path}")
652
  logger.info("=" * 60)
653
  return result_path
654
+
655
+ # Execute with ChatGPT's comprehensive error wrapper
656
+ try:
657
+ return run_safely(_inner_process)
658
  except Exception as e:
659
+ # Enhanced error cleanup
660
+ logger.error("🧹 Performing error cleanup...")
661
+ clear_gpu_memory()
662
+
663
+ # Additional error context
664
+ logger.error(f"Job directory state: {list(job_dir.iterdir()) if job_dir.exists() else 'does not exist'}")
665
+
 
 
 
 
666
  raise