ytp-dl 0.6.2__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 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,12 +17,17 @@ 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
- _sem = Semaphore(MAX_CONCURRENT)
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
29
  STALE_JOB_TTL_S = int(os.environ.get("YTPDL_STALE_JOB_TTL_S", "3600"))
24
30
 
25
- # extension is now a "mode": mp3 | mp4 | best
26
31
  _ALLOWED_EXTENSIONS = {"mp3", "mp4", "best"}
27
32
 
28
33
 
@@ -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 _sem.acquire(blocking=False):
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,12 +79,14 @@ def handle_download():
57
79
  nonlocal released
58
80
  if not released:
59
81
  released = True
60
- _sem.release()
82
+ _release_job_slot()
61
83
 
62
84
  try:
63
85
  data = request.get_json(force=True)
64
86
  url = (data.get("url") or "").strip()
65
87
  resolution = data.get("resolution")
88
+
89
+ # extension is now a "mode": mp3 | mp4 | best
66
90
  extension = (data.get("extension") or "mp4").strip().lower()
67
91
 
68
92
  if not url:
@@ -88,7 +112,7 @@ def handle_download():
88
112
  if not (filename and os.path.exists(filename)):
89
113
  raise RuntimeError("Download failed")
90
114
 
91
- # Release semaphore as soon as yt-dlp is done.
115
+ # Release slot as soon as yt-dlp is done.
92
116
  _release_once()
93
117
 
94
118
  response = send_file(filename, as_attachment=True)
@@ -108,7 +132,11 @@ def handle_download():
108
132
  if job_dir:
109
133
  shutil.rmtree(job_dir, ignore_errors=True)
110
134
  _release_once()
111
- return jsonify(error=f"Download failed: {str(e)}"), 500
135
+
136
+ msg = str(e)
137
+ if "Mullvad not logged in" in msg:
138
+ return jsonify(error=msg), 503
139
+ return jsonify(error=f"Download failed: {msg}"), 500
112
140
 
113
141
  except Exception as e:
114
142
  if job_dir:
@@ -119,7 +147,9 @@ def handle_download():
119
147
 
120
148
  @app.route("/healthz", methods=["GET"])
121
149
  def healthz():
122
- return jsonify(ok=True, in_use=(MAX_CONCURRENT - _sem.counter), capacity=MAX_CONCURRENT), 200
150
+ with _in_use_lock:
151
+ in_use = _in_use
152
+ return jsonify(ok=True, in_use=in_use, capacity=MAX_CONCURRENT), 200
123
153
 
124
154
 
125
155
  def main():
scripts/downloader.py CHANGED
@@ -5,13 +5,15 @@ import os
5
5
  import shlex
6
6
  import shutil
7
7
  import subprocess
8
+ import time
8
9
  from typing import Optional, List, Tuple
9
10
 
10
11
  # =========================
11
12
  # Config / constants
12
13
  # =========================
13
- VENV_PATH = os.environ.get("YTPDL_VENV", "/opt/ytpdl/venv")
14
+ VENV_PATH = os.environ.get("YTPDL_VENV", "/opt/yt-dlp-mullvad/venv")
14
15
  YTDLP_BIN = os.path.join(VENV_PATH, "bin", "yt-dlp")
16
+ MULLVAD_LOCATION = os.environ.get("YTPDL_MULLVAD_LOCATION", "us")
15
17
 
