From cb4d4d8832cee0c7d9274db80826bfa856dcfd12 Mon Sep 17 00:00:00 2001 From: Deepak Singh Date: Fri, 27 Mar 2026 08:02:54 +0000 Subject: [PATCH] refactor: convert CLI to subcommand structure (closes #54) --- src/sounddiff/cli.py | 54 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/src/sounddiff/cli.py b/src/sounddiff/cli.py index 2355aa9..775a931 100644 --- a/src/sounddiff/cli.py +++ b/src/sounddiff/cli.py @@ -12,14 +12,35 @@ from sounddiff import __version__ from sounddiff.core import diff -from sounddiff.formats import FFMPEG_FORMATS +from sounddiff.formats import FFMPEG_FORMATS, load_audio from sounddiff.report import render from sounddiff.types import OutputFormat _PROGRESS_THRESHOLD = 30 -@click.command() +@click.group(invoke_without_command=True) +@click.version_option(version=__version__, prog_name="sounddiff") +@click.pass_context +def main(ctx: click.Context) -> None: + """Compare and inspect audio files. + + sounddiff compare FILE_A FILE_B + sounddiff inspect FILE + """ + if ctx.invoked_subcommand is None: + # Handle legacy positional arguments: sounddiff FILE_A FILE_B + # We only do this if exactly two arguments are provided and they look like paths. + args = sys.argv[1:] + # Filter out flags/options + pos_args = [a for a in args if not a.startswith("-")] + if len(pos_args) == 2: + ctx.invoke(compare, file_a=pos_args[0], file_b=pos_args[1]) + else: + click.echo(ctx.get_help()) + + +@main.command() @click.argument("file_a", type=click.Path(exists=True)) @click.argument("file_b", type=click.Path(exists=True)) @click.option( @@ -49,8 +70,7 @@ default=False, help="Disable colored terminal output.", ) -@click.version_option(version=__version__, prog_name="sounddiff") -def main( +def compare( file_a: str, file_b: str, output_format: str, @@ -60,8 +80,6 @@ def main( ) -> None: """Compare two audio files and report what changed. - sounddiff FILE_A FILE_B - Compares FILE_A (reference) against FILE_B (comparison) and reports differences in loudness, spectral content, timing, and potential issues. """ @@ -113,3 +131,27 @@ def main( click.echo(f"Report written to {output_path}") else: click.echo(output) + + +@main.command() +@click.argument("file", type=click.Path(exists=True)) +def inspect(file: str) -> None: + """Inspect a single audio file's metadata and properties. + + Displays duration, sample rate, channel count, bit depth, and format. + """ + try: + _, meta = load_audio(file) + click.echo(f"File: {meta.path}") + click.echo(f"Duration: {meta.duration:.3f}s") + click.echo(f"Sample Rate: {meta.sample_rate}Hz") + click.echo(f"Channels: {meta.channels}") + click.echo(f"Bit Depth: {meta.bit_depth or 'Unknown'}") + click.echo(f"Format: {meta.format_name}") + except Exception as e: + click.echo(f"Error: {e}", err=True) + sys.exit(1) + + +if __name__ == "__main__": + main()