File size: 7,131 Bytes
8e80ab9
 
77308ac
8e80ab9
87680ff
 
 
 
 
6b0d066
3ec06be
dd0209f
 
8299fd5
8e80ab9
d465483
8e80ab9
f01b5cc
8e80ab9
 
 
f01b5cc
8e80ab9
d465483
8299fd5
d465483
dd0209f
8e80ab9
dd0209f
 
d465483
dd0209f
 
 
8e80ab9
dd0209f
 
 
 
 
 
 
 
 
 
 
 
 
d465483
dd0209f
d465483
3ec06be
 
8e80ab9
d465483
 
 
 
87680ff
db66357
 
 
2b0c920
db66357
2b0c920
db66357
2b0c920
db66357
602f352
db66357
5fe7c80
2b0c920
5685865
dd0209f
 
 
77308ac
 
db66357
dd0209f
 
 
db66357
dd0209f
d465483
8299fd5
 
8e80ab9
 
77308ac
8299fd5
8e80ab9
 
8299fd5
 
 
8e80ab9
 
 
 
 
6337f3a
f642dbf
8299fd5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d465483
 
 
3074b94
8299fd5
 
d465483
8299fd5
8e80ab9
 
8299fd5
f642dbf
d465483
 
 
 
8299fd5
 
d465483
8299fd5
8e80ab9
 
8299fd5
f642dbf
8e80ab9
 
8299fd5
8e80ab9
8299fd5
 
 
8e80ab9
 
8299fd5
8e80ab9
 
 
8299fd5
8e80ab9
 
 
d465483
8e80ab9
6337f3a
 
 
 
 
 
 
 
8299fd5
6337f3a
 
 
d465483
87680ff
8e80ab9
8299fd5
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#!/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 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):
            # Map messages to progress percentages and stages
            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()