Skip to content

Commit cccfa45

Browse files
FEAT: Add early skip for files with correct default audio and subtitle streams in main.py
1 parent 0cb8c31 commit cccfa45

1 file changed

Lines changed: 126 additions & 0 deletions

File tree

  • Default Audio Track Switcher

Default Audio Track Switcher/main.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,129 @@ def find_original_default_position(streams, pos_key):
780780
return None # No original default found
781781

782782

783+
def find_current_default_stream(streams):
784+
"""
785+
Find the current default stream from disposition metadata.
786+
787+
:param streams: List of stream dicts
788+
:return: Stream dict marked as default or None
789+
"""
790+
791+
for stream in streams: # Iterate stream list to locate the stream marked as default.
792+
try: # Guard disposition coercion for malformed metadata values.
793+
if int(stream.get("disposition", {}).get("default", 0)) == 1: # Verify ffprobe default flag is enabled.
794+
return stream # Return stream immediately when default disposition is present.
795+
except Exception: # Ignore malformed disposition values and continue scanning.
796+
continue # Continue iteration when disposition parsing fails.
797+
798+
return None # Return None when no default stream is marked.
799+
800+
801+
def resolve_priority_canonical_language(stream_type):
802+
"""
803+
Resolve the highest-priority configured language to a canonical desired key.
804+
805+
:param stream_type: Stream type key ('audio' or 'subtitle')
806+
:return: Canonical language key from DESIRED_LANGUAGES or None
807+
"""
808+
809+
priority_names = resolve_priority_list(stream_type) # Resolve ordered priority names for the requested stream type.
810+
if len(priority_names) == 0: # Verify at least one configured priority exists.
811+
return None # Return None when no priority language is configured.
812+
813+
top_priority_name = priority_names[0] # Read the highest-priority display name from configuration.
814+
if top_priority_name in DESIRED_LANGUAGES: # Verify top priority already matches a canonical desired language key.
815+
return top_priority_name # Return canonical key directly when an exact key match exists.
816+
817+
normalized_priority = normalize_text(top_priority_name) # Normalize top priority text for robust alias comparison.
818+
normalized_aliases = get_normalized_desired_language_aliases() # Build normalized alias map for desired languages.
819+
820+
for language_name, aliases in normalized_aliases.items(): # Iterate canonical language aliases for fuzzy canonical resolution.
821+
for alias in aliases: # Iterate each normalized alias for the current canonical language.
822+
if alias == "": # Verify alias has meaningful content before comparison.
823+
continue # Continue to next alias when normalized alias is empty.
824+
if alias == normalized_priority or normalized_priority in alias or alias in normalized_priority: # Verify alias relation between priority value and canonical aliases.
825+
return language_name # Return canonical language key when alias relation matches.
826+
827+
return None # Return None when no canonical desired language can be resolved.
828+
829+
830+
def detect_stream_language_for_validation(stream, stream_type):
831+
"""
832+
Detect canonical stream language using tags, title, codec, and fuzzy aliases.
833+
834+
:param stream: Stream metadata dict
835+
:param stream_type: Stream type key ('audio' or 'subtitle')
836+
:return: Canonical language key from DESIRED_LANGUAGES or None
837+
"""
838+
839+
tags = stream.get("tags", {}) or {} # Resolve stream tags mapping with empty fallback.
840+
raw_lang = stream.get("raw_language") or tags.get("language") or tags.get("LANGUAGE") or tags.get("lang") or "" # Resolve raw language metadata with fallbacks.
841+
raw_title = stream.get("raw_title") or stream.get("title") or tags.get("title") or "" # Resolve raw title metadata with fallbacks.
842+
detected_language = detect_language(raw_lang, raw_title, stream_type) # Reuse existing normalized language detector first.
843+
if detected_language is not None: # Verify canonical language was resolved from standard metadata fields.
844+
return detected_language # Return detected canonical language immediately when available.
845+
846+
normalized_aliases = get_normalized_desired_language_aliases() # Build normalized desired language aliases for fallback matching.
847+
metadata_values = [] # Initialize metadata value list for fuzzy fallback matching.
848+
metadata_values.append(normalize_text(stream.get("codec", ""))) # Append normalized codec name to fallback candidates.
849+
metadata_values.append(normalize_text(raw_lang)) # Append normalized raw language value to fallback candidates.
850+
metadata_values.append(normalize_text(raw_title)) # Append normalized raw title value to fallback candidates.
851+
for value in tags.values(): # Iterate all tag values for additional fuzzy matching input.
852+
metadata_values.append(normalize_text(value)) # Append normalized tag value to fallback candidates.
853+
854+
priority_names = resolve_priority_list(stream_type) # Resolve configured priority language names for deterministic ordering.
855+
ordered_languages = [] # Initialize ordered canonical language scan list.
856+
for language_name in priority_names: # Iterate configured priority names first.
857+
if language_name in DESIRED_LANGUAGES and language_name not in ordered_languages: # Verify language is canonical and not duplicated.
858+
ordered_languages.append(language_name) # Append canonical priority language to ordered scan list.
859+
for language_name in DESIRED_LANGUAGES.keys(): # Iterate remaining canonical desired language keys.
860+
if language_name not in ordered_languages: # Verify language key has not already been added.
861+
ordered_languages.append(language_name) # Append remaining canonical language after priority languages.
862+
863+
for language_name in ordered_languages: # Iterate canonical language keys in deterministic order.
864+
for alias in normalized_aliases.get(language_name, []): # Iterate normalized aliases for current canonical language.
865+
if alias == "": # Verify alias is not empty before metadata comparison.
866+
continue # Continue to next alias when normalized alias is empty.
867+
for metadata_value in metadata_values: # Iterate normalized metadata values for substring matching.
868+
if metadata_value == "": # Verify metadata value has content before matching.
869+
continue # Continue to next metadata value when empty.
870+
if alias in metadata_value: # Verify alias appears in current normalized metadata value.
871+
return language_name # Return canonical language key when a fuzzy match is found.
872+
873+
return None # Return None when language detection cannot resolve a canonical desired language.
874+
875+
876+
def should_skip_processing_for_correct_defaults(video_path, audio_streams, subtitle_streams):
877+
"""
878+
Determine whether processing can be skipped when default streams already match priority.
879+
880+
:param video_path: Path to the video file
881+
:param audio_streams: List of parsed audio stream dicts
882+
:param subtitle_streams: List of parsed subtitle stream dicts
883+
:return: True when both default audio and subtitle streams already match highest priority
884+
"""
885+
886+
default_audio_stream = find_current_default_stream(audio_streams) # Resolve currently flagged default audio stream.
887+
default_subtitle_stream = find_current_default_stream(subtitle_streams) # Resolve currently flagged default subtitle stream.
888+
desired_audio_language = resolve_priority_canonical_language("audio") # Resolve canonical top-priority audio language from configuration.
889+
desired_subtitle_language = resolve_priority_canonical_language("subtitle") # Resolve canonical top-priority subtitle language from configuration.
890+
891+
if default_audio_stream is None or default_subtitle_stream is None: # Verify both default streams exist before skip decision.
892+
return False # Return False when one of the default streams is missing.
893+
if desired_audio_language is None or desired_subtitle_language is None: # Verify top-priority canonical languages were resolved from configuration.
894+
return False # Return False when priority canonical languages cannot be resolved.
895+
896+
current_audio_language = detect_stream_language_for_validation(default_audio_stream, "audio") # Detect canonical language for current default audio stream.
897+
current_subtitle_language = detect_stream_language_for_validation(default_subtitle_stream, "subtitle") # Detect canonical language for current default subtitle stream.
898+
899+
if current_audio_language == desired_audio_language and current_subtitle_language == desired_subtitle_language: # Verify both defaults already match highest-priority desired languages.
900+
verbose_output(f"[DEBUG] Skipping file as default streams already match desired priority: {os.path.basename(video_path)}") # Output debug skip log in the existing format.
901+
return True # Return True to skip remuxing for this file.
902+
903+
return False # Return False when at least one default stream does not match configured priority.
904+
905+
783906
def apply_prune_and_set_defaults(video_path, audio_streams, subtitle_streams):
784907
"""
785908
Build and run ffmpeg command to keep only DESIRED tracks and set defaults accordingly.
@@ -1082,6 +1205,9 @@ def swap_audio_tracks(video_path):
10821205
)
10831206
return # Skip this file
10841207

1208+
if should_skip_processing_for_correct_defaults(video_path, audio_streams, subtitle_streams): # Verify whether both current defaults already match highest-priority desired languages.
1209+
return # Skip remuxing when default streams already match configured priority.
1210+
10851211
apply_prune_and_set_defaults(video_path, audio_streams, subtitle_streams) # Prune non-desired and set defaults
10861212
return # Return after pruning operation
10871213

0 commit comments

Comments
 (0)