Harden yt-dlp binary resolution and analysis subprocess handling

- get_yt_dlp_path() no longer implicitly executes a bare "yt-dlp" from
  PATH (on Windows that lookup includes the CWD, so a planted binary
  in a writable directory could be run). A system yt-dlp is used only
  behind the explicit advanced.allow_system_ytdlp config opt-in, and
  then always as a which()-resolved absolute path. Analysis and
  download refuse to exec the not-installed sentinel.
- Analysis subprocesses now run in their own session and the whole
  process group is killed on timeout, so deno grandchildren no longer
  leak; partial stderr is preserved and logged, and output decoding is
  pinned to utf-8 with replacement (Windows locale codecs crashed on
  non-UTF8 titles).
- Flat-playlist entries are filtered for None (private/deleted first
  video no longer breaks analysis).
- update_yt_dlp() normalizes the sentinel to Path, unbreaking the pip
  fallback path that crashed on str.exists().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jaroslav Beneš
2026-07-25 01:30:08 +02:00
parent 852804ee5c
commit ebb9422591
4 changed files with 80 additions and 22 deletions
+5 -1
View File
@@ -244,8 +244,12 @@ class DownloadThread(QThread):
def _build_yt_dlp_command(self) -> List[str]:
"""Build the yt-dlp command line with all options for direct execution."""
yt_dlp_path: str = get_yt_dlp_path()
if str(yt_dlp_path) == "yt-dlp":
# Sentinel: no managed binary and no opted-in system binary.
# Never exec a bare command name from PATH.
raise FileNotFoundError("yt-dlp is not installed - run the yt-dlp setup first")
# Build the command line array
cmd: List[str] = [yt_dlp_path]
cmd: List[str] = [str(yt_dlp_path)]
logger.debug(f"Using yt-dlp from: {yt_dlp_path}")
# Add concurrent fragments setting