| | |
| | """ |
| | Advanced Video Background Replacer - Streamlit Entrypoint |
| | """ |
| | import os |
| | import sys |
| | import time |
| | from pathlib import Path |
| | import logging |
| | import logging.handlers |
| | import traceback |
| | import uuid |
| | from tempfile import NamedTemporaryFile |
| | import threading |
| | import streamlit as st |
| |
|
| | from ui import render_ui |
| | from pipeline.video_pipeline import ( |
| | stage1_create_transparent_video, |
| | stage2_composite_background, |
| | setup_t4_environment, |
| | check_gpu |
| | ) |
| | from models.model_loaders import load_sam2, load_matanyone |
| | from utils.progress_tracker import init_progress, update_progress, mark_complete, get_progress |
| |
|
| | APP_NAME = "Advanced Video Background Replacer" |
| | LOG_FILE = "/tmp/app.log" |
| | LOG_MAX_BYTES = 5 * 1024 * 1024 |
| | LOG_BACKUPS = 5 |
| |
|
| | def setup_logging(level: int = logging.INFO) -> logging.Logger: |
| | logger = logging.getLogger(APP_NAME) |
| | logger.setLevel(level) |
| | logger.propagate = False |
| | for h in list(logger.handlers): |
| | logger.removeHandler(h) |
| | ch = logging.StreamHandler(sys.stdout) |
| | ch.setLevel(level) |
| | ch.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) |
| | fh = logging.handlers.RotatingFileHandler( |
| | LOG_FILE, maxBytes=LOG_MAX_BYTES, backupCount=LOG_BACKUPS, encoding="utf-8" |
| | ) |
| | fh.setLevel(level) |
| | fh.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) |
| | logger.addHandler(ch) |
| | logger.addHandler(fh) |
| | return logger |
| |
|
| | logger = setup_logging() |
| |
|
| | def custom_excepthook(type, value, tb): |
| | logger.error(f"Unhandled: {type.__name__}: {value}\n{''.join(traceback.format_tb(tb))}", exc_info=True) |
| | sys.excepthook = custom_excepthook |
| |
|
| | sam2_predictor = load_sam2() |
| | matanyone_processor = load_matanyone() |
| |
|
| | def initialize_session_state(): |
| | defaults = { |
| | 'uploaded_video': None, |
| | 'video_bytes_cache': None, |
| | 'video_preview_placeholder': None, |
| | 'bg_image_cache': None, |
| | 'bg_preview_placeholder': None, |
| | 'bg_color': "#00FF00", |
| | 'cached_color': None, |
| | 'color_display_cache': None, |
| | 'processed_video_bytes': None, |
| | 'processing': False, |
| | 'gpu_available': None, |
| | 'last_video_id': None, |
| | 'last_bg_image_id': None, |
| | 'last_error': None, |
| | 'log_level_name': 'INFO', |
| | 'auto_refresh_logs': False, |
| | 'log_tail_lines': 400, |
| | 'generated_bg': None, |
| | } |
| | for k, v in defaults.items(): |
| | if k not in st.session_state: |
| | st.session_state[k] = v |
| | if st.session_state.gpu_available is None: |
| | st.session_state.gpu_available = check_gpu(logger) |
| |
|
| | def process_video_background(uploaded_video, background, bg_type): |
| | """Background thread for video processing""" |
| | run_id = uuid.uuid4().hex[:8] |
| | logger.info("=" * 80) |
| | logger.info(f"[RUN {run_id}] VIDEO PROCESSING STARTED at {time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())}") |
| | |
| | t0 = time.time() |
| | try: |
| | init_progress() |
| | update_progress("Starting video processing...", 0, "Initialization") |
| | |
| | suffix = Path(uploaded_video.name).suffix or ".mp4" |
| | with NamedTemporaryFile(delete=False, suffix=suffix) as tmp_vid: |
| | uploaded_video.seek(0) |
| | tmp_vid.write(uploaded_video.read()) |
| | tmp_vid_path = tmp_vid.name |
| | logger.info(f"[RUN {run_id}] Temporary video path: {tmp_vid_path}") |
| |
|
| | def progress_cb(msg): |
| | |
| | progress = 0 |
| | stage = "Processing" |
| | |
| | if "Stage 1 initiated" in msg: |
| | progress, stage = 5, "Stage 1" |
| | elif "GPU engaged" in msg: |
| | progress, stage = 10, "Stage 1 - SAM2" |
| | elif "SAM2 processing frame" in msg: |
| | progress, stage = 15, "Stage 1 - SAM2" |
| | elif "SAM2 complete" in msg: |
| | progress, stage = 25, "Stage 1 - SAM2" |
| | elif "MatAnyone starting" in msg: |
| | progress, stage = 30, "Stage 1 - MatAnyone" |
| | elif "MatAnyone processing" in msg: |
| | progress, stage = 50, "Stage 1 - MatAnyone" |
| | elif "MatAnyone complete" in msg: |
| | progress, stage = 70, "Stage 1 - MatAnyone" |
| | elif "Smoothing" in msg: |
| | progress, stage = 75, "Stage 1 - Smoothing" |
| | elif "transparent video" in msg: |
| | progress, stage = 80, "Stage 1 - Finalizing" |
| | elif "Stage 1 complete" in msg: |
| | progress, stage = 85, "Stage 1 Complete" |
| | elif "Stage 2 begun" in msg: |
| | progress, stage = 86, "Stage 2" |
| | elif "Compositing" in msg: |
| | progress, stage = 90, "Stage 2 - Compositing" |
| | elif "Restoring audio" in msg: |
| | progress, stage = 95, "Stage 2 - Audio" |
| | elif "Stage 2 complete" in msg: |
| | progress, stage = 98, "Stage 2 Complete" |
| | |
| | update_progress(msg, progress, stage) |
| | logger.info(f"[PROGRESS] {msg}") |
| |
|
| | transparent_path, audio_path = stage1_create_transparent_video( |
| | tmp_vid_path, |
| | sam2_predictor=sam2_predictor, |
| | matanyone_processor=matanyone_processor, |
| | mat_timeout_sec=300, |
| | progress_callback=progress_cb |
| | ) |
| | |
| | if not transparent_path or not os.path.exists(transparent_path): |
| | raise RuntimeError("Stage 1 failed: Transparent video not created") |
| | logger.info(f"[RUN {run_id}] Stage 1 completed") |
| |
|
| | final_path = stage2_composite_background( |
| | transparent_path, |
| | audio_path, |
| | background, |
| | bg_type.lower(), |
| | progress_callback=progress_cb |
| | ) |
| | |
| | if not final_path or not os.path.exists(final_path): |
| | raise RuntimeError("Stage 2 failed: Final video not created") |
| | logger.info(f"[RUN {run_id}] Stage 2 completed") |
| |
|
| | with open(final_path, 'rb') as f: |
| | st.session_state.processed_video_bytes = f.read() |
| | |
| | total = time.time() - t0 |
| | logger.info(f"[RUN {run_id}] SUCCESS total Δ={total:.2f}s") |
| | mark_complete(success=True) |
| | |
| | except Exception as e: |
| | total = time.time() - t0 |
| | error_msg = f"Processing Error: {str(e)} (Δ {total:.2f}s)" |
| | logger.error(error_msg) |
| | logger.error(traceback.format_exc()) |
| | st.session_state.last_error = error_msg |
| | mark_complete(success=False, error=str(e)) |
| | finally: |
| | st.session_state.processing = False |
| | logger.info(f"[RUN {run_id}] Processing finished") |
| |
|
| | def main(): |
| | try: |
| | st.set_page_config( |
| | page_title=APP_NAME, |
| | page_icon="🎥", |
| | layout="wide", |
| | initial_sidebar_state="expanded" |
| | ) |
| | initialize_session_state() |
| | render_ui(process_video_background) |
| | except Exception as e: |
| | logger.error(f"Main app error: {e}", exc_info=True) |
| | st.error(f"App startup failed: {str(e)}. Check logs for details.") |
| |
|
| | if __name__ == "__main__": |
| | setup_t4_environment() |
| | main() |