Keep one long-lived yt-dlp worker process instead of spawning per call
This commit is contained in:
53
server/ytdlp-worker.py
Normal file
53
server/ytdlp-worker.py
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ytdlp-worker.py - one long-lived yt-dlp process answering many requests.
|
||||
|
||||
Spawning yt-dlp per call pays Python start-up + extractor import every time.
|
||||
This worker imports yt_dlp ONCE and runs each request's argv in-process
|
||||
through the CLI's own entry point (yt_dlp._real_main), so config files such
|
||||
as /etc/yt-dlp.conf and everything else the command line sets up apply.
|
||||
|
||||
Protocol (newline-delimited JSON):
|
||||
stdin : {"id": <int>, "args": [<yt-dlp argv>...]}
|
||||
stdout: {"id": <int>, "code": <int>, "out": "<captured stdout>", "err": "<stderr tail>"}
|
||||
Only for calls whose result is printed to stdout (-J, --dump-json, --version).
|
||||
Usage: python3 ytdlp-worker.py <path-to-yt-dlp-zipapp-or-empty>
|
||||
"""
|
||||
import io, json, sys, contextlib
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1]:
|
||||
sys.path.insert(0, sys.argv[1]) # the yt-dlp release binary is a zipapp
|
||||
import yt_dlp # noqa: E402
|
||||
|
||||
proto_out = sys.stdout
|
||||
sys.stdout = io.StringIO() # nothing may leak onto the protocol pipe
|
||||
|
||||
def run(args):
|
||||
out, err = io.StringIO(), io.StringIO()
|
||||
code = 0
|
||||
with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
|
||||
try:
|
||||
# The CLI's own entry point (not parse_options + YoutubeDL): it also
|
||||
# sets up what the command line does (JS challenge solving, plugins,
|
||||
# post-processing defaults). A bare YoutubeDL got "Sign in to
|
||||
# confirm you're not a bot" where the CLI succeeded.
|
||||
ret = yt_dlp._real_main(args)
|
||||
code = ret[0] if isinstance(ret, tuple) else (ret or 0)
|
||||
except SystemExit as e:
|
||||
code = e.code if isinstance(e.code, int) else 1
|
||||
except Exception as e: # report, never die
|
||||
err.write(f"ERROR: {e}\n")
|
||||
code = 1
|
||||
return code, out.getvalue(), err.getvalue()[-8000:]
|
||||
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
req = json.loads(line)
|
||||
code, out, err = run([str(a) for a in req.get("args", [])])
|
||||
resp = {"id": req.get("id"), "code": code, "out": out, "err": err}
|
||||
except Exception as e:
|
||||
resp = {"id": None, "code": 1, "out": "", "err": f"worker: {e}"}
|
||||
proto_out.write(json.dumps(resp) + "\n")
|
||||
proto_out.flush()
|
||||
Reference in New Issue
Block a user