"""Command‑line interface and entry point.""" import os import sys import subprocess import argparse from .constants import DEFAULT_BAD_CHARS from .tracklist import read_tracklist, parse_format from .core import split_audio def main(): """Parse arguments and start the splitting process.""" parser = argparse.ArgumentParser( description="Split an audio file into tracks using a tracklist.", epilog="Tracklist format: mm:ss track_name - author_name" ) # Positional arguments. parser.add_argument('input_file', help='Input audio/video file') parser.add_argument('tracklist_file', help='Tracklist file') # Optional arguments. parser.add_argument( '--output-dir', '-o', help='Output directory for split tracks (default: _splits)' ) parser.add_argument( '--format', help='Output container format (e.g., mp3, m4a, mkv, mp4, ogg, opus)' ) parser.add_argument( '--transcode-to', help='Re-encode audio to this codec (e.g., libmp3lame, aac, libopus)' ) parser.add_argument( '--drop-video', action='store_true', help='Remove video streams from output' ) parser.add_argument( '--drop-subs', action='store_true', help='Remove subtitle streams from output' ) parser.add_argument( '--number-tracks', action='store_true', help='Prepend track number to output filenames (convenience; use %%num in template for full control)' ) parser.add_argument( '--replace-bad-chars', action='store_true', help='Replace problematic characters in filenames (default: off)' ) parser.add_argument( '--replacement-char', default='_', help='Character used as replacement (default: "_")' ) parser.add_argument( '--bad-chars', default=DEFAULT_BAD_CHARS, help='String of characters to replace (default includes space and single quote)' ) parser.add_argument( '--skip-existing', action='store_true', help='Skip extraction if output file already exists (default: overwrite)' ) parser.add_argument( '--tracklist-format', default='%ts %tn - %an', help='Format of each line in the tracklist using placeholders: ' '%%ts (timestamp), %%tn (track name), %%an (author), %%al (album), ' '%%date (date/year), %%ext (file extension). ' 'Default: "%%ts %%tn - %%an"' ) parser.add_argument( '--output-template', default='%an-%tn.%ext', help='Template for output filenames using placeholders: ' '%%tn (track name), %%an (author), %%al (album), ' '%%date (date/year), %%ext (file extension), %%num (track number). ' 'Default: "%%an-%%tn.%%ext"' ) parser.add_argument( '--dry-run', action='store_true', help='Parse and display the tracklist without splitting any files' ) # ------------------------------------------------------------ # Metadata options (new) # ------------------------------------------------------------ parser.add_argument( '--album', help='Set album name in output metadata (overrides parsed %%al and original album)' ) parser.add_argument( '--comment', help='Explicit comment text (overrides all other comment settings)' ) parser.add_argument( '--no-comment', action='store_true', help='Explicitly ignore any comment (no comment written)' ) parser.add_argument( '--comment-stream', type=int, default=None, help='Select comment from a specific stream index (0‑based). Default: first stream with a comment.' ) parser.add_argument( '--merge-comments', action='store_true', help='Merge all comments from all streams into one (separated by --comment-separator)' ) parser.add_argument( '--comment-separator', default='; ', help='Separator used when merging comments (default: "; ")' ) args = parser.parse_args() # -------------------------------------------------------------------------- # Input validation # -------------------------------------------------------------------------- if not os.path.exists(args.input_file): print(f"Error: Input file not found: {args.input_file}") sys.exit(1) if not os.path.exists(args.tracklist_file): print(f"Error: Tracklist file not found: {args.tracklist_file}") sys.exit(1) # Ensure the replacement character is a single character. if len(args.replacement_char) != 1: print("Error: --replacement-char must be a single character.") sys.exit(1) # Check that FFmpeg is installed. try: subprocess.run(['ffmpeg', '-version'], capture_output=True, check=True) except (subprocess.CalledProcessError, FileNotFoundError): print("Error: FFmpeg not found. Please install FFmpeg first.") print(" - https://ffmpeg.org/download.html") sys.exit(1) # Parse the tracklist using the user‑provided format. try: tokens = parse_format(args.tracklist_format) except ValueError as error: print(f"Error in --tracklist-format: {error}") sys.exit(1) # Read and parse the tracklist file. tracks = read_tracklist(args.tracklist_file, tokens) if not tracks: print("Error: No valid tracks found in tracklist file.") sys.exit(1) print(f"Found {len(tracks)} tracks.") # Dry‑run mode: display parsed data and exit. if args.dry_run: print("\nParsed tracklist:") print("-" * 60) if tracks: fields = sorted(tracks[0].keys()) header = " | ".join(f"{f:>10}" for f in fields) print(header) print("-" * len(header)) for idx, track in enumerate(tracks, 1): values = [f"{track.get(f, ''):>10}" for f in fields] print(f"{idx:3d} | " + " | ".join(values)) print("-" * 60) print("Dry‑run complete. No files were created.") sys.exit(0) # Determine the output directory. if args.output_dir: output_dir = args.output_dir print(f"Using custom output directory: {output_dir}") else: base_name = os.path.splitext(os.path.basename(args.input_file))[0] output_dir = base_name + "_splits" print(f"Using default output directory: {output_dir}") # Run the splitter. try: split_audio(args.input_file, output_dir, tracks, args) except (RuntimeError, ValueError) as error: print(f"Error: {error}") sys.exit(1) print("Done!") if __name__ == "__main__": main()