from __future__ import annotations from pathlib import Path import typer from rich import print from .config import load_settings, override_settings from .pipeline import MattingPipeline, run_image app = typer.Typer(help="Offline flat-background character matting.") @app.command() def main( input: Path | None = typer.Option(None, "--input", "-i", exists=True, file_okay=True, dir_okay=False), output: Path | None = typer.Option(None, "--output", "-o", file_okay=True, dir_okay=False), input_dir: Path | None = typer.Option(None, "--input-dir", exists=True, file_okay=False, dir_okay=True), output_dir: Path | None = typer.Option(None, "--output-dir", file_okay=False, dir_okay=True), debug_dir: Path | None = typer.Option(None, "--debug-dir", file_okay=False, dir_okay=True), config: Path | None = typer.Option(None, "--config", exists=True, file_okay=True, dir_okay=False), model_name: str | None = typer.Option(None, "--model-name"), device: str | None = typer.Option(None, "--device"), screen_color: str | None = typer.Option(None, "--screen-color", help="Background colour prior as #RRGGBB (default: auto-detect the flat background colour)"), matting_method: str | None = typer.Option(None, "--matting-method"), fallback_to_chroma_alpha: bool | None = typer.Option(None, "--fallback-to-chroma-alpha/--no-fallback-to-chroma-alpha"), sure_bg_threshold: float | None = typer.Option(None, "--sure-bg-threshold", min=0.0, max=1.0), sure_fg_threshold: float | None = typer.Option(None, "--sure-fg-threshold", min=0.0, max=1.0), unknown_radius_ratio: float | None = typer.Option(None, "--unknown-radius-ratio", min=0.0), fg_safe_radius_ratio: float | None = typer.Option(None, "--fg-safe-radius-ratio", min=0.0), despill: bool | None = typer.Option(None, "--despill/--no-despill"), cross_check: bool | None = typer.Option(None, "--cross-check/--no-cross-check", help="Second-opinion veto of background-hued residue between hair strands (default: on; costs one extra model inference)"), trimap_mode: str | None = typer.Option(None, "--trimap-mode", help="Trimap mode (segmentation pipeline): directional | seg | directional-hard-bg"), seg_backend: str | None = typer.Option(None, "--seg-backend", help="Segmentation backend: birefnet (default) | anime-seg"), ) -> None: try: settings = override_settings( load_settings(config), model_name=model_name, device=device, screen_color=screen_color, matting_method=matting_method, fallback_to_chroma_alpha=fallback_to_chroma_alpha, sure_bg_threshold=sure_bg_threshold, sure_fg_threshold=sure_fg_threshold, unknown_radius_ratio=unknown_radius_ratio, fg_safe_radius_ratio=fg_safe_radius_ratio, despill=despill, cross_check=cross_check, trimap_mode=trimap_mode, seg_backend=seg_backend, ) if input_dir is not None: if output_dir is None: raise typer.BadParameter("--output-dir is required when --input-dir is used") images = sorted( p for p in input_dir.iterdir() if p.suffix.lower() in {".png", ".jpg", ".jpeg", ".webp"} ) if not images: raise typer.BadParameter(f"No supported images found in {input_dir}") pipeline = MattingPipeline(settings) for image_path in images: out_path = output_dir / f"{image_path.stem}_rgba.png" dbg = None if debug_dir is None else debug_dir / image_path.stem result = pipeline.run_image(image_path, out_path, dbg) print(f"[green]wrote[/green] {result['output']}") return if input is None or output is None: raise typer.BadParameter("Use either --input/--output or --input-dir/--output-dir") result = run_image(input, output, debug_dir, settings) print(f"[green]wrote[/green] {result['output']}") except RuntimeError as exc: print(f"[red]error:[/red] {exc}") raise typer.Exit(code=1) from exc except ValueError as exc: print(f"[red]config error:[/red] {exc}") raise typer.Exit(code=1) from exc if __name__ == "__main__": app()