fix: prevent concurrent yt-dlp sessions that invalidate cookies

Three code paths could fire yt-dlp immediately (polite=False) while a
download was already running, causing YouTube to see two simultaneous
authenticated sessions and invalidate the cookie:

- search.py: live yt-dlp fallback now skipped while any download is active
- downloads.py: _ensure_video uses polite=True so it waits for active
  downloads to finish before fetching metadata for an unknown video
- channels.py: follow_by_url uses polite=True when fetching metadata
  for a brand-new channel

Added is_download_active() helper to ytdlp.py to expose the active
download state without importing private globals.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 03:07:19 +02:00
parent a0384b2277
commit c223e57463
4 changed files with 10 additions and 4 deletions

View File

@@ -1185,7 +1185,7 @@ def follow_by_url(
channel = db.query(Channel).filter_by(youtube_channel_id=yt_channel_id).first()
if not channel:
meta = ytdlp.fetch_channel_metadata(yt_channel_id, max_videos=30)
meta = ytdlp.fetch_channel_metadata(yt_channel_id, max_videos=30, polite=True)
if not meta or not meta.get("channel"):
raise HTTPException(status_code=404, detail="Channel not found on YouTube")
ch_data = meta["channel"]

View File

@@ -125,7 +125,7 @@ def _ensure_video(db: Session, youtube_video_id: str) -> Video:
if video:
return video
meta = ytdlp.fetch_video_metadata(youtube_video_id)
meta = ytdlp.fetch_video_metadata(youtube_video_id, polite=True)
if not meta:
raise HTTPException(status_code=404, detail="Video not found on YouTube")

View File

@@ -264,8 +264,9 @@ def search(
source = "local" if (video_results or channel_results) else "none"
# Fall back to live yt-dlp search if no local results or explicitly requested
if not video_results or live:
# Fall back to live yt-dlp search if no local results or explicitly requested.
# Skip if a download is active — concurrent yt-dlp sessions invalidate cookies.
if (not video_results or live) and not ytdlp.is_download_active():
try:
live_raw = ytdlp.search_youtube(q)
live_results = _live_search_to_results(db, current_user.id, live_raw)

View File

@@ -84,6 +84,11 @@ def _meta_run(args: list[str], timeout: int = 60) -> tuple[str, str, int]:
_meta_last_call = time.monotonic()
def is_download_active() -> bool:
with _active_downloads_lock:
return _active_downloads > 0
def _parse_date(date_str: str | None) -> datetime | None:
if not date_str:
return None