#!/usr/bin/env python3 """Command-line interface for Bolt-Compress.""" import argparse import sys from pathlib import Path from .media import classify_path, find_ffmpeg_tools, human_size from .models import ImageOptions, VideoOptions, MediaKind from .processing import compress_image, compress_video def _collect_inputs(paths): """Expand directories into media files.""" files = [] for raw in paths: p = Path(raw) if p.is_dir(): for child in sorted(p.rglob("*")): if child.is_file() and classify_path(child): files.append(child) elif p.is_file() and classify_path(p): files.append(p) else: print(f"Skipping unsupported file: {raw}", file=sys.stderr) return files def _output_path(source: Path, output_dir: Path | None) -> Path: if output_dir: return output_dir / source.name return source.with_name(source.stem + "_compressed" + source.suffix) def main(): parser = argparse.ArgumentParser(description="Compress media files from the command line.") parser.add_argument("input", nargs="+", help="Files or directories to compress") parser.add_argument("-o", "--output", help="Output directory") parser.add_argument("-q", "--quality", type=int, default=80, help="Compression quality (1-100)") parser.add_argument("--batch", action="store_true", help="Process files in batch mode") parser.add_argument("--format", choices=["original", "jpeg", "webp", "png"], default="original", help="Output image format") parser.add_argument("--codec", choices=["h264", "h265", "vp9", "av1"], default="h264", help="Video codec") parser.add_argument("--resize", choices=["original", "fit", "percent"], default="original", help="Image resize mode") parser.add_argument("--max-width", type=int, default=1920, help="Max width for fit resize") parser.add_argument("--max-height", type=int, default=1080, help="Max height for fit resize") parser.add_argument("--scale", type=int, default=80, help="Scale percent for percent resize") parser.add_argument("--keep-metadata", action="store_true", help="Preserve metadata") args = parser.parse_args() files = _collect_inputs(args.input) if not files: print("No supported media files found.", file=sys.stderr) return 1 output_dir = Path(args.output) if args.output else None if output_dir: output_dir.mkdir(parents=True, exist_ok=True) tools = find_ffmpeg_tools() image_opts = ImageOptions( output_format=args.format, quality=args.quality, resize_mode=args.resize, max_width=args.max_width, max_height=args.max_height, scale_percent=args.scale, keep_metadata=args.keep_metadata, ) video_opts = VideoOptions( codec=args.codec, quality=args.quality, keep_metadata=args.keep_metadata, ) total = len(files) succeeded = 0 for i, source in enumerate(files, 1): kind = classify_path(source) dest = _output_path(source, output_dir) print(f"[{i}/{total}] {source.name} -> {dest.name}") try: if kind == MediaKind.IMAGE: compress_image(source, dest, image_opts) else: compress_video(source, dest, video_opts, tools) saved = source.stat().st_size - dest.stat().st_size print(f" Done: {human_size(source.stat().st_size)} -> {human_size(dest.stat().st_size)} (saved {human_size(max(0, saved))})") succeeded += 1 except Exception as exc: print(f" Failed: {exc}", file=sys.stderr) print(f"\n{succeeded}/{total} files compressed successfully.") return 0 if succeeded == total else 1 if __name__ == "__main__": sys.exit(main())