style: fix ruff E721, E701, and E722 lint errors

This commit is contained in:
Andy
2026-02-16 13:37:23 -07:00
parent df92f9e4b6
commit 0217086abf
2 changed files with 135 additions and 104 deletions

View File

@@ -195,12 +195,7 @@ class dl:
sdh_suffix = ".sdh" if (subtitle.sdh or subtitle.cc) else "" sdh_suffix = ".sdh" if (subtitle.sdh or subtitle.cc) else ""
extension = (target_codec or subtitle.codec or Subtitle.Codec.SubRip).extension extension = (target_codec or subtitle.codec or Subtitle.Codec.SubRip).extension
if ( if not target_codec and not subtitle.codec and source_path and source_path.suffix:
not target_codec
and not subtitle.codec
and source_path
and source_path.suffix
):
extension = source_path.suffix.lstrip(".") extension = source_path.suffix.lstrip(".")
filename = f"{base_filename}.{lang_suffix}{forced_suffix}{sdh_suffix}.{extension}" filename = f"{base_filename}.{lang_suffix}{forced_suffix}{sdh_suffix}.{extension}"
@@ -1056,13 +1051,13 @@ class dl:
return return
# Enables manual selection for Series when --select-titles is set # Enables manual selection for Series when --select-titles is set
if select_titles and type(titles) == Series: if select_titles and isinstance(titles, Series):
console.print(Padding(Rule(f"[rule.text]Select Titles"), (1, 2))) console.print(Padding(Rule("[rule.text]Select Titles"), (1, 2)))
selection_titles = [] selection_titles = []
dependencies = {} dependencies = {}
original_indices = {} original_indices = {}
current_season = None current_season = None
current_season_header_idx = -1 current_season_header_idx = -1
@@ -1082,36 +1077,30 @@ class dl:
# Format display name # Format display name
display_name = ((t.name[:35].rstrip() + "") if len(t.name) > 35 else t.name) if t.name else None display_name = ((t.name[:35].rstrip() + "") if len(t.name) > 35 else t.name) if t.name else None
# Apply indentation only for multiple seasons # Apply indentation only for multiple seasons
prefix = " " if multiple_seasons else "" prefix = " " if multiple_seasons else ""
option_text = ( option_text = f"{prefix}{t.number}" + (f". {display_name}" if t.name else "")
f"{prefix}{t.number}" + (f". {display_name}" if t.name else "")
)
selection_titles.append(option_text) selection_titles.append(option_text)
current_ui_idx = len(selection_titles) - 1 current_ui_idx = len(selection_titles) - 1
# Map UI index to actual title index # Map UI index to actual title index
original_indices[current_ui_idx] = i original_indices[current_ui_idx] = i
# Link episode to season header for group selection # Link episode to season header for group selection
if current_season_header_idx != -1: if current_season_header_idx != -1:
dependencies[current_season_header_idx].append(current_ui_idx) dependencies[current_season_header_idx].append(current_ui_idx)
selection_start = time.time() selection_start = time.time()
# Execute selector with dependencies (headers select all children) # Execute selector with dependencies (headers select all children)
selected_ui_idx = select_multiple( selected_ui_idx = select_multiple(
selection_titles, selection_titles, minimal_count=1, page_size=8, return_indices=True, dependencies=dependencies
minimal_count=1,
page_size=8,
return_indices=True,
dependencies=dependencies
) )
selection_end = time.time() selection_end = time.time()
start_time += (selection_end - selection_start) start_time += selection_end - selection_start
# Map UI indices back to title indices (excluding headers) # Map UI indices back to title indices (excluding headers)
selected_idx = [] selected_idx = []
@@ -1604,7 +1593,10 @@ class dl:
if audio_description: if audio_description:
standard_audio = [a for a in title.tracks.audio if not a.descriptive] standard_audio = [a for a in title.tracks.audio if not a.descriptive]
selected_standards = title.tracks.by_language( selected_standards = title.tracks.by_language(
standard_audio, processed_lang, per_language=per_language, exact_match=exact_lang standard_audio,
processed_lang,
per_language=per_language,
exact_match=exact_lang,
) )
desc_audio = [a for a in title.tracks.audio if a.descriptive] desc_audio = [a for a in title.tracks.audio if a.descriptive]
# Include all descriptive tracks for the requested languages. # Include all descriptive tracks for the requested languages.
@@ -1728,9 +1720,7 @@ class dl:
), ),
licence=partial( licence=partial(
service.get_playready_license service.get_playready_license
if ( if (is_playready_cdm(self.cdm))
is_playready_cdm(self.cdm)
)
and hasattr(service, "get_playready_license") and hasattr(service, "get_playready_license")
else service.get_widevine_license, else service.get_widevine_license,
title=title, title=title,
@@ -1848,9 +1838,7 @@ class dl:
# Subtitle output mode configuration (for sidecar originals) # Subtitle output mode configuration (for sidecar originals)
subtitle_output_mode = config.subtitle.get("output_mode", "mux") subtitle_output_mode = config.subtitle.get("output_mode", "mux")
sidecar_format = config.subtitle.get("sidecar_format", "srt") sidecar_format = config.subtitle.get("sidecar_format", "srt")
skip_subtitle_mux = ( skip_subtitle_mux = subtitle_output_mode == "sidecar" and (title.tracks.videos or title.tracks.audio)
subtitle_output_mode == "sidecar" and (title.tracks.videos or title.tracks.audio)
)
sidecar_subtitles: list[Subtitle] = [] sidecar_subtitles: list[Subtitle] = []
sidecar_original_paths: dict[str, Path] = {} sidecar_original_paths: dict[str, Path] = {}
if subtitle_output_mode in ("sidecar", "both") and not no_mux: if subtitle_output_mode in ("sidecar", "both") and not no_mux:
@@ -2101,7 +2089,9 @@ class dl:
sidecar_dir = config.directories.downloads sidecar_dir = config.directories.downloads
if not no_folder and isinstance(title, (Episode, Song)) and media_info: if not no_folder and isinstance(title, (Episode, Song)) and media_info:
sidecar_dir /= title.get_filename(media_info, show_service=not no_source, folder=True) sidecar_dir /= title.get_filename(
media_info, show_service=not no_source, folder=True
)
sidecar_dir.mkdir(parents=True, exist_ok=True) sidecar_dir.mkdir(parents=True, exist_ok=True)
with console.status("Saving subtitle sidecar files..."): with console.status("Saving subtitle sidecar files..."):

View File

@@ -1,21 +1,25 @@
import click
import sys import sys
import click
from rich.console import Group from rich.console import Group
from rich.live import Live from rich.live import Live
from rich.padding import Padding from rich.padding import Padding
from rich.table import Table from rich.table import Table
from rich.text import Text from rich.text import Text
from unshackle.core.console import console from unshackle.core.console import console
IS_WINDOWS = sys.platform == "win32" IS_WINDOWS = sys.platform == "win32"
if IS_WINDOWS: if IS_WINDOWS:
import msvcrt import msvcrt
class Selector: class Selector:
""" """
A custom interactive selector class using the Rich library. A custom interactive selector class using the Rich library.
Allows for multi-selection of items with pagination. Allows for multi-selection of items with pagination.
""" """
def __init__( def __init__(
self, self,
options: list[str], options: list[str],
@@ -24,7 +28,7 @@ class Selector:
page_size: int = 8, page_size: int = 8,
minimal_count: int = 0, minimal_count: int = 0,
dependencies: dict[int, list[int]] = None, dependencies: dict[int, list[int]] = None,
prefixes: list[str] = None prefixes: list[str] = None,
): ):
""" """
Initialize the Selector. Initialize the Selector.
@@ -43,7 +47,7 @@ class Selector:
self.page_size = page_size self.page_size = page_size
self.minimal_count = minimal_count self.minimal_count = minimal_count
self.dependencies = dependencies or {} self.dependencies = dependencies or {}
self.cursor_index = 0 self.cursor_index = 0
self.selected_indices = set() self.selected_indices = set()
self.scroll_offset = 0 self.scroll_offset = 0
@@ -58,11 +62,11 @@ class Selector:
for i in range(self.page_size): for i in range(self.page_size):
idx = self.scroll_offset + i idx = self.scroll_offset + i
if idx < len(self.options): if idx < len(self.options):
option = self.options[idx] option = self.options[idx]
is_cursor = (idx == self.cursor_index) is_cursor = idx == self.cursor_index
is_selected = (idx in self.selected_indices) is_selected = idx in self.selected_indices
symbol = "[X]" if is_selected else "[ ]" symbol = "[X]" if is_selected else "[ ]"
style = self.cursor_style if is_cursor else self.text_style style = self.cursor_style if is_cursor else self.text_style
@@ -77,12 +81,12 @@ class Selector:
total_pages = (len(self.options) + self.page_size - 1) // self.page_size total_pages = (len(self.options) + self.page_size - 1) // self.page_size
current_page = (self.scroll_offset // self.page_size) + 1 current_page = (self.scroll_offset // self.page_size) + 1
info_text = Text( info_text = Text(
f"\n[Space]: Toggle [a]: All [←/→]: Page [Enter]: Confirm (Page {current_page}/{total_pages})", f"\n[Space]: Toggle [a]: All [←/→]: Page [Enter]: Confirm (Page {current_page}/{total_pages})",
style="gray" style="gray",
) )
return Padding(Group(table, info_text), (0, 5)) return Padding(Group(table, info_text), (0, 5))
def move_cursor(self, delta: int): def move_cursor(self, delta: int):
@@ -117,7 +121,7 @@ class Selector:
Propagates selection to children if defined in dependencies. Propagates selection to children if defined in dependencies.
""" """
target_indices = {self.cursor_index} target_indices = {self.cursor_index}
if self.cursor_index in self.dependencies: if self.cursor_index in self.dependencies:
target_indices.update(self.dependencies[self.cursor_index]) target_indices.update(self.dependencies[self.cursor_index])
@@ -130,7 +134,7 @@ class Selector:
def toggle_all(self): def toggle_all(self):
""" """
Toggles the selection of all items. Toggles the selection of all items.
If all are selected, clears selection. Otherwise, selects all. If all are selected, clears selection. Otherwise, selects all.
""" """
if len(self.selected_indices) == len(self.options): if len(self.selected_indices) == len(self.options):
@@ -144,28 +148,43 @@ class Selector:
Returns command strings like 'UP', 'DOWN', 'ENTER', etc. Returns command strings like 'UP', 'DOWN', 'ENTER', etc.
""" """
key = msvcrt.getch() key = msvcrt.getch()
if key == b'\x03' or key == b'\x1b': if key == b"\x03" or key == b"\x1b":
return 'CANCEL' return "CANCEL"
if key == b'\xe0' or key == b'\x00': if key == b"\xe0" or key == b"\x00":
try: try:
key = msvcrt.getch() key = msvcrt.getch()
if key == b'H': return 'UP' if key == b"H":
if key == b'P': return 'DOWN' return "UP"
if key == b'K': return 'LEFT' if key == b"P":
if key == b'M': return 'RIGHT' return "DOWN"
except: pass if key == b"K":
return "LEFT"
try: char = key.decode('utf-8', errors='ignore') if key == b"M":
except: return None return "RIGHT"
except Exception:
if char in ('\r', '\n'): return 'ENTER' pass
if char == ' ': return 'SPACE'
if char in ('q', 'Q'): return 'QUIT' try:
if char in ('a', 'A'): return 'ALL' char = key.decode("utf-8", errors="ignore")
if char in ('w', 'W', 'k', 'K'): return 'UP' except Exception:
if char in ('s', 'S', 'j', 'J'): return 'DOWN' return None
if char in ('h', 'H'): return 'LEFT'
if char in ('d', 'D', 'l', 'L'): return 'RIGHT' if char in ("\r", "\n"):
return "ENTER"
if char == " ":
return "SPACE"
if char in ("q", "Q"):
return "QUIT"
if char in ("a", "A"):
return "ALL"
if char in ("w", "W", "k", "K"):
return "UP"
if char in ("s", "S", "j", "J"):
return "DOWN"
if char in ("h", "H"):
return "LEFT"
if char in ("d", "D", "l", "L"):
return "RIGHT"
return None return None
def get_input_unix(self): def get_input_unix(self):
@@ -174,44 +193,56 @@ class Selector:
Returns command strings like 'UP', 'DOWN', 'ENTER', etc. Returns command strings like 'UP', 'DOWN', 'ENTER', etc.
""" """
char = click.getchar() char = click.getchar()
if char == '\x03': if char == "\x03":
return 'CANCEL' return "CANCEL"
mapping = { mapping = {
'\x1b[A': 'UP', "\x1b[A": "UP",
'\x1b[B': 'DOWN', "\x1b[B": "DOWN",
'\x1b[C': 'RIGHT', "\x1b[C": "RIGHT",
'\x1b[D': 'LEFT', "\x1b[D": "LEFT",
} }
if char in mapping: if char in mapping:
return mapping[char] return mapping[char]
if char == '\x1b': if char == "\x1b":
try: try:
next1 = click.getchar() next1 = click.getchar()
if next1 in ('[', 'O'): if next1 in ("[", "O"):
next2 = click.getchar() next2 = click.getchar()
if next2 == 'A': return 'UP' if next2 == "A":
if next2 == 'B': return 'DOWN' return "UP"
if next2 == 'C': return 'RIGHT' if next2 == "B":
if next2 == 'D': return 'LEFT' return "DOWN"
return 'CANCEL' if next2 == "C":
except: return "RIGHT"
return 'CANCEL' if next2 == "D":
return "LEFT"
return "CANCEL"
except Exception:
return "CANCEL"
if char in ('\r', '\n'): return 'ENTER' if char in ("\r", "\n"):
if char == ' ': return 'SPACE' return "ENTER"
if char in ('q', 'Q'): return 'QUIT' if char == " ":
if char in ('a', 'A'): return 'ALL' return "SPACE"
if char in ('w', 'W', 'k', 'K'): return 'UP' if char in ("q", "Q"):
if char in ('s', 'S', 'j', 'J'): return 'DOWN' return "QUIT"
if char in ('h', 'H'): return 'LEFT' if char in ("a", "A"):
if char in ('d', 'D', 'l', 'L'): return 'RIGHT' return "ALL"
if char in ("w", "W", "k", "K"):
return "UP"
if char in ("s", "S", "j", "J"):
return "DOWN"
if char in ("h", "H"):
return "LEFT"
if char in ("d", "D", "l", "L"):
return "RIGHT"
return None return None
def run(self) -> list[int]: def run(self) -> list[int]:
""" """
Starts the main event loop for the selector. Starts the main event loop for the selector.
Renders the UI and processes input until confirmed or cancelled. Renders the UI and processes input until confirmed or cancelled.
Returns: Returns:
list[int]: A sorted list of selected indices. list[int]: A sorted list of selected indices.
""" """
@@ -219,33 +250,43 @@ class Selector:
with Live(self.get_renderable(), console=console, auto_refresh=False, transient=True) as live: with Live(self.get_renderable(), console=console, auto_refresh=False, transient=True) as live:
while True: while True:
live.update(self.get_renderable(), refresh=True) live.update(self.get_renderable(), refresh=True)
if IS_WINDOWS: action = self.get_input_windows() if IS_WINDOWS:
else: action = self.get_input_unix() action = self.get_input_windows()
else:
if action == 'UP': self.move_cursor(-1) action = self.get_input_unix()
elif action == 'DOWN': self.move_cursor(1)
elif action == 'LEFT': self.change_page(-1) if action == "UP":
elif action == 'RIGHT': self.change_page(1) self.move_cursor(-1)
elif action == 'SPACE': self.toggle_selection() elif action == "DOWN":
elif action == 'ALL': self.toggle_all() self.move_cursor(1)
elif action in ('ENTER', 'QUIT'): elif action == "LEFT":
self.change_page(-1)
elif action == "RIGHT":
self.change_page(1)
elif action == "SPACE":
self.toggle_selection()
elif action == "ALL":
self.toggle_all()
elif action in ("ENTER", "QUIT"):
if len(self.selected_indices) >= self.minimal_count: if len(self.selected_indices) >= self.minimal_count:
return sorted(list(self.selected_indices)) return sorted(list(self.selected_indices))
elif action == 'CANCEL': raise KeyboardInterrupt elif action == "CANCEL":
raise KeyboardInterrupt
except KeyboardInterrupt: except KeyboardInterrupt:
return [] return []
def select_multiple( def select_multiple(
options: list[str], options: list[str],
minimal_count: int = 1, minimal_count: int = 1,
page_size: int = 8, page_size: int = 8,
return_indices: bool = True, return_indices: bool = True,
cursor_style: str = "pink", cursor_style: str = "pink",
**kwargs **kwargs,
) -> list[int]: ) -> list[int]:
""" """
Drop-in replacement using custom Selector with global console. Drop-in replacement using custom Selector with global console.
Args: Args:
options: List of options to display. options: List of options to display.
minimal_count: Minimum number of selections required. minimal_count: Minimum number of selections required.
@@ -259,11 +300,11 @@ def select_multiple(
text_style="text", text_style="text",
page_size=page_size, page_size=page_size,
minimal_count=minimal_count, minimal_count=minimal_count,
**kwargs **kwargs,
) )
selected_indices = selector.run() selected_indices = selector.run()
if return_indices: if return_indices:
return selected_indices return selected_indices
return [options[i] for i in selected_indices] return [options[i] for i in selected_indices]