Add YAPF style + ignore, and format tracked Python files

This commit is contained in:
2025-12-29 18:42:02 -08:00
parent c019c00aed
commit 507946a3e4
108 changed files with 11664 additions and 6494 deletions

View File

@@ -54,15 +54,20 @@ class DownloadModal(ModalScreen):
"""Modal screen for initiating new download requests."""
BINDINGS = [
Binding("escape", "cancel", "Cancel"),
Binding("ctrl+enter", "submit", "Submit"),
Binding("escape",
"cancel",
"Cancel"),
Binding("ctrl+enter",
"submit",
"Submit"),
]
CSS_PATH = "download.tcss"
def __init__(
self,
on_submit: Optional[Callable[[dict], None]] = None,
on_submit: Optional[Callable[[dict],
None]] = None,
available_sources: Optional[list] = None,
config: Optional[dict] = None,
):
@@ -221,7 +226,10 @@ class DownloadModal(ModalScreen):
self.progress_bar = self.query_one("#progress_bar", ProgressBar)
self.playlist_tree = self.query_one("#playlist_tree", Tree)
self.playlist_input = self.query_one("#playlist_input", Input)
self.playlist_merge_checkbox = self.query_one("#playlist_merge_checkbox", Checkbox)
self.playlist_merge_checkbox = self.query_one(
"#playlist_merge_checkbox",
Checkbox
)
# Set default actions
self.download_checkbox.value = True
@@ -251,7 +259,11 @@ class DownloadModal(ModalScreen):
if not url:
logger.warning("Download request missing URL")
self.app.notify("URL is required", title="Missing Input", severity="warning")
self.app.notify(
"URL is required",
title="Missing Input",
severity="warning"
)
return
# Parse tags (one per line)
@@ -363,7 +375,12 @@ class DownloadModal(ModalScreen):
# Handle PDF playlist specially
if is_pdf_playlist and pdf_url:
logger.info(f"Processing PDF playlist with {len(pdf_url)} PDFs")
self._handle_pdf_playlist_download(pdf_url, tags, playlist_selection, merge_enabled)
self._handle_pdf_playlist_download(
pdf_url,
tags,
playlist_selection,
merge_enabled
)
self.app.call_from_thread(self._hide_progress)
self.app.call_from_thread(self.dismiss)
return
@@ -376,7 +393,10 @@ class DownloadModal(ModalScreen):
if not get_cmdlet:
logger.error("cmdlet module not available")
self.app.call_from_thread(
self.app.notify, "cmdlet system unavailable", title="Error", severity="error"
self.app.notify,
"cmdlet system unavailable",
title="Error",
severity="error"
)
self.app.call_from_thread(self._hide_progress)
return
@@ -402,7 +422,9 @@ class DownloadModal(ModalScreen):
# Always use yt-dlp's native --playlist-items for playlists
if playlist_selection:
# User provided specific selection
ytdlp_selection = self._convert_selection_to_ytdlp(playlist_selection)
ytdlp_selection = self._convert_selection_to_ytdlp(
playlist_selection
)
logger.info(
f"Playlist with user selection: {playlist_selection}{ytdlp_selection}"
)
@@ -442,17 +464,22 @@ class DownloadModal(ModalScreen):
logger.info(f"Calling download_cmdlet...")
cmd_config = (
dict(self.config)
if isinstance(self.config, dict)
else self.config
if isinstance(self.config,
dict) else self.config
)
if isinstance(cmd_config, dict):
cmd_config["_quiet_background_output"] = True
returncode = download_cmdlet(result_obj, cmdlet_args, cmd_config)
returncode = download_cmdlet(
result_obj,
cmdlet_args,
cmd_config
)
logger.info(f"download_cmdlet returned: {returncode}")
except Exception as cmdlet_error:
# If cmdlet throws an exception, log it
logger.error(
f"❌ download-cmdlet exception: {cmdlet_error}", exc_info=True
f"❌ download-cmdlet exception: {cmdlet_error}",
exc_info=True
)
if worker:
import traceback
@@ -488,16 +515,21 @@ class DownloadModal(ModalScreen):
# Log the output so it gets captured by WorkerLoggingHandler
if stdout_text:
logger.info(f"[{download_cmdlet_name} output]\n{stdout_text}")
logger.info(
f"[{download_cmdlet_name} output]\n{stdout_text}"
)
if stderr_text:
logger.info(f"[{download_cmdlet_name} stderr]\n{stderr_text}")
logger.info(
f"[{download_cmdlet_name} stderr]\n{stderr_text}"
)
if returncode != 0:
download_failed_msg = f"{download_cmdlet_name} stage failed with code {returncode}\nstdout: {stdout_text}\nstderr: {stderr_text}"
logger.error(download_failed_msg)
if worker:
worker.append_stdout(f"\n{download_failed_msg}\n")
worker.finish(
"error", "Download stage failed - see logs above for details"
"error",
"Download stage failed - see logs above for details"
)
# Log to stderr as well so it shows in terminal
@@ -528,26 +560,23 @@ class DownloadModal(ModalScreen):
import re
http_match = re.search(
r"HTTP Error (\d{3})", stderr_text + stdout_text, re.IGNORECASE
r"HTTP Error (\d{3})",
stderr_text + stdout_text,
re.IGNORECASE
)
if http_match:
error_reason = f"HTTP Error {http_match.group(1)}: Server returned an error"
else:
error_reason = "HTTP error from server"
elif (
"no such file or directory" in error_text
or "file not found" in error_text
):
elif ("no such file or directory" in error_text
or "file not found" in error_text):
error_reason = (
"File not found (yt-dlp may not be installed or not in PATH)"
)
elif "unable to download" in error_text:
error_reason = "Unable to download video (network issue or content unavailable)"
elif (
"connection" in error_text
or "timeout" in error_text
or "timed out" in error_text
):
elif ("connection" in error_text or "timeout" in error_text
or "timed out" in error_text):
error_reason = "Network connection failed or timed out"
elif "permission" in error_text or "access denied" in error_text:
error_reason = (
@@ -567,22 +596,20 @@ class DownloadModal(ModalScreen):
# If still unknown, try to extract last line of stderr as it often contains the actual error
if error_reason == "Unknown error":
stderr_lines = [
line.strip() for line in stderr_text.split("\n") if line.strip()
line.strip() for line in stderr_text.split("\n")
if line.strip()
]
if stderr_lines:
# Look for error-like lines (usually contain "error", "failed", "ERROR", etc)
for line in reversed(stderr_lines):
if any(
keyword in line.lower()
for keyword in [
if any(keyword in line.lower() for keyword in [
"error",
"failed",
"exception",
"traceback",
"warning",
]
):
error_reason = line[:150] # Limit to 150 chars
"warning", ]):
error_reason = line[:150
] # Limit to 150 chars
break
# If no error keyword found, use the last line
if error_reason == "Unknown error":
@@ -613,9 +640,13 @@ class DownloadModal(ModalScreen):
worker.append_stdout(f"\n❌ DOWNLOAD FAILED\n")
worker.append_stdout(f"Reason: {error_reason}\n")
if stderr_text and stderr_text.strip():
worker.append_stdout(f"\nFull error output:\n{stderr_text}\n")
worker.append_stdout(
f"\nFull error output:\n{stderr_text}\n"
)
if stdout_text and stdout_text.strip():
worker.append_stdout(f"\nStandard output:\n{stdout_text}\n")
worker.append_stdout(
f"\nStandard output:\n{stdout_text}\n"
)
# Don't try to tag if download failed
self.app.call_from_thread(self._hide_progress)
self.app.call_from_thread(self.dismiss)
@@ -623,11 +654,17 @@ class DownloadModal(ModalScreen):
else:
download_succeeded = True
# Always log output at INFO level so we can see what happened
logger.info(f"{download_cmdlet_name} stage completed successfully")
logger.info(
f"{download_cmdlet_name} stage completed successfully"
)
if stdout_text:
logger.info(f"{download_cmdlet_name} stdout:\n{stdout_text}")
logger.info(
f"{download_cmdlet_name} stdout:\n{stdout_text}"
)
if stderr_text:
logger.info(f"{download_cmdlet_name} stderr:\n{stderr_text}")
logger.info(
f"{download_cmdlet_name} stderr:\n{stderr_text}"
)
# Log step to worker
if worker:
@@ -641,7 +678,7 @@ class DownloadModal(ModalScreen):
if self.is_playlist and merge_enabled:
# Get output directory
from pathlib import Path
from config import resolve_output_dir
from SYS.config import resolve_output_dir
output_dir = resolve_output_dir(self.config)
logger.info(
@@ -660,7 +697,9 @@ class DownloadModal(ModalScreen):
if filename:
full_path = output_dir / filename
if full_path.exists():
extracted_files.append(str(full_path))
extracted_files.append(
str(full_path)
)
logger.debug(
f"Found downloaded file from output: {filename}"
)
@@ -677,24 +716,29 @@ class DownloadModal(ModalScreen):
current_time = time.time()
recent_files = []
for f in (
list(output_dir.glob("*.mp3"))
+ list(output_dir.glob("*.m4a"))
+ list(output_dir.glob("*.mp4"))
):
for f in (list(output_dir.glob("*.mp3")) +
list(output_dir.glob("*.m4a")) +
list(output_dir.glob("*.mp4"))):
# Files modified in last 30 minutes (extended window)
if current_time - f.stat().st_mtime < 1800:
recent_files.append((f, f.stat().st_mtime))
recent_files.append(
(f,
f.stat().st_mtime)
)
# Sort by modification time to preserve order
recent_files.sort(key=lambda x: x[1])
downloaded_files = [str(f[0]) for f in recent_files]
downloaded_files = [
str(f[0]) for f in recent_files
]
logger.info(
f"Found {len(downloaded_files)} recently modified files in directory (fallback)"
)
if downloaded_files:
logger.info(f"Found {len(downloaded_files)} files to merge")
logger.info(
f"Found {len(downloaded_files)} files to merge"
)
if downloaded_files:
logger.info(
f"Files to merge: {downloaded_files[:3]}... (showing first 3)"
@@ -707,10 +751,12 @@ class DownloadModal(ModalScreen):
# Extract path after "Saved to "
saved_idx = line.find("Saved to")
if saved_idx != -1:
path = line[saved_idx + 8 :].strip()
path = line[saved_idx + 8:].strip()
if path:
downloaded_files.append(path)
logger.debug(f"Found downloaded file: {path}")
logger.debug(
f"Found downloaded file: {path}"
)
# For merge scenarios, DON'T set to first file yet - merge first, then tag
# For non-merge, set to first file for tagging
@@ -733,9 +779,14 @@ class DownloadModal(ModalScreen):
+ download_stderr_text
)
logger.info(f"{download_cmdlet_name} stage completed successfully")
logger.info(
f"{download_cmdlet_name} stage completed successfully"
)
except Exception as e:
logger.error(f"{download_cmdlet_name} execution error: {e}", exc_info=True)
logger.error(
f"{download_cmdlet_name} execution error: {e}",
exc_info=True
)
self.app.call_from_thread(
self.app.notify,
f"Download error: {e}",
@@ -785,9 +836,13 @@ class DownloadModal(ModalScreen):
# Extract file list from marker
files_line = download_stderr_text.split("\n")[0]
if files_line.startswith("DOWNLOADED_FILES:"):
files_str = files_line[len("DOWNLOADED_FILES:") :]
file_list = [f.strip() for f in files_str.split(",") if f.strip()]
logger.info(f"Found {len(file_list)} downloaded files from marker")
files_str = files_line[len("DOWNLOADED_FILES:"):]
file_list = [
f.strip() for f in files_str.split(",") if f.strip()
]
logger.info(
f"Found {len(file_list)} downloaded files from marker"
)
# Create result objects with proper attributes
for filepath in file_list:
@@ -821,7 +876,9 @@ class DownloadModal(ModalScreen):
with redirect_stdout(stdout_buf), redirect_stderr(stderr_buf):
# Pass the list of file results to merge-file
merge_returncode = merge_cmdlet(
files_to_merge, merge_args, self.config
files_to_merge,
merge_args,
self.config
)
merge_stdout = stdout_buf.getvalue()
@@ -863,7 +920,8 @@ class DownloadModal(ModalScreen):
# Extract path after "into: "
into_idx = line.find("into:")
if into_idx != -1:
merged_file_path = line[into_idx + 5 :].strip()
merged_file_path = line[into_idx +
5:].strip()
if merged_file_path:
logger.info(
f"Detected merged file path: {merged_file_path}"
@@ -873,16 +931,13 @@ class DownloadModal(ModalScreen):
# If not found in stderr, try stdout
if not merged_file_path:
for line in merge_stdout.split("\n"):
if (
"merged" in line.lower()
or line.endswith(".mp3")
or line.endswith(".m4a")
):
if ("merged" in line.lower()
or line.endswith(".mp3")
or line.endswith(".m4a")):
merged_file_path = line.strip()
if (
merged_file_path
and not merged_file_path.startswith("[")
):
if (merged_file_path and
not merged_file_path.startswith("[")
):
logger.info(
f"Detected merged file path: {merged_file_path}"
)
@@ -924,11 +979,15 @@ class DownloadModal(ModalScreen):
# Log step to worker
if worker:
worker.log_step(f"Starting add-tags stage with {len(tags)} tags...")
worker.log_step(
f"Starting add-tags stage with {len(tags)} tags..."
)
# Build add-tags arguments. add-tags requires a store; for downloads, default to local sidecar tagging.
tag_args = (
["-store", "local"] + [str(t) for t in tags] + ["--source", str(source)]
["-store",
"local"] + [str(t) for t in tags] + ["--source",
str(source)]
)
logger.info(f" Tag args: {tag_args}")
logger.info(
@@ -944,7 +1003,11 @@ class DownloadModal(ModalScreen):
stderr_buf = io.StringIO()
with redirect_stdout(stdout_buf), redirect_stderr(stderr_buf):
returncode = add_tags_cmdlet(result_obj, tag_args, self.config)
returncode = add_tags_cmdlet(
result_obj,
tag_args,
self.config
)
stdout_text = stdout_buf.getvalue()
stderr_text = stderr_buf.getvalue()
@@ -956,7 +1019,9 @@ class DownloadModal(ModalScreen):
logger.info(f"[add-tags stderr]\n{stderr_text}")
if returncode != 0:
logger.error(f"add-tags stage failed with code {returncode}")
logger.error(
f"add-tags stage failed with code {returncode}"
)
logger.error(f" stdout: {stdout_text}")
logger.error(f" stderr: {stderr_text}")
self.app.call_from_thread(
@@ -996,7 +1061,10 @@ class DownloadModal(ModalScreen):
logger.info(skip_msg)
if worker:
worker.append_stdout(f"\n{skip_msg}\n")
worker.finish("error", "Download stage failed - see logs above for details")
worker.finish(
"error",
"Download stage failed - see logs above for details"
)
elif tags:
logger.info("No tags to add (tags list is empty)")
@@ -1029,13 +1097,17 @@ class DownloadModal(ModalScreen):
pass
self.app.call_from_thread(self._hide_progress)
self.app.call_from_thread(
self.app.notify, f"Error: {e}", title="Error", severity="error"
self.app.notify,
f"Error: {e}",
title="Error",
severity="error"
)
def _create_url_result(self, url: str):
"""Create a result object from a URL for cmdlet processing."""
class URLDownloadResult:
def __init__(self, u):
self.target = u
self.url = u
@@ -1089,9 +1161,13 @@ class DownloadModal(ModalScreen):
# Check if multiple url provided
if len(url) > 1:
logger.info(f"Detected {len(url)} url - checking for PDF pseudo-playlist")
logger.info(
f"Detected {len(url)} url - checking for PDF pseudo-playlist"
)
# Check if all url appear to be PDFs
all_pdfs = all(url.endswith(".pdf") or "pdf" in url.lower() for url in url)
all_pdfs = all(
url.endswith(".pdf") or "pdf" in url.lower() for url in url
)
if all_pdfs:
logger.info(f"All url are PDFs - creating pseudo-playlist")
self._handle_pdf_playlist(url)
@@ -1107,7 +1183,9 @@ class DownloadModal(ModalScreen):
# Run in background to prevent UI freezing
self._scrape_metadata_worker(
url, wipe_tags_and_source=wipe_tags, skip_tag_scraping=not wipe_tags
url,
wipe_tags_and_source=wipe_tags,
skip_tag_scraping=not wipe_tags
)
except Exception as e:
@@ -1129,7 +1207,10 @@ class DownloadModal(ModalScreen):
if not expand_tag_lists or not process_tags_from_string:
logger.warning("tag_helpers not available")
self.app.notify(
"Tag processing unavailable", title="Error", severity="error", timeout=2
"Tag processing unavailable",
title="Error",
severity="error",
timeout=2
)
return
@@ -1169,7 +1250,11 @@ class DownloadModal(ModalScreen):
except Exception as e:
logger.error(f"Error in _action_scrape_tags: {e}", exc_info=True)
self.app.notify(f"Error processing tags: {e}", title="Error", severity="error")
self.app.notify(
f"Error processing tags: {e}",
title="Error",
severity="error"
)
def _handle_pdf_playlist(self, pdf_url: list) -> None:
"""Handle multiple PDF url as a pseudo-playlist.
@@ -1196,7 +1281,10 @@ class DownloadModal(ModalScreen):
if not filename or filename.endswith(".pdf"):
filename = filename or f"pdf_{idx}.pdf"
# Remove .pdf extension for display
title = filename.replace(".pdf", "").replace("_", " ").replace("-", " ")
title = filename.replace(".pdf",
"").replace("_",
" ").replace("-",
" ")
except Exception as e:
logger.debug(f"Could not extract filename: {e}")
title = f"PDF {idx}"
@@ -1236,11 +1324,18 @@ class DownloadModal(ModalScreen):
except Exception as e:
logger.error(f"Error handling PDF playlist: {e}", exc_info=True)
self.app.notify(
f"Error loading PDF playlist: {e}", title="Error", severity="error", timeout=3
f"Error loading PDF playlist: {e}",
title="Error",
severity="error",
timeout=3
)
def _handle_pdf_playlist_download(
self, pdf_url: list, tags: list, selection: str, merge_enabled: bool
self,
pdf_url: list,
tags: list,
selection: str,
merge_enabled: bool
) -> None:
"""Download and merge PDF playlist.
@@ -1263,13 +1358,15 @@ class DownloadModal(ModalScreen):
try:
from pathlib import Path
import requests
from config import resolve_output_dir
from SYS.config import resolve_output_dir
# Create temporary list of playlist items for selection parsing
# We need this because _parse_playlist_selection uses self.playlist_items
temp_items = []
for url in pdf_url:
temp_items.append({"title": url})
temp_items.append({
"title": url
})
self.playlist_items = temp_items
# Parse selection to get which PDFs to download
@@ -1339,7 +1436,9 @@ class DownloadModal(ModalScreen):
reader = PdfReader(pdf_file)
for page in reader.pages:
writer.add_page(page)
logger.info(f"Added {len(reader.pages)} pages from {pdf_file.name}")
logger.info(
f"Added {len(reader.pages)} pages from {pdf_file.name}"
)
# Save merged PDF to output directory
output_dir = Path(resolve_output_dir(self.config))
@@ -1365,6 +1464,7 @@ class DownloadModal(ModalScreen):
# Create a result object for the PDF
class PDFResult:
def __init__(self, p):
self.path = str(p)
self.target = str(p)
@@ -1380,10 +1480,16 @@ class DownloadModal(ModalScreen):
tag_args = ["-store", "local"] + [str(t) for t in tags]
with redirect_stdout(stdout_buf), redirect_stderr(stderr_buf):
tag_returncode = tag_cmdlet(result_obj, tag_args, self.config)
tag_returncode = tag_cmdlet(
result_obj,
tag_args,
self.config
)
if tag_returncode != 0:
logger.warning(f"Tag stage returned code {tag_returncode}")
logger.warning(
f"Tag stage returned code {tag_returncode}"
)
self.app.call_from_thread(
self.app.notify,
@@ -1440,7 +1546,10 @@ class DownloadModal(ModalScreen):
@work(thread=True)
def _scrape_metadata_worker(
self, url: str, wipe_tags_and_source: bool = False, skip_tag_scraping: bool = False
self,
url: str,
wipe_tags_and_source: bool = False,
skip_tag_scraping: bool = False
) -> None:
"""Background worker to scrape metadata using get-tag cmdlet.
@@ -1456,7 +1565,10 @@ class DownloadModal(ModalScreen):
if not get_cmdlet:
logger.error("cmdlet module not available")
self.app.call_from_thread(
self.app.notify, "cmdlet module not available", title="Error", severity="error"
self.app.notify,
"cmdlet module not available",
title="Error",
severity="error"
)
return
@@ -1465,12 +1577,16 @@ class DownloadModal(ModalScreen):
if not get_tag_cmdlet:
logger.error("get-tag cmdlet not found")
self.app.call_from_thread(
self.app.notify, "get-tag cmdlet not found", title="Error", severity="error"
self.app.notify,
"get-tag cmdlet not found",
title="Error",
severity="error"
)
return
# Create a simple result object for the cmdlet
class URLResult:
def __init__(self, u):
self.target = u
self.hash_hex = None
@@ -1489,7 +1605,9 @@ class DownloadModal(ModalScreen):
args = [] if skip_tag_scraping else ["-scrape", url]
with redirect_stdout(output_buffer), redirect_stderr(error_buffer):
returncode = get_tag_cmdlet(result_obj, args, {})
returncode = get_tag_cmdlet(result_obj,
args,
{})
if returncode != 0:
error_msg = error_buffer.getvalue()
@@ -1559,7 +1677,8 @@ class DownloadModal(ModalScreen):
# Build metadata dict in the format expected by _populate_from_metadata
# If skipping tag scraping, preserve existing tags
existing_tags = self.tags_textarea.text.strip().split("\n") if skip_tag_scraping else []
existing_tags = self.tags_textarea.text.strip(
).split("\n") if skip_tag_scraping else []
existing_tags = [tag.strip() for tag in existing_tags if tag.strip()]
# Extract playlist items if present
@@ -1579,7 +1698,11 @@ class DownloadModal(ModalScreen):
)
# Update UI on main thread
self.app.call_from_thread(self._populate_from_metadata, metadata, wipe_tags_and_source)
self.app.call_from_thread(
self._populate_from_metadata,
metadata,
wipe_tags_and_source
)
except Exception as e:
logger.error(f"Metadata worker error: {e}", exc_info=True)
@@ -1611,7 +1734,9 @@ class DownloadModal(ModalScreen):
# Handle keywords (all, merge, a, m) - can be space or comma separated
# "ALL MERGE", "A M", "ALL,MERGE" etc all mean download all items
if any(kw in selection_str.replace(",", " ").split() for kw in {"A", "ALL", "M", "MERGE"}):
if any(kw in selection_str.replace(",",
" ").split()
for kw in {"A", "ALL", "M", "MERGE"}):
# User said to get all items (merge is same as all in this context)
return f"1-{max_idx}"
@@ -1647,7 +1772,9 @@ class DownloadModal(ModalScreen):
# Handle keywords (all, merge, a, m) - can be space or comma separated
# "ALL MERGE", "A M", "ALL,MERGE" etc all mean download all items
if any(kw in selection_str.replace(",", " ").split() for kw in {"A", "ALL", "M", "MERGE"}):
if any(kw in selection_str.replace(",",
" ").split()
for kw in {"A", "ALL", "M", "MERGE"}):
# User said to get all items
return list(range(max_idx))
@@ -1676,7 +1803,12 @@ class DownloadModal(ModalScreen):
return []
def _execute_download_pipeline(
self, result_obj: Any, tags: list, source: str, download_enabled: bool, worker=None
self,
result_obj: Any,
tags: list,
source: str,
download_enabled: bool,
worker=None
) -> None:
"""Execute the download pipeline for a single item.
@@ -1694,7 +1826,10 @@ class DownloadModal(ModalScreen):
if worker:
worker.append_stdout(f"❌ ERROR: {error_msg}\n")
self.app.call_from_thread(
self.app.notify, "cmdlet system unavailable", title="Error", severity="error"
self.app.notify,
"cmdlet system unavailable",
title="Error",
severity="error"
)
return
@@ -1716,7 +1851,8 @@ class DownloadModal(ModalScreen):
with redirect_stdout(stdout_buf), redirect_stderr(stderr_buf):
cmd_config = (
dict(self.config) if isinstance(self.config, dict) else self.config
dict(self.config) if isinstance(self.config,
dict) else self.config
)
if isinstance(cmd_config, dict):
cmd_config["_quiet_background_output"] = True
@@ -1760,7 +1896,10 @@ class DownloadModal(ModalScreen):
f"{error_msg}\nTraceback:\n{__import__('traceback').format_exc()}\n"
)
self.app.call_from_thread(
self.app.notify, str(e)[:100], title="Download Error", severity="error"
self.app.notify,
str(e)[:100],
title="Download Error",
severity="error"
)
return
@@ -1781,7 +1920,9 @@ class DownloadModal(ModalScreen):
stderr_buf = io.StringIO()
with redirect_stdout(stdout_buf), redirect_stderr(stderr_buf):
tag_returncode = tag_cmdlet(result_obj, tag_args, {})
tag_returncode = tag_cmdlet(result_obj,
tag_args,
{})
stdout_text = stdout_buf.getvalue()
stderr_text = stderr_buf.getvalue()
@@ -1860,7 +2001,11 @@ class DownloadModal(ModalScreen):
except Exception as e:
logger.error(f"Error populating playlist tree: {e}")
def _populate_from_metadata(self, metadata: dict, wipe_tags_and_source: bool = False) -> None:
def _populate_from_metadata(
self,
metadata: dict,
wipe_tags_and_source: bool = False
) -> None:
"""Populate modal fields from extracted metadata.
Args:
@@ -1965,7 +2110,11 @@ class DownloadModal(ModalScreen):
except Exception as e:
logger.error(f"Error populating metadata: {e}", exc_info=True)
self.app.notify(f"Failed to populate metadata: {e}", title="Error", severity="error")
self.app.notify(
f"Failed to populate metadata: {e}",
title="Error",
severity="error"
)
def on_select_changed(self, event: Select.Changed) -> None:
"""Handle Select widget changes (format selection)."""