ytp-dl 0.6.3__py3-none-any.whl → 0.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scripts/api.py +31 -8
- {ytp_dl-0.6.3.dist-info → ytp_dl-0.6.4.dist-info}/METADATA +13 -8
- ytp_dl-0.6.4.dist-info/RECORD +8 -0
- ytp_dl-0.6.3.dist-info/RECORD +0 -8
- {ytp_dl-0.6.3.dist-info → ytp_dl-0.6.4.dist-info}/WHEEL +0 -0
- {ytp_dl-0.6.3.dist-info → ytp_dl-0.6.4.dist-info}/entry_points.txt +0 -0
- {ytp_dl-0.6.3.dist-info → ytp_dl-0.6.4.dist-info}/top_level.txt +0 -0
scripts/api.py
CHANGED
|
@@ -5,9 +5,9 @@ import os
|
|
|
5
5
|
import shutil
|
|
6
6
|
import tempfile
|
|
7
7
|
import time
|
|
8
|
+
from threading import BoundedSemaphore, Lock
|
|
8
9
|
|
|
9
10
|
from flask import Flask, request, send_file, jsonify
|
|
10
|
-
from gevent.lock import Semaphore
|
|
11
11
|
|
|
12
12
|
from .downloader import validate_environment, download_video
|
|
13
13
|
|
|
@@ -17,10 +17,15 @@ BASE_DOWNLOAD_DIR = os.environ.get("YTPDL_JOB_BASE_DIR", "/root/ytpdl_jobs")
|
|
|
17
17
|
os.makedirs(BASE_DOWNLOAD_DIR, exist_ok=True)
|
|
18
18
|
|
|
19
19
|
MAX_CONCURRENT = int(os.environ.get("YTPDL_MAX_CONCURRENT", "1"))
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
# Thread-safe concurrency gate (caps actual download jobs).
|
|
22
|
+
_sem = BoundedSemaphore(MAX_CONCURRENT)
|
|
23
|
+
|
|
24
|
+
# Track in-flight jobs for /healthz reporting.
|
|
25
|
+
_in_use = 0
|
|
26
|
+
_in_use_lock = Lock()
|
|
21
27
|
|
|
22
28
|
# Failsafe: delete abandoned job dirs older than this many seconds.
|
|
23
|
-
# (keep 21600 if you prefer; 3600 is fine too)
|
|
24
29
|
STALE_JOB_TTL_S = int(os.environ.get("YTPDL_STALE_JOB_TTL_S", "3600"))
|
|
25
30
|
|
|
26
31
|
_ALLOWED_EXTENSIONS = {"mp3", "mp4", "best"}
|
|
@@ -43,11 +48,28 @@ def _cleanup_stale_jobs() -> None:
|
|
|
43
48
|
pass
|
|
44
49
|
|
|
45
50
|
|
|
51
|
+
def _try_acquire_job_slot() -> bool:
|
|
52
|
+
global _in_use
|
|
53
|
+
if not _sem.acquire(blocking=False):
|
|
54
|
+
return False
|
|
55
|
+
with _in_use_lock:
|
|
56
|
+
_in_use += 1
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _release_job_slot() -> None:
|
|
61
|
+
global _in_use
|
|
62
|
+
with _in_use_lock:
|
|
63
|
+
if _in_use > 0:
|
|
64
|
+
_in_use -= 1
|
|
65
|
+
_sem.release()
|
|
66
|
+
|
|
67
|
+
|
|
46
68
|
@app.route("/api/download", methods=["POST"])
|
|
47
69
|
def handle_download():
|
|
48
70
|
_cleanup_stale_jobs()
|
|
49
71
|
|
|
50
|
-
if not
|
|
72
|
+
if not _try_acquire_job_slot():
|
|
51
73
|
return jsonify(error="Server busy, try again later"), 503
|
|
52
74
|
|
|
53
75
|
job_dir: str | None = None
|
|
@@ -57,7 +79,7 @@ def handle_download():
|
|
|
57
79
|
nonlocal released
|
|
58
80
|
if not released:
|
|
59
81
|
released = True
|
|
60
|
-
|
|
82
|
+
_release_job_slot()
|
|
61
83
|
|
|
62
84
|
try:
|
|
63
85
|
data = request.get_json(force=True)
|
|
@@ -90,8 +112,7 @@ def handle_download():
|
|
|
90
112
|
if not (filename and os.path.exists(filename)):
|
|
91
113
|
raise RuntimeError("Download failed")
|
|
92
114
|
|
|
93
|
-
# Release
|
|
94
|
-
# Streaming the file should not block the next download job.
|
|
115
|
+
# Release slot as soon as yt-dlp is done.
|
|
95
116
|
_release_once()
|
|
96
117
|
|
|
97
118
|
response = send_file(filename, as_attachment=True)
|
|
@@ -126,7 +147,9 @@ def handle_download():
|
|
|
126
147
|
|
|
127
148
|
@app.route("/healthz", methods=["GET"])
|
|
128
149
|
def healthz():
|
|
129
|
-
|
|
150
|
+
with _in_use_lock:
|
|
151
|
+
in_use = _in_use
|
|
152
|
+
return jsonify(ok=True, in_use=in_use, capacity=MAX_CONCURRENT), 200
|
|
130
153
|
|
|
131
154
|
|
|
132
155
|
def main():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: ytp-dl
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
4
4
|
Summary: YouTube video downloader with Mullvad VPN integration and Flask API
|
|
5
5
|
Home-page: https://github.com/yourusername/ytp-dl
|
|
6
6
|
Author: dumgum82
|
|
@@ -20,7 +20,6 @@ Description-Content-Type: text/markdown
|
|
|
20
20
|
Requires-Dist: yt-dlp[default]
|
|
21
21
|
Requires-Dist: flask
|
|
22
22
|
Requires-Dist: requests
|
|
23
|
-
Requires-Dist: gevent
|
|
24
23
|
Requires-Dist: gunicorn
|
|
25
24
|
Dynamic: author
|
|
26
25
|
Dynamic: author-email
|
|
@@ -58,7 +57,7 @@ Dynamic: summary
|
|
|
58
57
|
## 📦 Installation
|
|
59
58
|
|
|
60
59
|
```bash
|
|
61
|
-
pip install ytp-dl==0.6.
|
|
60
|
+
pip install ytp-dl==0.6.4 yt-dlp[default]
|
|
62
61
|
```
|
|
63
62
|
|
|
64
63
|
**Requirements:**
|
|
@@ -249,9 +248,9 @@ sudo systemctl start ytp-dl-api
|
|
|
249
248
|
* ✅ Installs Python, FFmpeg, and Mullvad CLI
|
|
250
249
|
* ✅ Installs Deno system-wide (required by yt-dlp for modern YouTube extraction)
|
|
251
250
|
* ✅ Creates virtualenv at `/opt/yt-dlp-mullvad/venv`
|
|
252
|
-
* ✅ Installs `ytp-dl==0.6.
|
|
251
|
+
* ✅ Installs `ytp-dl==0.6.4` + `yt-dlp[default]` into the virtualenv
|
|
253
252
|
* ✅ Sets up systemd service on port 5000
|
|
254
|
-
* ✅ Configures Gunicorn with
|
|
253
|
+
* ✅ Configures Gunicorn with gthread (threaded) workers
|
|
255
254
|
|
|
256
255
|
```bash
|
|
257
256
|
#!/usr/bin/env bash
|
|
@@ -261,7 +260,7 @@ sudo systemctl start ytp-dl-api
|
|
|
261
260
|
# - Installs Python, ffmpeg, Mullvad CLI
|
|
262
261
|
# - Installs Deno system-wide (JS runtime required for modern YouTube extraction via yt-dlp)
|
|
263
262
|
# - Creates a virtualenv at /opt/yt-dlp-mullvad/venv
|
|
264
|
-
# - Installs ytp-dl==0.6.
|
|
263
|
+
# - Installs ytp-dl==0.6.4 + yt-dlp[default] + gunicorn + gevent in that venv
|
|
265
264
|
# - Creates a simple systemd service ytp-dl-api.service on port 5000
|
|
266
265
|
#
|
|
267
266
|
# Mullvad connect/disconnect is handled per-job by downloader.py.
|
|
@@ -276,6 +275,7 @@ VENV_DIR="${VENV_DIR:-${APP_DIR}/venv}" # python venv
|
|
|
276
275
|
MV_ACCOUNT="${MV_ACCOUNT:-}" # Mullvad account (put number after -)
|
|
277
276
|
YTPDL_MAX_CONCURRENT="${YTPDL_MAX_CONCURRENT:-1}" # API concurrency cap
|
|
278
277
|
YTPDL_MULLVAD_LOCATION="${YTPDL_MULLVAD_LOCATION:-us}" # default Mullvad relay hint
|
|
278
|
+
GUNICORN_THREADS="${GUNICORN_THREADS:-4}" # keep API responsive on tiny VPS
|
|
279
279
|
### -------------------------------------------------------------------------
|
|
280
280
|
|
|
281
281
|
[[ "${EUID}" -eq 0 ]] || { echo "Please run as root"; exit 1; }
|
|
@@ -297,6 +297,11 @@ fi
|
|
|
297
297
|
|
|
298
298
|
mullvad status || true
|
|
299
299
|
|
|
300
|
+
# Keep the public API reachable even if Mullvad disconnects between jobs.
|
|
301
|
+
# (Lockdown mode can block all traffic while disconnected.)
|
|
302
|
+
mullvad lockdown-mode set off || true
|
|
303
|
+
mullvad lan set allow || true
|
|
304
|
+
|
|
300
305
|
echo "==> 1.5) Install Deno (system-wide, for yt-dlp YouTube extraction)"
|
|
301
306
|
# Install into /usr/local/bin/deno so systemd PATH can see it.
|
|
302
307
|
# Official installer supports system-wide install via DENO_INSTALL=/usr/local.
|
|
@@ -310,7 +315,7 @@ mkdir -p "${APP_DIR}"
|
|
|
310
315
|
python3 -m venv "${VENV_DIR}"
|
|
311
316
|
source "${VENV_DIR}/bin/activate"
|
|
312
317
|
pip install --upgrade pip
|
|
313
|
-
pip install "ytp-dl==0.6.
|
|
318
|
+
pip install "ytp-dl==0.6.4" "yt-dlp[default]" gunicorn
|
|
314
319
|
deactivate
|
|
315
320
|
|
|
316
321
|
echo "==> 3) API environment file (/etc/default/ytp-dl-api)"
|
|
@@ -334,7 +339,7 @@ EnvironmentFile=/etc/default/ytp-dl-api
|
|
|
334
339
|
Environment=VIRTUAL_ENV=${VENV_DIR}
|
|
335
340
|
Environment=PATH=${VENV_DIR}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
|
|
336
341
|
|
|
337
|
-
ExecStart=${VENV_DIR}/bin/gunicorn -k
|
|
342
|
+
ExecStart=${VENV_DIR}/bin/gunicorn -k gthread -w 1 --threads ${GUNICORN_THREADS} --timeout 0 --graceful-timeout 15 --keep-alive 20 --bind 0.0.0.0:${PORT} scripts.api:app
|
|
338
343
|
|
|
339
344
|
Restart=always
|
|
340
345
|
RestartSec=3
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
scripts/__init__.py,sha256=EbAplfCcyLD3Q_9sxemm6owCc5_UJv53vmlxy810p2s,152
|
|
2
|
+
scripts/api.py,sha256=cyLjHmelLwzh8-GOjqXsQdhm6wLX8bOkADZ_qU1naRQ,4331
|
|
3
|
+
scripts/downloader.py,sha256=vvHasu-41DGPDUzOTA4kz52tijTkaii1NnuU4cHQxg8,10825
|
|
4
|
+
ytp_dl-0.6.4.dist-info/METADATA,sha256=Euvl-2zd5I4uh-6RVV1xGp-E8VQan7DvGXWOO5LXh6Q,11627
|
|
5
|
+
ytp_dl-0.6.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
6
|
+
ytp_dl-0.6.4.dist-info/entry_points.txt,sha256=QqjqZZAEt3Y7RGrleqZ312sjjboUpbMLdo7qFxuCH30,48
|
|
7
|
+
ytp_dl-0.6.4.dist-info/top_level.txt,sha256=rmzd5mewlrJy4sT608KPib7sM7edoY75AeqJeY3SPB4,8
|
|
8
|
+
ytp_dl-0.6.4.dist-info/RECORD,,
|
ytp_dl-0.6.3.dist-info/RECORD
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
scripts/__init__.py,sha256=EbAplfCcyLD3Q_9sxemm6owCc5_UJv53vmlxy810p2s,152
|
|
2
|
-
scripts/api.py,sha256=EmTmzhpElx5QaJJ5z8GiimJTVZOHRoKhcReIRUCShBg,3943
|
|
3
|
-
scripts/downloader.py,sha256=vvHasu-41DGPDUzOTA4kz52tijTkaii1NnuU4cHQxg8,10825
|
|
4
|
-
ytp_dl-0.6.3.dist-info/METADATA,sha256=Rloz8LictcjPy_qXwkUtYWjAKyefSAGbxQ3GvmSyBv8,11342
|
|
5
|
-
ytp_dl-0.6.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
6
|
-
ytp_dl-0.6.3.dist-info/entry_points.txt,sha256=QqjqZZAEt3Y7RGrleqZ312sjjboUpbMLdo7qFxuCH30,48
|
|
7
|
-
ytp_dl-0.6.3.dist-info/top_level.txt,sha256=rmzd5mewlrJy4sT608KPib7sM7edoY75AeqJeY3SPB4,8
|
|
8
|
-
ytp_dl-0.6.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|