Project modified to be installable, installation files added
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
"""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: <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 (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()
|
||||
Reference in New Issue
Block a user