154 lines
6.4 KiB
Python
154 lines
6.4 KiB
Python
"""Command‑line interface and entry point."""
|
||
|
||
import argparse
|
||
import os
|
||
import sys
|
||
import subprocess
|
||
|
||
from .constants import DEFAULT_BAD_CHARS
|
||
from .defaults import (
|
||
DEFAULT_FORMAT,
|
||
DEFAULT_OUTPUT_TEMPLATE,
|
||
DEFAULT_REPLACEMENT_CHAR,
|
||
DEFAULT_BAD_CHARS,
|
||
DEFAULT_TRACKLIST_FORMAT,
|
||
DEFAULT_ALBUM,
|
||
DEFAULT_COMMENT,
|
||
DEFAULT_COMMENT_STREAM,
|
||
DEFAULT_COMMENT_SEPARATOR,
|
||
DEFAULT_NO_COMMENT,
|
||
DEFAULT_MERGE_COMMENTS,
|
||
DEFAULT_DROP_VIDEO,
|
||
DEFAULT_DROP_SUBS,
|
||
DEFAULT_NUMBER_TRACKS,
|
||
DEFAULT_REPLACE_BAD_CHARS,
|
||
DEFAULT_SKIP_EXISTING,
|
||
DEFAULT_DELETE_ORIGINAL,
|
||
DEFAULT_TRANSCODE_TO,
|
||
)
|
||
from .core import split_audio
|
||
from .tracklist import read_tracklist, parse_format
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description="Split audio file using a tracklist.")
|
||
parser.add_argument('input_file', help='Input audio file')
|
||
parser.add_argument('tracklist_file', help='Tracklist file')
|
||
|
||
# Output options
|
||
parser.add_argument('--format', default=DEFAULT_FORMAT, help=f"Output container format (default: {DEFAULT_FORMAT})")
|
||
parser.add_argument('--transcode-to', default=DEFAULT_TRANSCODE_TO, help="Audio codec to transcode to (default: copy)")
|
||
parser.add_argument('--drop-video', action='store_true', default=DEFAULT_DROP_VIDEO, help="Drop video streams")
|
||
parser.add_argument('--drop-subs', action='store_true', default=DEFAULT_DROP_SUBS, help="Drop subtitle streams")
|
||
|
||
# Filename options
|
||
parser.add_argument('--number-tracks', action='store_true', default=DEFAULT_NUMBER_TRACKS, help="Prepend track numbers")
|
||
parser.add_argument('--output-template', default=DEFAULT_OUTPUT_TEMPLATE, help=f"Output filename template (default: {DEFAULT_OUTPUT_TEMPLATE})")
|
||
parser.add_argument('--replace-bad-chars', action='store_true', default=DEFAULT_REPLACE_BAD_CHARS, help="Replace bad characters")
|
||
parser.add_argument('--replacement-char', default=DEFAULT_REPLACEMENT_CHAR, help=f"Replacement character (default: {DEFAULT_REPLACEMENT_CHAR})")
|
||
parser.add_argument('--bad-chars', default=DEFAULT_BAD_CHARS, help=f"Bad characters to replace (default: {DEFAULT_BAD_CHARS})")
|
||
parser.add_argument('--skip-existing', action='store_true', default=DEFAULT_SKIP_EXISTING, help="Skip existing output files")
|
||
|
||
# Metadata options
|
||
parser.add_argument('--album', default=DEFAULT_ALBUM, help="Album name")
|
||
parser.add_argument('--comment', default=DEFAULT_COMMENT, help="Comment")
|
||
parser.add_argument('--no-comment', action='store_true', default=DEFAULT_NO_COMMENT, help="Ignore comment")
|
||
parser.add_argument('--comment-stream', type=int, default=DEFAULT_COMMENT_STREAM, help="Comment stream index")
|
||
parser.add_argument('--merge-comments', action='store_true', default=DEFAULT_MERGE_COMMENTS, help="Merge all comments")
|
||
parser.add_argument('--comment-separator', default=DEFAULT_COMMENT_SEPARATOR, help=f"Separator for merged comments (default: {DEFAULT_COMMENT_SEPARATOR})")
|
||
|
||
# Tracklist format
|
||
parser.add_argument('--tracklist-format', default=DEFAULT_TRACKLIST_FORMAT, help=f"Tracklist format (default: {DEFAULT_TRACKLIST_FORMAT})")
|
||
|
||
# Other
|
||
parser.add_argument('--delete-original', action='store_true', default=DEFAULT_DELETE_ORIGINAL, help="Delete original file after split")
|
||
parser.add_argument('--dry-run', action='store_true', help="Parse and display tracklist without splitting")
|
||
|
||
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)
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Delete original file if requested and successful.
|
||
# --------------------------------------------------------------------------
|
||
if args.delete_original:
|
||
try:
|
||
os.remove(args.input_file)
|
||
print(f"Deleted original file: {args.input_file}")
|
||
except OSError as e:
|
||
print(f"Warning: Could not delete original file: {e}")
|
||
|
||
print("Done!")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|