|
| 1 | +"""Command Line Interface for Shorts Maker, orchestrating the entire processing pipeline.""" |
| 2 | + |
1 | 3 | import logging |
| 4 | +import multiprocessing |
2 | 5 | from pathlib import Path |
3 | 6 |
|
4 | 7 | import typer |
|
9 | 12 |
|
10 | 13 | app = typer.Typer(help="GPU-accelerated shorts generator.") |
11 | 14 |
|
| 15 | +def _process_video_worker(config: ProcessingConfig, video_file: Path, output_dir: Path) -> None: |
| 16 | + """Isolated worker for processing a single video. |
| 17 | +
|
| 18 | + Ensures that PyTorch and VPF memory is entirely cleared upon exit. |
| 19 | + """ |
| 20 | + processor = VideoProcessor(config) |
| 21 | + processor.process_video(video_file, output_dir) |
| 22 | + |
12 | 23 |
|
13 | 24 | @app.command() |
14 | 25 | def process( |
@@ -53,15 +64,27 @@ def process( |
53 | 64 | logger.warning(f"No '{input_dir}' directory found. Exiting.") |
54 | 65 | raise typer.Exit(code=1) |
55 | 66 |
|
56 | | - processor = VideoProcessor(config) |
57 | | - |
58 | 67 | for video_file in input_dir.iterdir(): |
59 | 68 | if video_file.is_file() and video_file.suffix.lower() in [ |
60 | 69 | ".mp4", |
61 | 70 | ".mkv", |
62 | 71 | ".mov", |
63 | 72 | ]: |
64 | | - processor.process_video(video_file, output_dir) |
| 73 | + logger.info(f"\n--- Spawning isolated process for: {video_file.name} ---") |
| 74 | + |
| 75 | + # Using 'spawn' guarantees a clean process without inherited CUDA contexts |
| 76 | + ctx = multiprocessing.get_context("spawn") |
| 77 | + p = ctx.Process( |
| 78 | + target=_process_video_worker, |
| 79 | + args=(config, video_file, output_dir) |
| 80 | + ) |
| 81 | + p.start() |
| 82 | + p.join() |
| 83 | + |
| 84 | + if p.exitcode != 0: |
| 85 | + logger.error(f"Processing failed for {video_file.name} with exit code {p.exitcode}") |
| 86 | + if p.exitcode in (-9, 137): |
| 87 | + logger.error("Process was likely OOM killed by Docker/WSL.") |
65 | 88 |
|
66 | 89 |
|
67 | 90 | if __name__ == "__main__": |
|
0 commit comments