File size: 6,225 Bytes
8e80ab9
 
77308ac
8e80ab9
87680ff
 
 
 
 
6b0d066
3ec06be
dd0209f
 
8e80ab9
d465483
8e80ab9
f01b5cc
8e80ab9
 
 
f01b5cc
8e80ab9
d465483
 
dd0209f
8e80ab9
dd0209f
 
d465483
dd0209f
 
 
8e80ab9
77308ac
dd0209f
 
 
 
 
 
 
 
 
 
 
 
 
d465483
dd0209f
d465483
3ec06be
 
8e80ab9
d465483
77308ac
d465483
 
 
87680ff
db66357
 
 
2b0c920
db66357
2b0c920
db66357
2b0c920
db66357
602f352
db66357
5fe7c80
2b0c920
5685865
dd0209f
 
 
77308ac
 
db66357
dd0209f
 
 
db66357
dd0209f
d465483
f642dbf
8e80ab9
 
77308ac
6337f3a
77308ac
8e80ab9
 
 
 
 
f642dbf
 
8e80ab9
 
 
 
 
6337f3a
f642dbf
 
 
3074b94
d465483
 
 
3074b94
571da76
d465483
8e80ab9
 
6337f3a
f642dbf
 
 
d465483
 
 
 
 
 
8e80ab9
 
6337f3a
f642dbf
 
 
8e80ab9
 
 
 
f642dbf
 
8e80ab9
 
 
 
 
 
 
f642dbf
 
8e80ab9
 
 
 
d465483
8e80ab9
6337f3a
 
 
 
 
 
 
 
 
 
 
 
d465483
87680ff
8e80ab9
d465483
d838606
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/usr/bin/env python3
"""
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 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

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
    # Remove previous handlers (Streamlit reruns)
    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

# Only load once
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(uploaded_video, background, bg_type, progress_callback=None):
    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())}")
    logger.info(f"[RUN {run_id}] Video size={len(uploaded_video.read()) / 1e6:.2f}MB, BG type={bg_type}")
    uploaded_video.seek(0)
    st.session_state.processing = True
    st.session_state.processed_video_bytes = None
    st.session_state.last_error = None
    t0 = time.time()
    try:
        if progress_callback:
            progress_callback("πŸ“₯ Uploading video...")
        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}")

        if progress_callback:
            progress_callback("πŸš€ Stage 1: Creating transparent video (matting & segmentation)...")
        # >>> CHANGE: pass a short watchdog timeout so hangs surface quickly
        transparent_path, audio_path = stage1_create_transparent_video(
            tmp_vid_path,
            sam2_predictor=sam2_predictor,
            matanyone_processor=matanyone_processor,
            mat_timeout_sec=300
        )
        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: Transparent path={transparent_path}, Audio path={audio_path}")

        if progress_callback:
            progress_callback("🎨 Stage 2: Compositing with background and restoring audio...")
        final_path = stage2_composite_background(
            transparent_path,
            audio_path,
            background,
            bg_type.lower()
        )
        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: Final path={final_path}")

        if progress_callback:
            progress_callback("πŸ“€ Loading final video for download...")
        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 size={len(st.session_state.processed_video_bytes)/1e6:.2f}MB, total Ξ”={total:.2f}s")
        if progress_callback:
            progress_callback("βœ… All done!")
        return True
    except Exception as e:
        total = time.time() - t0
        error_msg = f"[RUN {run_id}] Processing Error: {str(e)} (Ξ” {total:.2f}s)\n\nCheck logs for details."
        logger.error(error_msg)
        logger.error(traceback.format_exc())
        st.session_state.last_error = error_msg
        if progress_callback:
            progress_callback(f"❌ ERROR: {str(e)}")
        return False
    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)
    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()