131 lines
5.5 KiB
Python
131 lines
5.5 KiB
Python
"""Core logic: orchestrates the splitting process."""
|
||
|
||
import os
|
||
import subprocess
|
||
|
||
from .constants import FORMAT_INFO
|
||
from .ffmpeg import get_audio_duration, get_stream_info, get_metadata, build_ffmpeg_command
|
||
from .formats import determine_output_format, validate_format_compatibility
|
||
from .timestamp import parse_track_timestamps, resolve_end_times
|
||
from .filename import build_filename
|
||
from .metadata import build_metadata_dict
|
||
from .utils import format_time
|
||
|
||
|
||
def split_audio(input_file, output_directory, tracks, args):
|
||
"""
|
||
Main orchestration function: split the audio file into tracks.
|
||
|
||
Args:
|
||
input_file: Path to the input media file.
|
||
output_directory: Directory where output files will be saved.
|
||
tracks: List of dicts, each containing parsed fields.
|
||
args: Parsed command‑line arguments (namespace).
|
||
|
||
Raises:
|
||
RuntimeError: If no audio stream is found.
|
||
ValueError: If timestamp parsing or format compatibility fails.
|
||
"""
|
||
# --------------------------------------------------------------------------
|
||
# 1. Setup
|
||
# --------------------------------------------------------------------------
|
||
os.makedirs(output_directory, exist_ok=True)
|
||
|
||
total_duration = get_audio_duration(input_file)
|
||
stream_info = get_stream_info(input_file)
|
||
|
||
print(f"Detected streams: audio={stream_info['has_audio']}, "
|
||
f"video={stream_info['has_video']}, subs={stream_info['has_subtitle']}")
|
||
print(f"Audio codec: {stream_info.get('audio_codec', 'unknown')}")
|
||
|
||
if not stream_info['has_audio']:
|
||
raise RuntimeError("No audio stream found in input file.")
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 2. Format decision
|
||
# --------------------------------------------------------------------------
|
||
output_format = determine_output_format(stream_info, args.format, args.transcode_to)
|
||
print(f"Output container: {output_format}")
|
||
|
||
validate_format_compatibility(output_format, stream_info,
|
||
args.drop_video, args.drop_subs)
|
||
|
||
# Determine file extension.
|
||
extension_info = FORMAT_INFO.get(output_format, {})
|
||
extension = extension_info.get('ext', '.mkv')
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 3. Parse timestamps
|
||
# --------------------------------------------------------------------------
|
||
track_times = parse_track_timestamps(tracks)
|
||
resolved_times = resolve_end_times(track_times, total_duration)
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 4. Fetch original metadata (for fallbacks)
|
||
# --------------------------------------------------------------------------
|
||
input_metadata = get_metadata(input_file)
|
||
original_album = input_metadata.get('album')
|
||
original_title = input_metadata.get('title')
|
||
original_comments = input_metadata.get('comments', [])
|
||
|
||
if original_album:
|
||
print(f"Original album: '{original_album}'")
|
||
if original_title:
|
||
print(f"Original title: '{original_title}'")
|
||
if original_comments:
|
||
print(f"Found {len(original_comments)} comment(s) in input file.")
|
||
for idx, comment in original_comments:
|
||
print(f" Stream {idx}: '{comment}'")
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 5. Process each track
|
||
# --------------------------------------------------------------------------
|
||
for idx, track in enumerate(tracks, start=1):
|
||
start_seconds, end_seconds = resolved_times[idx - 1]
|
||
duration_seconds = end_seconds - start_seconds
|
||
|
||
if duration_seconds <= 0:
|
||
print(f"Warning: Track {idx} has zero or negative duration, skipping.")
|
||
continue
|
||
|
||
track_name = track.get('tn', 'Unknown')
|
||
|
||
# ----------------------------------------------------------------------
|
||
# 5a. Build filename
|
||
# ----------------------------------------------------------------------
|
||
clean_filename = build_filename(track, idx, extension, args)
|
||
output_path = os.path.join(output_directory, clean_filename)
|
||
|
||
# ----------------------------------------------------------------------
|
||
# 5b. Handle existing files
|
||
# ----------------------------------------------------------------------
|
||
if args.skip_existing and os.path.exists(output_path):
|
||
print(f"Skipping track {idx}: {output_path} already exists.")
|
||
continue
|
||
|
||
# ----------------------------------------------------------------------
|
||
# 5c. Build metadata
|
||
# ----------------------------------------------------------------------
|
||
metadata = build_metadata_dict(track, idx, input_metadata, args)
|
||
|
||
# ----------------------------------------------------------------------
|
||
# 5d. Build and execute FFmpeg command
|
||
# ----------------------------------------------------------------------
|
||
cmd = build_ffmpeg_command(
|
||
input_file, start_seconds, duration_seconds, output_path,
|
||
stream_info, output_format, args.transcode_to,
|
||
args.drop_video, args.drop_subs,
|
||
metadata=metadata
|
||
)
|
||
|
||
print(f"Extracting track {idx}: {track_name} "
|
||
f"({format_time(start_seconds)} - {format_time(end_seconds)})")
|
||
|
||
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
||
|
||
if result.returncode != 0:
|
||
print(f"ERROR extracting track {idx}:")
|
||
print(result.stderr)
|
||
else:
|
||
print(f" -> Saved to: {output_path}")
|