Pause discovery metadata calls while a download is active
_meta_run now checks _active_downloads before each background yt-dlp call. If a download is running it waits (3s poll loop) until the download finishes before making the next metadata request. This prevents YouTube from seeing the same session used simultaneously by a download and a discovery/metadata call, which was causing cookie invalidation even with private cookie copies. Downloads still run immediately without waiting for metadata. Background discovery is the one that yields. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -55,10 +55,25 @@ _meta_lock = threading.Lock()
|
|||||||
_meta_last_call: float = 0.0
|
_meta_last_call: float = 0.0
|
||||||
_META_MIN_GAP = 5.0 # seconds between any two metadata requests
|
_META_MIN_GAP = 5.0 # seconds between any two metadata requests
|
||||||
|
|
||||||
|
# Active download counter — _meta_run pauses while any download is running so
|
||||||
|
# background discovery and a concurrent download never share the YouTube session
|
||||||
|
# at the same time. Downloads set/clear this; _meta_run reads it.
|
||||||
|
_active_downloads: int = 0
|
||||||
|
_active_downloads_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
def _meta_run(args: list[str], timeout: int = 60) -> tuple[str, str, int]:
|
def _meta_run(args: list[str], timeout: int = 60) -> tuple[str, str, int]:
|
||||||
global _meta_last_call
|
global _meta_last_call
|
||||||
with _meta_lock:
|
with _meta_lock:
|
||||||
|
# Pause background metadata calls while a download is active.
|
||||||
|
# Running both concurrently causes YouTube to see two requests from the
|
||||||
|
# same session simultaneously, which triggers cookie invalidation.
|
||||||
|
while True:
|
||||||
|
with _active_downloads_lock:
|
||||||
|
if _active_downloads == 0:
|
||||||
|
break
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
wait = _META_MIN_GAP - (now - _meta_last_call)
|
wait = _META_MIN_GAP - (now - _meta_last_call)
|
||||||
if wait > 0:
|
if wait > 0:
|
||||||
@@ -884,63 +899,71 @@ def start_download(
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _run_download():
|
def _run_download():
|
||||||
with _SEMAPHORE:
|
global _active_downloads
|
||||||
cookie_args = _cookie_args()
|
# Signal to _meta_run that a download is active so it pauses all
|
||||||
print(f"[ytdlp] cookie_args={cookie_args!r}", flush=True)
|
# background discovery/metadata calls for the duration. Running both
|
||||||
cmd = [
|
# concurrently causes YouTube to see the same session used simultaneously
|
||||||
"yt-dlp", url,
|
# and invalidates cookies.
|
||||||
"-f", fmt,
|
with _active_downloads_lock:
|
||||||
"--merge-output-format", "mp4",
|
_active_downloads += 1
|
||||||
"--no-part", "--no-mtime",
|
try:
|
||||||
"-o", output_template,
|
with _SEMAPHORE:
|
||||||
"--newline", "--progress", "--no-colors",
|
cookie_args = _cookie_args()
|
||||||
*subtitle_args,
|
print(f"[ytdlp] cookie_args={cookie_args!r}", flush=True)
|
||||||
*cookie_args,
|
cmd = [
|
||||||
]
|
"yt-dlp", url,
|
||||||
# Private cookie copy — download runs for minutes; without this,
|
"-f", fmt,
|
||||||
# concurrent metadata calls would write-back to the same cookie file
|
"--merge-output-format", "mp4",
|
||||||
# and corrupt the session.
|
"--no-part", "--no-mtime",
|
||||||
cmd, tmp_cookie_path = _make_private_cookie_copy(cmd)
|
"-o", output_template,
|
||||||
try:
|
"--newline", "--progress", "--no-colors",
|
||||||
process = subprocess.Popen(
|
*subtitle_args,
|
||||||
cmd,
|
*cookie_args,
|
||||||
stdout=subprocess.PIPE,
|
]
|
||||||
stderr=subprocess.STDOUT,
|
cmd, tmp_cookie_path = _make_private_cookie_copy(cmd)
|
||||||
text=True,
|
try:
|
||||||
)
|
process = subprocess.Popen(
|
||||||
file_path = None
|
cmd,
|
||||||
stream_index = 0
|
stdout=subprocess.PIPE,
|
||||||
output_lines: list[str] = []
|
stderr=subprocess.STDOUT,
|
||||||
for line in process.stdout:
|
text=True,
|
||||||
line = line.strip()
|
)
|
||||||
output_lines.append(line)
|
file_path = None
|
||||||
if re.search(r"\[download\] Destination:", line):
|
stream_index = 0
|
||||||
stream_index += 1
|
output_lines: list[str] = []
|
||||||
m = re.search(r"\[download\]\s+([\d.]+)%", line)
|
for line in process.stdout:
|
||||||
if m:
|
line = line.strip()
|
||||||
pct = float(m.group(1))
|
output_lines.append(line)
|
||||||
scaled = pct * 0.85 if stream_index <= 1 else 85.0 + pct * 0.10
|
if re.search(r"\[download\] Destination:", line):
|
||||||
on_progress(download_id, min(scaled, 95.0))
|
stream_index += 1
|
||||||
m2 = re.search(r"\[(?:download|Merger)\] Destination: (.+)", line)
|
m = re.search(r"\[download\]\s+([\d.]+)%", line)
|
||||||
if m2:
|
if m:
|
||||||
file_path = m2.group(1).strip()
|
pct = float(m.group(1))
|
||||||
|
scaled = pct * 0.85 if stream_index <= 1 else 85.0 + pct * 0.10
|
||||||
|
on_progress(download_id, min(scaled, 95.0))
|
||||||
|
m2 = re.search(r"\[(?:download|Merger)\] Destination: (.+)", line)
|
||||||
|
if m2:
|
||||||
|
file_path = m2.group(1).strip()
|
||||||
|
|
||||||
process.wait()
|
process.wait()
|
||||||
if process.returncode == 0:
|
if process.returncode == 0:
|
||||||
_strip_vtt_cue_settings(video_id)
|
_strip_vtt_cue_settings(video_id)
|
||||||
resolution = detect_resolution(file_path) if file_path else None
|
resolution = detect_resolution(file_path) if file_path else None
|
||||||
on_complete(download_id, file_path, resolution)
|
on_complete(download_id, file_path, resolution)
|
||||||
else:
|
else:
|
||||||
tail = "\n".join(output_lines[-20:]) if output_lines else "(no output)"
|
tail = "\n".join(output_lines[-20:]) if output_lines else "(no output)"
|
||||||
import logging
|
import logging
|
||||||
logging.getLogger(__name__).error("yt-dlp failed (code %d):\n%s", process.returncode, tail)
|
logging.getLogger(__name__).error("yt-dlp failed (code %d):\n%s", process.returncode, tail)
|
||||||
on_error(download_id, f"yt-dlp exited with code {process.returncode}:\n{tail}")
|
on_error(download_id, f"yt-dlp exited with code {process.returncode}:\n{tail}")
|
||||||
finally:
|
finally:
|
||||||
if tmp_cookie_path:
|
if tmp_cookie_path:
|
||||||
try:
|
try:
|
||||||
os.unlink(tmp_cookie_path)
|
os.unlink(tmp_cookie_path)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
finally:
|
||||||
|
with _active_downloads_lock:
|
||||||
|
_active_downloads -= 1
|
||||||
|
|
||||||
thread = threading.Thread(target=_run_download, daemon=True)
|
thread = threading.Thread(target=_run_download, daemon=True)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|||||||
Reference in New Issue
Block a user