fix(downloads): R5 S1 — downloads robustness

- pick the produced file safely: Path("") is Path("."), which passes .exists(),
  so a missing yt-dlp filepath skipped the staging fallback and crashed in
  with_name ("PosixPath('.') has an empty name") on non-YouTube sources
- unwrap a playlist/multi_video extraction to the entry that carries the media
- run_ffmpeg: drain stderr while it runs (a full 64KB pipe blocked ffmpeg
  forever and hung the worker thread), 1s cancel polling, stall watchdog and a
  bounded wait on exit
- escape apostrophes/backslashes in the ffmpeg concat list (pre-0.51.0 names)
- sweep orphaned HLS segment dirs at startup (nothing else ever reaped them)
This commit is contained in:
2026-07-23 22:32:25 +02:00
parent 08049d2aa5
commit 2f46947dcf
5 changed files with 249 additions and 30 deletions
+21
View File
@@ -254,6 +254,27 @@ def wait_for(path: Path, timeout: float = 20.0) -> bool:
return path.exists()
def sweep_orphans() -> int:
"""Delete segment directories no live session owns.
Sessions live only in THIS process, so at startup every directory under `_HLS_ROOT` is a
leftover from a crashed or killed run — and nothing else ever reaps them (the download GC
deliberately skips `.plex-hls` as a system tree), so a few restarts mid-playback leave
gigabytes of `.ts` segments behind forever."""
with _lock:
live = {s.dir for s in _sessions.values()}
dropped = 0
try:
entries = list(_HLS_ROOT.iterdir())
except OSError: # the root doesn't exist yet — nothing to sweep
return 0
for p in entries:
if p.is_dir() and p not in live:
shutil.rmtree(p, ignore_errors=True)
dropped += 1
return dropped
def reap_idle() -> int:
now = time.time()
dropped = 0