Skip to content

Commit 4cf6e77

Browse files
FEAT: Add forced stream filtering in ./main.py
1 parent 2a24c1e commit 4cf6e77

1 file changed

Lines changed: 61 additions & 7 deletions

File tree

  • Default Audio Track Switcher

Default Audio Track Switcher/main.py

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class BackgroundColors: # Colors for the terminal
9090
REMOVE_OTHER_AUDIO_TRACKS = True # Set to True to remove other audio tracks after setting the default
9191
REMOVE_OTHER_SUBTITLE_TRACKS = True # Set to True to remove other subtitle tracks
9292
REMOVE_DESCRIPTIVE_STREAMS = True # If True, remove descriptive/SDH streams (audio and subtitles) before selection
93+
REMOVE_FORCED_STREAMS = True # If True, remove forced streams before selection
9394

9495
STREAM_TYPE_PRIORITY_ORDER = {
9596
"audio": ["English", "Portuguese"], # Priority for audio tracks (English first)
@@ -684,19 +685,62 @@ def is_descriptive_stream(stream):
684685
return False # No descriptive indicators found
685686

686687

688+
def is_forced_stream(stream):
689+
"""
690+
Detect whether a stream is forced.
691+
692+
:param stream: Stream metadata dict
693+
:return: True if forced keywords or disposition flags are found, False otherwise
694+
"""
695+
696+
keywords = ["forced"] # Forced keyword to match
697+
values = [str(stream.get("title", "")).lower()] # Start with title normalized
698+
values.append(str(stream.get("language", "")).lower()) # Append language normalized
699+
tags = stream.get("tags", {}) or {} # Safely get tags dict or empty
700+
701+
for v in tags.values(): # Iterate over all tag values
702+
try: # Guard conversion to string for each tag value
703+
values.append(str(v).lower()) # Append lowercased tag value
704+
except Exception: # Ignore problematic tag value conversions
705+
continue # Continue with next tag value
706+
707+
disp = stream.get("disposition", {}) or {} # Safely get disposition dict or empty
708+
709+
try: # Guard numeric/coercion checks on disposition flags
710+
for key in ("forced",): # Keys to inspect
711+
if key in disp and disp.get(key) is not None: # If disposition key exists and is not None
712+
val = disp.get(key) # Get raw disposition value
713+
sval = str(val).lower() # Normalize disposition value to string
714+
715+
if sval in ("1", "true", "yes"): # Treat these as affirmative indicators
716+
return True # Disposition indicates forced stream
717+
except Exception: # Ignore any unexpected disposition parsing issues
718+
pass # Continue to keyword scanning if disposition checks fail
719+
720+
for val in values: # Evaluate all collected metadata values for keywords
721+
for kw in keywords: # Iterate forced keywords
722+
if kw in val: # If keyword is present in any metadata value
723+
return True # Mark stream as forced
724+
725+
return False # No forced indicators found
726+
727+
687728
def filter_undesired_streams(streams):
688729
"""
689-
Filter out undesired streams based on classification and descriptive detection.
730+
Filter out undesired streams based on classification and stream detection.
690731
691732
:param streams: List of stream dicts
692733
:return: Filtered list of stream dicts
693734
"""
694-
735+
695736
filtered = streams # Initialize filtered list with original streams
696737

697738
if REMOVE_DESCRIPTIVE_STREAMS: # Only remove descriptive streams when configured
698-
filtered = [s for s in streams if not is_descriptive_stream(s)] # Build new list excluding descriptive streams
739+
filtered = [s for s in filtered if not is_descriptive_stream(s)] # Build new list excluding descriptive streams
699740

741+
if REMOVE_FORCED_STREAMS: # Only remove forced streams when configured
742+
filtered = [s for s in filtered if not is_forced_stream(s)] # Build new list excluding forced streams
743+
700744
return filtered # Return the filtered (or original) list
701745

702746

@@ -708,7 +752,7 @@ def filter_desired_streams(streams):
708752
:return: List of streams with classification == 'desired'
709753
"""
710754

711-
prefiltered = filter_undesired_streams(streams) # Ensure descriptive streams removed before classification filtering
755+
prefiltered = filter_undesired_streams(streams) # Ensure undesired streams removed before classification filtering
712756

713757
return [s for s in prefiltered if s.get("classification") == "desired"] # Return only streams explicitly marked desired
714758

@@ -724,7 +768,7 @@ def select_best_stream(streams, kept_positions, priority_names, pos_key):
724768
:return: Selected physical position integer or None
725769
"""
726770

727-
filtered_streams = filter_undesired_streams(streams) # Remove descriptive streams before priority selection
771+
filtered_streams = filter_undesired_streams(streams) # Remove undesired streams before priority selection
728772
candidate_streams = [s for s in filtered_streams if s.get(pos_key) in kept_positions] # Keep only streams that remain mapped
729773

730774
for preferred in priority_names: # Iterate language priorities in configured order
@@ -851,7 +895,7 @@ def select_best_subtitle_stream(streams, kept_positions, priority_names, pos_key
851895
:return: Selected subtitle physical position integer or None
852896
"""
853897

854-
filtered_streams = filter_undesired_streams(streams) # Remove descriptive streams before subtitle selection
898+
filtered_streams = filter_undesired_streams(streams) # Remove undesired streams before subtitle selection
855899
candidate_streams = [s for s in filtered_streams if s.get(pos_key) in kept_positions] # Keep only mapped subtitle candidates
856900

857901
for preferred in priority_names: # Iterate configured subtitle language priorities in order
@@ -1442,7 +1486,7 @@ def main():
14421486
:return: None
14431487
"""
14441488

1445-
global REMOVE_OTHER_AUDIO_TRACKS, REMOVE_OTHER_SUBTITLE_TRACKS, REMOVE_DESCRIPTIVE_STREAMS # Make descriptive config writable
1489+
global REMOVE_OTHER_AUDIO_TRACKS, REMOVE_OTHER_SUBTITLE_TRACKS, REMOVE_DESCRIPTIVE_STREAMS, REMOVE_FORCED_STREAMS # Make descriptive and forced config writable
14461490

14471491
# Parse command-line arguments
14481492
parser = argparse.ArgumentParser(
@@ -1466,6 +1510,12 @@ def main():
14661510
default=None, # Default to None so absence does not override constants
14671511
help="Remove descriptive/SDH streams before selection"
14681512
)
1513+
parser.add_argument( # Register the remove forced streams flag
1514+
"--remove-forced-streams",
1515+
action="store_true", # Use presence to set True when explicitly passed
1516+
default=None, # Default to None so absence does not override constants
1517+
help="Remove forced streams before selection" # Describe the forced stream filter
1518+
)
14691519
args = parser.parse_args()
14701520

14711521
if args.remove_other_audio is not None: # Only override when flag explicitly present
@@ -1474,6 +1524,8 @@ def main():
14741524
REMOVE_OTHER_SUBTITLE_TRACKS = args.remove_other_subtitles # Override global constant accordingly
14751525
if args.remove_descriptive_streams is not None: # Only override when flag explicitly present
14761526
REMOVE_DESCRIPTIVE_STREAMS = args.remove_descriptive_streams # Override global constant accordingly
1527+
if args.remove_forced_streams is not None: # Only override when flag explicitly present
1528+
REMOVE_FORCED_STREAMS = args.remove_forced_streams # Override global constant accordingly
14771529

14781530
print(
14791531
f"{BackgroundColors.CLEAR_TERMINAL}{BackgroundColors.BOLD}{BackgroundColors.GREEN}Welcome to the {BackgroundColors.CYAN}Default Audio Track Switcher{BackgroundColors.GREEN}!{Style.RESET_ALL}\n"
@@ -1485,6 +1537,8 @@ def main():
14851537
print(f"{BackgroundColors.GREEN}Mode: {BackgroundColors.CYAN}Removing Non-Desired Subtitle Tracks{Style.RESET_ALL}")
14861538
if REMOVE_DESCRIPTIVE_STREAMS: # If descriptive stream filtering is enabled, print the mode
14871539
print(f"{BackgroundColors.GREEN}Mode: {BackgroundColors.CYAN}Filtering Descriptive Streams{Style.RESET_ALL}")
1540+
if REMOVE_FORCED_STREAMS: # If forced stream filtering is enabled, print the mode
1541+
print(f"{BackgroundColors.GREEN}Mode: {BackgroundColors.CYAN}Filtering Forced Streams{Style.RESET_ALL}")
14881542

14891543
print() # Add a newline for better separation before processing starts
14901544

0 commit comments

Comments
 (0)