FIX: codec/container/file_extension autodetection when default values are used

This commit is contained in:
2026-08-28 11:04:40 +05:00
parent f3abbb8f0c
commit 748532be7c
2 changed files with 132 additions and 56 deletions
+23 -15
View File
@@ -6,7 +6,7 @@ import subprocess
import sys import sys
from typing import List, Dict, Any from typing import List, Dict, Any
from .constants import FORMAT_INFO from .constants import FORMAT_INFO, CODEC_TO_EXTENSION_MAP
from .ffmpeg import ( from .ffmpeg import (
get_audio_duration, get_audio_duration,
get_stream_info, get_stream_info,
@@ -34,10 +34,11 @@ def split_audio(input_file: str, output_directory: str, tracks: List[Dict[str, A
output_directory: Directory where output files will be saved. output_directory: Directory where output files will be saved.
tracks: List of dicts, each containing parsed fields (ts, tn, an, ...). tracks: List of dicts, each containing parsed fields (ts, tn, an, ...).
args: Parsed commandline arguments (namespace) with attributes: args: Parsed commandline arguments (namespace) with attributes:
- container: output container name - container: output container name (or None for auto-detect)
- audio_codec: audio codec (copy or encoder) - audio_codec: audio codec (copy or encoder)
- video_codec: video codec (copy or encoder) - video_codec: video codec (copy or encoder)
- subtitle_codec: subtitle codec (copy or encoder) - subtitle_codec: subtitle codec (copy or encoder)
- video_quality: integer or None
- drop_video: bool - drop_video: bool
- drop_subs: bool - drop_subs: bool
- number_tracks: bool - number_tracks: bool
@@ -77,7 +78,6 @@ def split_audio(input_file: str, output_directory: str, tracks: List[Dict[str, A
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# 2. Determine output container # 2. Determine output container
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# Use args.container if provided, otherwise auto-detect
user_container = getattr(args, 'container', None) user_container = getattr(args, 'container', None)
output_container = determine_output_format( output_container = determine_output_format(
stream_info, stream_info,
@@ -104,13 +104,30 @@ def split_audio(input_file: str, output_directory: str, tracks: List[Dict[str, A
raise RuntimeError(f"Compatibility error: {e}") raise RuntimeError(f"Compatibility error: {e}")
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# 4. Parse timestamps # 4. Determine output audio codec and extension
# --------------------------------------------------------------------------
# Determine the audio codec that will be used in the output
if args.audio_codec != 'copy':
output_audio_codec = args.audio_codec
else:
output_audio_codec = stream_info.get('audio_codec', '')
# Choose extension based on audio codec if possible, otherwise fallback to container default
if output_audio_codec in CODEC_TO_EXTENSION_MAP:
extension = CODEC_TO_EXTENSION_MAP[output_audio_codec]
else:
extension = FORMAT_INFO.get(output_container, {}).get('ext', '.mkv')
print(f"Output extension: {extension}")
# --------------------------------------------------------------------------
# 5. Parse timestamps
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
track_times = parse_track_timestamps(tracks) track_times = parse_track_timestamps(tracks)
resolved_times = resolve_end_times(track_times, total_duration) resolved_times = resolve_end_times(track_times, total_duration)
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# 5. Fetch original metadata (for fallbacks) # 6. Fetch original metadata (for fallbacks)
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
input_metadata = get_metadata(input_file) input_metadata = get_metadata(input_file)
original_album = input_metadata.get('album') original_album = input_metadata.get('album')
@@ -127,13 +144,12 @@ def split_audio(input_file: str, output_directory: str, tracks: List[Dict[str, A
print(f" Stream {idx}: '{comment}'") print(f" Stream {idx}: '{comment}'")
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# 6. Handle attached picture (cover art) # 7. Handle attached picture (cover art)
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
cover_image_path = getattr(args, 'cover_image', None) cover_image_path = getattr(args, 'cover_image', None)
if cover_image_path and not os.path.exists(cover_image_path): if cover_image_path and not os.path.exists(cover_image_path):
cover_image_path = None cover_image_path = None
# If not provided via args (CLI case), try to detect and extract
if not cover_image_path and not args.drop_video and stream_info.get('has_video'): if not cover_image_path and not args.drop_video and stream_info.get('has_video'):
if is_attached_picture(input_file): if is_attached_picture(input_file):
cover_image_path = os.path.join(output_directory, 'cover.png') cover_image_path = os.path.join(output_directory, 'cover.png')
@@ -142,12 +158,6 @@ def split_audio(input_file: str, output_directory: str, tracks: List[Dict[str, A
else: else:
cover_image_path = None cover_image_path = None
# --------------------------------------------------------------------------
# 7. Determine file extension
# --------------------------------------------------------------------------
extension_info = FORMAT_INFO.get(output_container, {})
extension = extension_info.get('ext', '.mkv')
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# 8. Process each track # 8. Process each track
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
@@ -199,7 +209,6 @@ def split_audio(input_file: str, output_directory: str, tracks: List[Dict[str, A
drop_subs=args.drop_subs, drop_subs=args.drop_subs,
) )
print(cmd)
print(f"Extracting track {idx}: {track_name} " print(f"Extracting track {idx}: {track_name} "
f"({format_time(start_seconds)} - {format_time(end_seconds)})") f"({format_time(start_seconds)} - {format_time(end_seconds)})")
@@ -208,7 +217,6 @@ def split_audio(input_file: str, output_directory: str, tracks: List[Dict[str, A
if result.returncode != 0: if result.returncode != 0:
print(f"ERROR extracting track {idx}:") print(f"ERROR extracting track {idx}:")
print(result.stderr) print(result.stderr)
# Optionally stop on first error? We'll continue.
else: else:
print(f" -> Saved to: {output_path}") print(f" -> Saved to: {output_path}")
+109 -41
View File
@@ -31,7 +31,6 @@ from .defaults import (
DEFAULT_REPLACE_BAD_CHARS, DEFAULT_REPLACE_BAD_CHARS,
DEFAULT_SKIP_EXISTING, DEFAULT_SKIP_EXISTING,
DEFAULT_DELETE_ORIGINAL, DEFAULT_DELETE_ORIGINAL,
DEFAULT_TRANSCODE_TO,
) )
from .core import split_audio from .core import split_audio
from .tracklist import read_tracklist, parse_format from .tracklist import read_tracklist, parse_format
@@ -48,18 +47,26 @@ def main():
parser.add_argument('tracklist_file', help='Tracklist file') parser.add_argument('tracklist_file', help='Tracklist file')
# Container and codec options # Container and codec options
parser.add_argument('--container', default=DEFAULT_FORMAT, parser.add_argument(
help="Output container format (default: %(default)s)") '--container',
parser.add_argument('--audio-codec', default='copy', default=None,
help="Audio codec (copy or encoder name, e.g., libopus)") help="Output container format (auto-detect if not specified)"
parser.add_argument('--video-codec', default='copy', )
help="Video codec (copy or encoder name, e.g., libx264)") parser.add_argument(
parser.add_argument('--subtitle-codec', default='copy', '--audio-codec',
help="Subtitle codec (copy or encoder name, e.g., srt)") default='copy',
parser.add_argument('--drop-video', action='store_true', default=DEFAULT_DROP_VIDEO, help="Audio codec (copy or encoder name, e.g., libopus)"
help="Drop video streams") )
parser.add_argument('--drop-subs', action='store_true', default=DEFAULT_DROP_SUBS, parser.add_argument(
help="Drop subtitle streams") '--video-codec',
default='copy',
help="Video codec (copy or encoder name, e.g., libx264)"
)
parser.add_argument(
'--subtitle-codec',
default='copy',
help="Subtitle codec (copy or encoder name, e.g., srt)"
)
parser.add_argument( parser.add_argument(
'--video-quality', '-vq', '--video-quality', '-vq',
type=int, type=int,
@@ -67,44 +74,105 @@ def main():
help="Video quality (integer, encoder-specific; usually 1-31, lower=better). " help="Video quality (integer, encoder-specific; usually 1-31, lower=better). "
"If omitted, FFmpeg default is used." "If omitted, FFmpeg default is used."
) )
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 # Filename options
parser.add_argument('--number-tracks', action='store_true', default=DEFAULT_NUMBER_TRACKS, parser.add_argument(
help="Prepend track numbers") '--number-tracks',
parser.add_argument('--output-template', default=DEFAULT_OUTPUT_TEMPLATE, action='store_true',
help="Output filename template (default: %(default)s)") default=DEFAULT_NUMBER_TRACKS,
parser.add_argument('--replace-bad-chars', action='store_true', default=DEFAULT_REPLACE_BAD_CHARS, help="Prepend track numbers"
help="Replace bad characters") )
parser.add_argument('--replacement-char', default=DEFAULT_REPLACEMENT_CHAR, parser.add_argument(
help="Replacement character (default: %(default)s)") '--output-template',
parser.add_argument('--bad-chars', default=DEFAULT_BAD_CHARS, default=DEFAULT_OUTPUT_TEMPLATE,
help="Bad characters to replace (default: %(default)s)") help="Output filename template (default: %(default)s)"
parser.add_argument('--skip-existing', action='store_true', default=DEFAULT_SKIP_EXISTING, )
help="Skip existing output files") 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="Replacement character (default: %(default)s)"
)
parser.add_argument(
'--bad-chars',
default=DEFAULT_BAD_CHARS,
help="Bad characters to replace (default: %(default)s)"
)
parser.add_argument(
'--skip-existing',
action='store_true',
default=DEFAULT_SKIP_EXISTING,
help="Skip existing output files"
)
# Metadata # Metadata
parser.add_argument('--album', default=DEFAULT_ALBUM, help="Album name") parser.add_argument('--album', default=DEFAULT_ALBUM, help="Album name")
parser.add_argument('--comment', default=DEFAULT_COMMENT, help="Comment") parser.add_argument('--comment', default=DEFAULT_COMMENT, help="Comment")
parser.add_argument('--no-comment', action='store_true', default=DEFAULT_NO_COMMENT, parser.add_argument(
help="Ignore comment") '--no-comment',
parser.add_argument('--comment-stream', type=int, default=DEFAULT_COMMENT_STREAM, action='store_true',
help="Comment stream index") default=DEFAULT_NO_COMMENT,
parser.add_argument('--merge-comments', action='store_true', default=DEFAULT_MERGE_COMMENTS, help="Ignore comment"
help="Merge all comments") )
parser.add_argument('--comment-separator', default=DEFAULT_COMMENT_SEPARATOR, parser.add_argument(
help="Separator for merged comments (default: %(default)s)") '--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="Separator for merged comments (default: %(default)s)"
)
# Tracklist format # Tracklist format
parser.add_argument('--tracklist-format', default=DEFAULT_TRACKLIST_FORMAT, parser.add_argument(
help="Tracklist format (default: %(default)s)") '--tracklist-format',
default=DEFAULT_TRACKLIST_FORMAT,
help="Tracklist format (default: %(default)s)"
)
# Other # Other
parser.add_argument('--delete-original', action='store_true', default=DEFAULT_DELETE_ORIGINAL, parser.add_argument(
help="Delete original file after split") '--delete-original',
parser.add_argument('--dry-run', action='store_true', action='store_true',
help="Parse and display tracklist without splitting") default=DEFAULT_DELETE_ORIGINAL,
parser.add_argument('--output-dir', '-o', default=None, help="Delete original file after split"
help="Output directory (default: <input_basename>_splits)") )
parser.add_argument(
'--dry-run',
action='store_true',
help="Parse and display tracklist without splitting"
)
parser.add_argument(
'--output-dir', '-o',
default=None,
help="Output directory (default: <input_basename>_splits)"
)
args = parser.parse_args() args = parser.parse_args()