From b07043f919eacfcef33fcb8c72a15614ad7377f7 Mon Sep 17 00:00:00 2001 From: majestic Date: Sat, 24 Oct 2026 02:39:55 -0600 Subject: [PATCH] Replace stub CLI with real compression engine --- bolt_compress/cli.py | 82 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 3 deletions(-) diff --git a/bolt_compress/cli.py b/bolt_compress/cli.py index acda876..ebbba3d 100644 --- a/bolt_compress/cli.py +++ b/bolt_compress/cli.py @@ -4,17 +4,93 @@ 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() - print(f"Bolt-Compress CLI: {len(args.input)} item(s), quality {args.quality}") - # Compression engine hooks in here - return 0 + 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())