MogensR commited on
Commit
caeb5db
Β·
1 Parent(s): 45a250f

Create cli/main.py

Browse files
Files changed (1) hide show
  1. cli/main.py +282 -0
cli/main.py ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Command-line interface for BackgroundFX Pro.
3
+ Integrates with existing app.py infrastructure.
4
+ """
5
+
6
+ import click
7
+ import sys
8
+ import os
9
+ from pathlib import Path
10
+ from typing import Optional, Tuple
11
+ import logging
12
+ from rich.console import Console
13
+ from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
14
+ from rich.table import Table
15
+
16
+ # Import the existing application components from app.py
17
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
18
+
19
+ # Import from existing app.py
20
+ from app import (
21
+ VideoProcessor,
22
+ processor as app_processor, # Use the global processor instance
23
+ PROFESSIONAL_BACKGROUNDS,
24
+ TWO_STAGE_AVAILABLE,
25
+ CHROMA_PRESETS
26
+ )
27
+
28
+ console = Console()
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ @click.group()
33
+ @click.option('--verbose', '-v', is_flag=True, help='Verbose output')
34
+ @click.option('--debug', is_flag=True, help='Debug mode')
35
+ def cli(verbose: bool, debug: bool):
36
+ """
37
+ BackgroundFX Pro CLI - Professional video background replacement.
38
+
39
+ Uses the same processing engine as the Gradio UI.
40
+ """
41
+ # Setup logging
42
+ log_level = logging.DEBUG if debug else (logging.INFO if verbose else logging.WARNING)
43
+ logging.basicConfig(
44
+ level=log_level,
45
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
46
+ )
47
+
48
+
49
+ @cli.command()
50
+ @click.option('--force', is_flag=True, help='Force reload models')
51
+ def load_models(force: bool):
52
+ """Load AI models for processing."""
53
+ console.print("[bold blue]Loading models...[/bold blue]")
54
+
55
+ def progress_callback(progress: float, message: str):
56
+ console.print(f" {int(progress*100)}% - {message}")
57
+
58
+ # Use existing processor
59
+ if force or not app_processor.models_loaded:
60
+ result = app_processor.load_models(progress_callback)
61
+ console.print(f"[green]βœ“[/green] {result}")
62
+ else:
63
+ console.print("[yellow]Models already loaded[/yellow]")
64
+
65
+
66
+ @cli.command()
67
+ @click.argument('input_video', type=click.Path(exists=True))
68
+ @click.argument('output_video', type=click.Path())
69
+ @click.option('--background', '-b',
70
+ type=click.Choice(list(PROFESSIONAL_BACKGROUNDS.keys()) + ['custom']),
71
+ default='blur',
72
+ help='Background type')
73
+ @click.option('--background-image', '-i', type=click.Path(exists=True),
74
+ help='Custom background image (when using custom background)')
75
+ @click.option('--two-stage', is_flag=True,
76
+ help='Use two-stage processing (cinema quality)')
77
+ @click.option('--chroma-preset',
78
+ type=click.Choice(list(CHROMA_PRESETS.keys()) if TWO_STAGE_AVAILABLE else ['standard']),
79
+ default='standard',
80
+ help='Chroma keying preset for two-stage')
81
+ @click.option('--preview-mask', is_flag=True,
82
+ help='Generate mask preview video')
83
+ @click.option('--preview-greenscreen', is_flag=True,
84
+ help='Generate greenscreen preview video')
85
+ def process(input_video: str, output_video: str, background: str,
86
+ background_image: Optional[str], two_stage: bool,
87
+ chroma_preset: str, preview_mask: bool, preview_greenscreen: bool):
88
+ """Process a video file."""
89
+
90
+ # Check if models are loaded
91
+ if not app_processor.models_loaded:
92
+ console.print("[yellow]Loading models first...[/yellow]")
93
+
94
+ def progress_callback(progress: float, message: str):
95
+ console.print(f" {int(progress*100)}% - {message}")
96
+
97
+ result = app_processor.load_models(progress_callback)
98
+ console.print(f"[green]βœ“[/green] {result}")
99
+
100
+ # Validate custom background
101
+ if background == 'custom' and not background_image:
102
+ console.print("[red]Error: Custom background requires --background-image[/red]")
103
+ sys.exit(1)
104
+
105
+ console.print(f"[bold blue]Processing video:[/bold blue] {input_video}")
106
+ console.print(f" Background: {background}")
107
+ console.print(f" Two-stage: {'Yes' if two_stage else 'No'}")
108
+
109
+ with Progress(
110
+ SpinnerColumn(),
111
+ TextColumn("[progress.description]{task.description}"),
112
+ BarColumn(),
113
+ console=console
114
+ ) as progress:
115
+
116
+ task = progress.add_task("Processing...", total=100)
117
+
118
+ def progress_callback(value: float, message: str):
119
+ progress.update(task, completed=int(value * 100), description=message)
120
+
121
+ # Process video using existing processor
122
+ result_path, message = app_processor.process_video(
123
+ video_path=input_video,
124
+ background_choice=background,
125
+ custom_background_path=background_image if background == 'custom' else None,
126
+ progress_callback=progress_callback,
127
+ use_two_stage=two_stage,
128
+ chroma_preset=chroma_preset,
129
+ preview_mask=preview_mask,
130
+ preview_greenscreen=preview_greenscreen
131
+ )
132
+
133
+ if result_path:
134
+ # Move/copy result to desired output path
135
+ import shutil
136
+ shutil.move(result_path, output_video)
137
+
138
+ console.print(f"[green]βœ“ Success![/green]")
139
+ console.print(f" Output: {output_video}")
140
+ console.print(f" {message}")
141
+ else:
142
+ console.print(f"[red]βœ— Failed:[/red] {message}")
143
+ sys.exit(1)
144
+
145
+
146
+ @cli.command()
147
+ def status():
148
+ """Show system and model status."""
149
+ status_info = app_processor.get_status()
150
+
151
+ # Create status table
152
+ table = Table(title="BackgroundFX Pro Status")
153
+ table.add_column("Component", style="cyan")
154
+ table.add_column("Status", style="green")
155
+
156
+ table.add_row("Models Loaded", "βœ“" if status_info['models_loaded'] else "βœ—")
157
+ table.add_row("Device", str(status_info['device']))
158
+ table.add_row("Two-Stage Available", "βœ“" if status_info['two_stage_available'] else "βœ—")
159
+
160
+ if 'memory_usage' in status_info:
161
+ mem = status_info['memory_usage']
162
+ table.add_row("Memory Usage", f"{mem['percent']:.1f}% ({mem['used_gb']:.1f}/{mem['total_gb']:.1f} GB)")
163
+
164
+ if 'models' in status_info:
165
+ models = status_info['models']
166
+ table.add_row("SAM2 Predictor", "βœ“" if models.get('sam2_loaded') else "βœ—")
167
+ table.add_row("MatAnyone", "βœ“" if models.get('matanyone_loaded') else "βœ—")
168
+
169
+ console.print(table)
170
+
171
+
172
+ @cli.command()
173
+ def list_backgrounds():
174
+ """List available background options."""
175
+ table = Table(title="Available Backgrounds")
176
+ table.add_column("ID", style="cyan")
177
+ table.add_column("Description", style="white")
178
+ table.add_column("Type", style="yellow")
179
+
180
+ for bg_id, bg_info in PROFESSIONAL_BACKGROUNDS.items():
181
+ table.add_row(
182
+ bg_id,
183
+ bg_info.get('description', 'Professional background'),
184
+ bg_info.get('type', 'gradient')
185
+ )
186
+
187
+ table.add_row("custom", "Use your own image", "image")
188
+
189
+ console.print(table)
190
+
191
+
192
+ @cli.command()
193
+ def cleanup():
194
+ """Clean up resources and cache."""
195
+ console.print("[bold blue]Cleaning up resources...[/bold blue]")
196
+
197
+ app_processor.cleanup_resources()
198
+
199
+ # Clean temporary files
200
+ import tempfile
201
+ import shutil
202
+ temp_dir = Path(tempfile.gettempdir())
203
+
204
+ patterns = ['processed_video_*.mp4', 'mask_preview_*.mp4', 'greenscreen_preview_*.mp4']
205
+ removed = 0
206
+
207
+ for pattern in patterns:
208
+ for file in temp_dir.glob(pattern):
209
+ try:
210
+ file.unlink()
211
+ removed += 1
212
+ except:
213
+ pass
214
+
215
+ console.print(f"[green]βœ“[/green] Cleaned up {removed} temporary files")
216
+ console.print("[green]βœ“[/green] Memory resources freed")
217
+
218
+
219
+ @cli.command()
220
+ @click.argument('input_dir', type=click.Path(exists=True))
221
+ @click.argument('output_dir', type=click.Path())
222
+ @click.option('--background', '-b', default='blur', help='Background type')
223
+ @click.option('--pattern', '-p', default='*.mp4', help='File pattern to match')
224
+ @click.option('--two-stage', is_flag=True, help='Use two-stage processing')
225
+ def batch(input_dir: str, output_dir: str, background: str, pattern: str, two_stage: bool):
226
+ """Process multiple videos in batch."""
227
+ input_path = Path(input_dir)
228
+ output_path = Path(output_dir)
229
+ output_path.mkdir(parents=True, exist_ok=True)
230
+
231
+ # Find videos
232
+ videos = list(input_path.glob(pattern))
233
+
234
+ if not videos:
235
+ console.print(f"[yellow]No files matching '{pattern}' found in {input_dir}[/yellow]")
236
+ return
237
+
238
+ console.print(f"[bold blue]Found {len(videos)} videos to process[/bold blue]")
239
+
240
+ # Ensure models are loaded
241
+ if not app_processor.models_loaded:
242
+ console.print("[yellow]Loading models...[/yellow]")
243
+ app_processor.load_models()
244
+
245
+ # Process each video
246
+ success_count = 0
247
+
248
+ for i, video_file in enumerate(videos, 1):
249
+ console.print(f"\n[bold]Processing {i}/{len(videos)}:[/bold] {video_file.name}")
250
+
251
+ output_file = output_path / f"processed_{video_file.name}"
252
+
253
+ def progress_callback(value: float, message: str):
254
+ console.print(f" {int(value*100)}% - {message}", end='\r')
255
+
256
+ result_path, message = app_processor.process_video(
257
+ video_path=str(video_file),
258
+ background_choice=background,
259
+ custom_background_path=None,
260
+ progress_callback=progress_callback,
261
+ use_two_stage=two_stage,
262
+ chroma_preset='standard'
263
+ )
264
+
265
+ if result_path:
266
+ import shutil
267
+ shutil.move(result_path, str(output_file))
268
+ console.print(f" [green]βœ“[/green] Saved to {output_file.name}")
269
+ success_count += 1
270
+ else:
271
+ console.print(f" [red]βœ—[/red] Failed: {message}")
272
+
273
+ console.print(f"\n[bold]Batch complete:[/bold] {success_count}/{len(videos)} successful")
274
+
275
+
276
+ def main():
277
+ """Main CLI entry point."""
278
+ cli()
279
+
280
+
281
+ if __name__ == '__main__':
282
+ main()