2 Commits
Author SHA1 Message Date
majestic b07043f919 Replace stub CLI with real compression engine 2026-10-24 02:39:55 -06:00
majestic b694f1a9d7 Add CLI interface 2026-10-24 02:37:44 -06:00
+96
View File
@@ -0,0 +1,96 @@
#!/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())