16
18
  MODERN_UA = os.environ.get(
17
19
  "YTPDL_USER_AGENT",
@@ -20,11 +22,6 @@ MODERN_UA = os.environ.get(
20
22
  "Chrome/124.0.0.0 Safari/537.36",
21
23
  )
22
24
 
23
- # Optional: explicitly pin a JS runtime (useful if systemd PATH is odd).
24
- # Example: YTPDL_JS_RUNTIMES="deno:/usr/local/bin/deno"
25
- # Deno is enabled by default in yt-dlp; supplying a path is optional. :contentReference[oaicite:3]{index=3}
26
- JS_RUNTIMES = os.environ.get("YTPDL_JS_RUNTIMES", "").strip()
27
-
28
25
  FFMPEG_BIN = shutil.which("ffmpeg") or "ffmpeg"
29
26
  DEFAULT_OUT_DIR = os.environ.get("YTPDL_DOWNLOAD_DIR", "/root")
30
27
 
@@ -63,8 +60,13 @@ def _tail(out: str) -> str:
63
60
  return txt.strip()
64
61
 
65
62
 
63
+ def _is_youtube_url(url: str) -> bool:
64
+ u = (url or "").lower()
65
+ return any(h in u for h in ("youtube.com", "youtu.be", "youtube-nocookie.com"))
66
+
67
+
66
68
  # =========================
67
- # Environment checks
69
+ # Environment / Mullvad
68
70
  # =========================
69
71
  def validate_environment() -> None:
70
72
  if not os.path.exists(YTDLP_BIN):
@@ -73,12 +75,59 @@ def validate_environment() -> None:
73
75
  raise RuntimeError("ffmpeg not found on PATH")
74
76
 
75
77
 
78
+ def _mullvad_present() -> bool:
79
+ return shutil.which("mullvad") is not None
80
+
81
+
82
+ def mullvad_logged_in() -> bool:
83
+ if not _mullvad_present():
84
+ return False
85
+ res = subprocess.run(
86
+ ["mullvad", "account", "get"],
87
+ stdout=subprocess.PIPE,
88
+ stderr=subprocess.STDOUT,
89
+ text=True,
90
+ )
91
+ return "not logged in" not in (res.stdout or "").lower()
92
+
93
+
94
+ def require_mullvad_login() -> None:
95
+ if _mullvad_present() and not mullvad_logged_in():
96
+ raise RuntimeError("Mullvad not logged in. Run: mullvad account login <ACCOUNT>")
97
+
98
+
99
+ def mullvad_connect(location: Optional[str] = None) -> None:
100
+ if not _mullvad_present():
101
+ return
102
+ loc = (location or MULLVAD_LOCATION).strip()
103
+ _run_argv(["mullvad", "disconnect"], check=False)
104
+ if loc:
105
+ _run_argv(["mullvad", "relay", "set", "location", loc], check=False)
106
+ _run_argv(["mullvad", "connect"], check=False)
107
+
108
+
109
+ def mullvad_wait_connected(timeout: int = 20) -> bool:
110
+ if not _mullvad_present():
111
+ return True
112
+ for _ in range(timeout):
113
+ res = subprocess.run(
114
+ ["mullvad", "status"],
115
+ stdout=subprocess.PIPE,
116
+ stderr=subprocess.STDOUT,
117
+ text=True,
118
+ )
119
+ if "Connected" in (res.stdout or ""):
120
+ return True
121
+ time.sleep(1)
122
+ return False
123
+
124
+
76
125
  # =========================
77
126
  # yt-dlp helpers
78
127
  # =========================
79
128
  def _common_flags() -> List[str]:
80
129
  # --no-playlist prevents accidental channel/playlist pulls (and disk blowups)
81
- flags = [
130
+ return [
82
131
  "--no-playlist",
83
132
  "--retries", "10",
84
133
  "--fragment-retries", "10",
@@ -90,13 +139,6 @@ def _common_flags() -> List[str]:
90
139
  "--sleep-interval", "1",
91
140
  ]
92
141
 
93
- # If you want to hard-pin deno (or any runtime), set YTPDL_JS_RUNTIMES.
94
- # Example: deno:/usr/local/bin/deno :contentReference[oaicite:4]{index=4}
95
- if JS_RUNTIMES:
96
- flags.extend(["--js-runtimes", JS_RUNTIMES])
97
-
98
- return flags
99
-
100
142
 
101
143
  def _extract_final_path(stdout: str, out_dir: str) -> Optional[str]:
102
144
  """
@@ -215,6 +257,7 @@ def _download_with_format(
215
257
  ]
216
258
 
217
259
  if extract_mp3:
260
+ # Force audio extraction to MP3 (requires ffmpeg)
218
261
  argv.extend(["--extract-audio", "--audio-format", "mp3"])
219
262
 
220
263
  # Only force merge container when we actually want MP4 output.
@@ -266,44 +309,55 @@ def download_video(
266
309
 
267
310
  validate_environment()
268
311
 
269
- mode = (extension or "mp4").lower().strip()
312
+ require_mullvad_login()
313
+ mullvad_connect(MULLVAD_LOCATION)
314
+ if not mullvad_wait_connected():
315
+ raise RuntimeError("Mullvad connection failed")
270
316
 
271
- if mode == "mp3":
272
- return _download_with_format(
273
- url=url,
274
- out_dir=out_dir,
275
- fmt="bestaudio",
276
- merge_output_format=None,
277
- extract_mp3=True,
278
- )
279
-
280
- cap = int(resolution or 1080)
317
+ try:
318
+ mode = (extension or "mp4").lower().strip()
281
319
 
282
- if mode == "best":
283
- # Try best first (may produce webm/mkv/etc).
284
- try:
320
+ if mode == "mp3":
321
+ # bestaudio -> ffmpeg -> mp3 (post-processed by yt-dlp)
285
322
  return _download_with_format(
286
323
  url=url,
287
324
  out_dir=out_dir,
288
- fmt=_fmt_best(cap),
325
+ fmt="bestaudio",
289
326
  merge_output_format=None,
290
- extract_mp3=False,
291
- )
292
- except Exception:
293
- # If best fails, fall back to Apple-safe MP4.
294
- return _download_with_format(
295
- url=url,
296
- out_dir=out_dir,
297
- fmt=_fmt_mp4_apple_safe(cap),
298
- merge_output_format="mp4",
299
- extract_mp3=False,
327
+ extract_mp3=True,
300
328
  )
301
329
 
302
- # Default / "mp4" mode: force Apple-safe MP4 up to cap.
303
- return _download_with_format(
304
- url=url,
305
- out_dir=out_dir,
306
- fmt=_fmt_mp4_apple_safe(cap),
307
- merge_output_format="mp4",
308
- extract_mp3=False,
309
- )
330
+ cap = int(resolution or 1080)
331
+
332
+ if mode == "best":
333
+ # Try best first (may produce webm/mkv/etc).
334
+ try:
335
+ return _download_with_format(
336
+ url=url,
337
+ out_dir=out_dir,
338
+ fmt=_fmt_best(cap),
339
+ merge_output_format=None,
340
+ extract_mp3=False,
341
+ )
342
+ except Exception:
343
+ # If best fails for any reason, fall back to Apple-safe MP4.
344
+ return _download_with_format(
345
+ url=url,
346
+ out_dir=out_dir,
347
+ fmt=_fmt_mp4_apple_safe(cap),
348
+ merge_output_format="mp4",
349
+ extract_mp3=False,
350
+ )
351
+
352
+ # Default / "mp4" mode: force Apple-safe MP4 up to cap.
353
+ return _download_with_format(
354
+ url=url,
355
+ out_dir=out_dir,
356
+ fmt=_fmt_mp4_apple_safe(cap),
357
+ merge_output_format="mp4",
358
+ extract_mp3=False,
359
+ )
360
+
361
+ finally:
362
+ if _mullvad_present():
363
+ _run_argv(["mullvad", "disconnect"], check=False)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ytp-dl
3
- Version: 0.6.2
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
@@ -34,35 +33,37 @@ Dynamic: summary
34
33
 
35
34
  # ytp-dl
36
35
 
37
- > A lightweight YouTube downloader with an HTTP API
36
+ > A lightweight YouTube downloader with Mullvad VPN integration and HTTP API
38
37
 
39
38
  [![PyPI version](https://img.shields.io/pypi/v/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
40
39
  [![Python Support](https://img.shields.io/pypi/pyversions/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
41
40
  [![License](https://img.shields.io/pypi/l/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
42
41
  [![Downloads](https://img.shields.io/pypi/dm/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
43
42
 
44
- **ytp-dl** is a privacy-focused YouTube downloader that exposes a simple HTTP API for automated downloads.
43
+ **ytp-dl** is a privacy-focused YouTube downloader that automatically routes downloads through Mullvad VPN via an HTTP API.
45
44
 
46
45
  ---
47
46
 
48
- ## Features
47
+ ## Features
49
48
 
50
- * Smart quality selection (prefers 1080p H.264 + AAC when available; no transcoding needed)
51
- * Audio downloads (extract audio as MP3)
52
- * HTTP API (Flask-based API with concurrency controls)
53
- * VPS-ready (includes an automated installer script for Ubuntu)
49
+ * 🔒 **Privacy First** Automatically connects/disconnects Mullvad VPN per download
50
+ * 🎥 **Smart Quality Selection** — Prefers 1080p H.264 + AAC (no transcoding needed)
51
+ * 🎵 **Audio Downloads** Extract audio as MP3
52
+ * 🚀 **HTTP API** Simple Flask-based API with concurrency controls
53
+ * ⚡ **VPS Ready** — Includes automated installer script for Ubuntu
54
54
 
55
55
  ---
56
56
 
57
- ## Installation
57
+ ## 📦 Installation
58
58
 
59
59
  ```bash
60
- pip install ytp-dl==0.6.2 yt-dlp[default]
60
+ pip install ytp-dl==0.6.4 yt-dlp[default]
61
61
  ```
62
62
 
63
63
  **Requirements:**
64
64
 
65
65
  * Linux operating system (tested on Ubuntu 24.04/25.04)
66
+ * [Mullvad CLI](https://mullvad.net/en/download/vpn/linux) installed and configured
66
67
  * FFmpeg (for handling audio + video)
67
68
  * Deno (system-wide, required by yt-dlp for modern YouTube extraction)
68
69
  * Python 3.8+
@@ -73,7 +74,7 @@ Notes:
73
74
 
74
75
  ---
75
76
 
76
- ## Using Your VPS
77
+ ## 🎯 Using Your VPS
77
78
 
78
79
  Once your VPS is running, you can download videos using simple HTTP requests:
79
80
 
@@ -138,26 +139,34 @@ else:
138
139
 
139
140
  ---
140
141
 
141
- ## Configuration
142
+ ## ⚙️ Configuration
142
143
 
143
144
  ### Installation Script Variables
144
145
 
145
146
  These environment variables configure the VPS installation (they can be overridden when running the script):
146
147
 
147
- | Variable | Description | Default |
148
- | ---------------------- | ------------------------------------ | ------------------ |
149
- | `PORT` | API server port | `5000` |
150
- | `APP_DIR` | Installation directory | `/opt/ytp-dl` |
151
- | `YTPDL_MAX_CONCURRENT` | Max simultaneous downloads (API cap) | `1` |
148
+ | Variable | Description | Default |
149
+ | ------------------------ | --------------------------------------- | --------------------- |
150
+ | `PORT` | API server port | `5000` |
151
+ | `APP_DIR` | Installation directory | `/opt/yt-dlp-mullvad` |
152
+ | `MV_ACCOUNT` | Mullvad account number | your mullvad id |
153
+ | `YTPDL_MAX_CONCURRENT` | Max simultaneous downloads (API cap) | `1` |
154
+ | `YTPDL_MULLVAD_LOCATION` | Mullvad relay location code (e.g. `us`) | `us` |
155
+
156
+ Notes:
157
+
158
+ * If `MV_ACCOUNT` is set, the installer attempts `mullvad account login <MV_ACCOUNT>` once.
159
+ * If `MV_ACCOUNT` is left empty, the script skips login and assumes Mullvad is already configured.
152
160
 
153
161
  ### Runtime Environment Variables
154
162
 
155
163
  After installation, these variables control the API behavior. They are set in `/etc/default/ytp-dl-api` and can be edited manually:
156
164
 
157
- | Variable | Description | Default |
158
- | ---------------------- | ------------------------------- | --------------------- |
159
- | `YTPDL_MAX_CONCURRENT` | Maximum concurrent downloads | `1` |
160
- | `YTPDL_VENV` | Path to virtualenv for ytp-dl | `/opt/ytp-dl/venv` |
165
+ | Variable | Description | Default |
166
+ | ------------------------ | ----------------------------- | -------------------------- |
167
+ | `YTPDL_MAX_CONCURRENT` | Maximum concurrent downloads | `1` |
168
+ | `YTPDL_MULLVAD_LOCATION` | Mullvad relay location code | `us` |
169
+ | `YTPDL_VENV` | Path to virtualenv for ytp-dl | `/opt/yt-dlp-mullvad/venv` |
161
170
 
162
171
  To change configuration after installation:
163
172
 
@@ -168,7 +177,7 @@ sudo systemctl restart ytp-dl-api
168
177
 
169
178
  ---
170
179
 
171
- ## Managing Your VPS Service
180
+ ## 🔧 Managing Your VPS Service
172
181
 
173
182
  ### View Service Status
174
183
 
@@ -197,7 +206,7 @@ sudo systemctl start ytp-dl-api
197
206
 
198
207
  ---
199
208
 
200
- ## API Reference
209
+ ## 📋 API Reference
201
210
 
202
211
  ### POST `/api/download`
203
212
 
@@ -232,45 +241,66 @@ sudo systemctl start ytp-dl-api
232
241
 
233
242
  ---
234
243
 
235
- ## VPS Deployment
244
+ ## 🖥️ VPS Deployment
236
245
 
237
246
  **What it does:**
238
247
 
239
- * Installs Python and FFmpeg
240
- * Installs Deno system-wide (required by yt-dlp for modern YouTube extraction)
241
- * Creates a virtualenv at `/opt/ytp-dl/venv`
242
- * Installs `ytp-dl==0.6.2` + `yt-dlp[default]` into the virtualenv
243
- * Sets up a systemd service on port 5000
244
- * Configures Gunicorn with gevent workers
248
+ * Installs Python, FFmpeg, and Mullvad CLI
249
+ * Installs Deno system-wide (required by yt-dlp for modern YouTube extraction)
250
+ * Creates virtualenv at `/opt/yt-dlp-mullvad/venv`
251
+ * Installs `ytp-dl==0.6.4` + `yt-dlp[default]` into the virtualenv
252
+ * Sets up systemd service on port 5000
253
+ * Configures Gunicorn with gthread (threaded) workers
245
254
 
246
255
  ```bash
247
256
  #!/usr/bin/env bash
248
- # VPS_Installation.sh - Minimal Ubuntu 24.04/25.04 setup for ytp-dl API
257
+ # VPS_Installation.sh - Minimal Ubuntu 24.04/25.04 setup for ytp-dl API + Mullvad
249
258
  #
250
259
  # What this does:
251
- # - Installs Python + ffmpeg
260
+ # - Installs Python, ffmpeg, Mullvad CLI
252
261
  # - Installs Deno system-wide (JS runtime required for modern YouTube extraction via yt-dlp)
253
- # - Creates a virtualenv at /opt/ytp-dl/venv
254
- # - Installs ytp-dl==0.6.2 + yt-dlp[default] + gunicorn + gevent in that venv
262
+ # - Creates a virtualenv at /opt/yt-dlp-mullvad/venv
263
+ # - Installs ytp-dl==0.6.4 + yt-dlp[default] + gunicorn + gevent in that venv
255
264
  # - Creates a simple systemd service ytp-dl-api.service on port 5000
265
+ #
266
+ # Mullvad connect/disconnect is handled per-job by downloader.py.
256
267
 
257
268
  set -euo pipefail
258
269
 
259
270
  ### --- Tunables -------------------------------------------------------------
260
- PORT="${PORT:-5000}" # API listen port
261
- APP_DIR="${APP_DIR:-/opt/ytp-dl}" # app/venv root
262
- VENV_DIR="${VENV_DIR:-${APP_DIR}/venv}" # python venv
263
-
264
- YTPDL_MAX_CONCURRENT="${YTPDL_MAX_CONCURRENT:-1}" # API concurrency cap
271
+ PORT="${PORT:-5000}" # API listen port
272
+ APP_DIR="${APP_DIR:-/opt/yt-dlp-mullvad}" # app/venv root
273
+ VENV_DIR="${VENV_DIR:-${APP_DIR}/venv}" # python venv
274
+
275
+ MV_ACCOUNT="${MV_ACCOUNT:-}" # Mullvad account (put number after -)
276
+ YTPDL_MAX_CONCURRENT="${YTPDL_MAX_CONCURRENT:-1}" # API concurrency cap
277
+ YTPDL_MULLVAD_LOCATION="${YTPDL_MULLVAD_LOCATION:-us}" # default Mullvad relay hint
278
+ GUNICORN_THREADS="${GUNICORN_THREADS:-4}" # keep API responsive on tiny VPS
265
279
  ### -------------------------------------------------------------------------
266
280
 
267
281
  [[ "${EUID}" -eq 0 ]] || { echo "Please run as root"; exit 1; }
268
282
  export DEBIAN_FRONTEND=noninteractive
269
283
 
270
- echo "==> 1) Base packages"
284
+ echo "==> 1) Base packages & Mullvad CLI"
271
285
  apt-get update
272
- apt-get install -yq --no-install-recommends \
273
- python3-venv python3-pip curl ffmpeg ca-certificates unzip
286
+ apt-get install -yq --no-install-recommends python3-venv python3-pip curl ffmpeg ca-certificates unzip
287
+
288
+ if ! command -v mullvad >/dev/null 2>&1; then
289
+ curl -fsSLo /tmp/mullvad.deb https://mullvad.net/download/app/deb/latest/
290
+ apt-get install -y /tmp/mullvad.deb
291
+ fi
292
+
293
+ if [[ -n "${MV_ACCOUNT}" ]]; then
294
+ echo "Logging into Mullvad account (if not already logged in)..."
295
+ mullvad account login "${MV_ACCOUNT}" || true
296
+ fi
297
+
298
+ mullvad status || true
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
274
304
 
275
305
  echo "==> 1.5) Install Deno (system-wide, for yt-dlp YouTube extraction)"
276
306
  # Install into /usr/local/bin/deno so systemd PATH can see it.
@@ -285,19 +315,20 @@ mkdir -p "${APP_DIR}"
285
315
  python3 -m venv "${VENV_DIR}"
286
316
  source "${VENV_DIR}/bin/activate"
287
317
  pip install --upgrade pip
288
- pip install "ytp-dl==0.6.2" "yt-dlp[default]" gunicorn gevent
318
+ pip install "ytp-dl==0.6.4" "yt-dlp[default]" gunicorn
289
319
  deactivate
290
320
 
291
321
  echo "==> 3) API environment file (/etc/default/ytp-dl-api)"
292
322
  tee /etc/default/ytp-dl-api >/dev/null <<EOF
293
323
  YTPDL_MAX_CONCURRENT=${YTPDL_MAX_CONCURRENT}
324
+ YTPDL_MULLVAD_LOCATION=${YTPDL_MULLVAD_LOCATION}
294
325
  YTPDL_VENV=${VENV_DIR}
295
326
  EOF
296
327
 
297
328
  echo "==> 4) Gunicorn systemd service (ytp-dl-api.service on :${PORT})"
298
329
  tee /etc/systemd/system/ytp-dl-api.service >/dev/null <<EOF
299
330
  [Unit]
300
- Description=Gunicorn for ytp-dl API (minimal)
331
+ Description=Gunicorn for ytp-dl Mullvad API (minimal)
301
332
  After=network-online.target
302
333
  Wants=network-online.target
303
334
 
@@ -308,9 +339,7 @@ EnvironmentFile=/etc/default/ytp-dl-api
308
339
  Environment=VIRTUAL_ENV=${VENV_DIR}
309
340
  Environment=PATH=${VENV_DIR}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
310
341
 
311
- ExecStart=${VENV_DIR}/bin/gunicorn -k gevent -w 1 \
312
- --worker-connections 200 --timeout 0 --graceful-timeout 15 --keep-alive 20 \
313
- --bind 0.0.0.0:${PORT} scripts.api:app
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
314
343
 
315
344
  Restart=always
316
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,,
@@ -1,8 +0,0 @@
1
- scripts/__init__.py,sha256=EbAplfCcyLD3Q_9sxemm6owCc5_UJv53vmlxy810p2s,152
2
- scripts/api.py,sha256=K-4iwgj6W4Gx8y3mzBe_CP4Ua9Ky9quESOqcw6BpNv0,3713
3
- scripts/downloader.py,sha256=NGW1TmPmjHCTlN1uUNYwhSBm1c7UotkF8Pn9Q0dxqLk,9227
4
- ytp_dl-0.6.2.dist-info/METADATA,sha256=7U33Y4moFmjs1xivWbR3_xGe5imTbGFlecFx6BI63os,9754
5
- ytp_dl-0.6.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
6
- ytp_dl-0.6.2.dist-info/entry_points.txt,sha256=QqjqZZAEt3Y7RGrleqZ312sjjboUpbMLdo7qFxuCH30,48
7
- ytp_dl-0.6.2.dist-info/top_level.txt,sha256=rmzd5mewlrJy4sT608KPib7sM7edoY75AeqJeY3SPB4,8
8
- ytp_dl-0.6.2.dist-info/RECORD,,
File without changes