Files
audio_splitter/audio_splitter/main.py
T

220 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Commandline 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: <input_basename>_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.
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 (0based). 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: "; ")'
)
# NEW: Delete original after successful split.
parser.add_argument(
'--delete-original',
action='store_true',
help='Delete the original input file after successful splitting (default: keep)'
)
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 userprovided 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.")
# Dryrun 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("Dryrun 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()