54 lines
2.2 KiB
Python
54 lines
2.2 KiB
Python
#!/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()
|