huuyfytryr commited on
Commit
8c4d4e3
·
1 Parent(s): b490bff

Fix Tor start permissions, add nodejs to Docker for pytubefix signature, and fetch Invidious dynamically

Browse files
Files changed (3) hide show
  1. Dockerfile +4 -3
  2. app.py +75 -36
  3. start.sh +32 -0
Dockerfile CHANGED
@@ -13,6 +13,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
13
  fonts-liberation \
14
  curl \
15
  tor \
 
16
  && rm -rf /var/lib/apt/lists/*
17
 
18
  # ImageMagick policy fix — allow reading/writing all file types (needed by moviepy)
@@ -41,6 +42,6 @@ ENV HOST=0.0.0.0
41
  # Expose port
42
  EXPOSE 7860
43
 
44
- # Run with gunicorn 1 worker, 4 threads, 300s timeout for long video jobs
45
- # Start tor in background first so yt-dlp can use it as SOCKS5 proxy fallback
46
- CMD ["sh", "-c", "tor --RunAsDaemon 1 --SocksPort 9050 && sleep 3 && gunicorn --bind 0.0.0.0:7860 --workers 1 --threads 4 --timeout 300 --access-logfile - app:app"]
 
13
  fonts-liberation \
14
  curl \
15
  tor \
16
+ nodejs \
17
  && rm -rf /var/lib/apt/lists/*
18
 
19
  # ImageMagick policy fix — allow reading/writing all file types (needed by moviepy)
 
42
  # Expose port
43
  EXPOSE 7860
44
 
45
+ # Run startup script that configures and starts Tor and Gunicorn
46
+ RUN chmod +x start.sh
47
+ CMD ["/app/start.sh"]
app.py CHANGED
@@ -239,36 +239,53 @@ def download_via_pytubefix(url, output_path):
239
  try:
240
  from pytubefix import YouTube
241
  from pytubefix.cli import on_progress
242
- print(f'[pytubefix] Trying {url} ...')
243
- yt = YouTube(url, on_progress_callback=on_progress,
244
- use_oauth=False, allow_oauth_cache=False)
245
- # Try progressive (combined audio+video) MP4 first
246
- stream = (
247
- yt.streams
248
- .filter(progressive=True, file_extension='mp4')
249
- .order_by('resolution')
250
- .last() # highest resolution
251
- )
252
- if not stream:
253
- # Fall back to any MP4
254
- stream = yt.streams.filter(file_extension='mp4').first()
255
- if not stream:
256
- print('[pytubefix] No suitable stream found')
257
- return False
258
- print(f'[pytubefix] Downloading {stream.resolution} stream ...')
259
- import tempfile, shutil
260
- # pytubefix downloads to a directory, so use a temp dir
261
- tmp_dir = tempfile.mkdtemp()
262
- try:
263
- dl_path = stream.download(output_path=tmp_dir, filename='video.mp4')
264
- if dl_path and os.path.exists(dl_path) and os.path.getsize(dl_path) > 0:
265
- shutil.move(dl_path, output_path)
266
- print(f'[pytubefix] Success! {os.path.getsize(output_path)//1024} KB')
267
- return True
268
- finally:
269
- shutil.rmtree(tmp_dir, ignore_errors=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  except Exception as e:
271
- print(f'[pytubefix] Error: {e}')
272
  return False
273
 
274
 
@@ -283,17 +300,39 @@ def download_via_invidious(url, output_path):
283
  print('[invidious] Could not extract video ID')
284
  return False
285
 
 
286
  instances = [
287
  'https://inv.nadeko.net',
288
- 'https://invidious.fdn.fr',
289
- 'https://invidious.privacydev.net',
290
- 'https://iv.datura.network',
291
- 'https://invidious.projectsegfau.lt',
292
- 'https://yt.cdaut.de',
293
  'https://invidious.nerdvpn.de',
294
- 'https://vid.puffyan.us',
 
 
295
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  for instance in instances:
 
297
  try:
298
  print(f'[invidious] Querying {instance} for {vid_id} ...')
299
  api = f'{instance}/api/v1/videos/{vid_id}?fields=formatStreams'
@@ -630,7 +669,7 @@ def clip_youtube_video():
630
  '-f', 'bestvideo[height<=480][ext=mp4]+bestaudio/best[height<=480]/best',
631
  '--merge-output-format', 'mp4',
632
  '--no-check-certificates',
633
- '--proxy', 'socks5://127.0.0.1:9050', # Tor SOCKS5
634
  '--socket-timeout', '20',
635
  '--retries', '1',
636
  '-o', raw_download_path
 
239
  try:
240
  from pytubefix import YouTube
241
  from pytubefix.cli import on_progress
242
+
243
+ # Try multiple clients to find one that YouTube doesn't block or prompt on.
244
+ # 'WEB' triggers node-based signature generation if node is available.
245
+ # 'ANDROID' is the mobile client.
246
+ clients = ['WEB', 'ANDROID', 'ANDROID_VR']
247
+
248
+ for client in clients:
249
+ try:
250
+ print(f'[pytubefix] Trying client={client} for {url} ...')
251
+ yt = YouTube(
252
+ url,
253
+ client=client,
254
+ on_progress_callback=on_progress,
255
+ use_oauth=False,
256
+ allow_oauth_cache=False,
257
+ use_po_token=False # Do NOT use True, it prompts interactively in terminal
258
+ )
259
+ # Try progressive (combined audio+video) MP4 first
260
+ stream = (
261
+ yt.streams
262
+ .filter(progressive=True, file_extension='mp4')
263
+ .order_by('resolution')
264
+ .last() # highest resolution
265
+ )
266
+ if not stream:
267
+ # Fall back to any MP4
268
+ stream = yt.streams.filter(file_extension='mp4').first()
269
+ if not stream:
270
+ print(f'[pytubefix] No suitable stream found with client={client}')
271
+ continue
272
+ print(f'[pytubefix] Downloading {stream.resolution} stream with client={client} ...')
273
+ import tempfile, shutil
274
+ # pytubefix downloads to a directory, so use a temp dir
275
+ tmp_dir = tempfile.mkdtemp()
276
+ try:
277
+ dl_path = stream.download(output_path=tmp_dir, filename='video.mp4')
278
+ if dl_path and os.path.exists(dl_path) and os.path.getsize(dl_path) > 0:
279
+ shutil.move(dl_path, output_path)
280
+ print(f'[pytubefix] Success with client={client}! {os.path.getsize(output_path)//1024} KB')
281
+ return True
282
+ finally:
283
+ shutil.rmtree(tmp_dir, ignore_errors=True)
284
+ except Exception as client_err:
285
+ print(f'[pytubefix] Client {client} failed: {client_err}')
286
+
287
  except Exception as e:
288
+ print(f'[pytubefix] General Error: {e}')
289
  return False
290
 
291
 
 
300
  print('[invidious] Could not extract video ID')
301
  return False
302
 
303
+ # Default fallback list
304
  instances = [
305
  'https://inv.nadeko.net',
 
 
 
 
 
306
  'https://invidious.nerdvpn.de',
307
+ 'https://invidious.f5.si',
308
+ 'https://yt.chocolatemoo53.com',
309
+ 'https://inv.thepixora.com',
310
  ]
311
+
312
+ try:
313
+ print('[invidious] Querying public instances API for live hosts ...')
314
+ api_resp = _req.get('https://api.invidious.io/instances.json', timeout=10)
315
+ if api_resp.status_code == 200:
316
+ live_instances = []
317
+ for item in api_resp.json():
318
+ try:
319
+ info = item[1]
320
+ monitor = info.get('monitor')
321
+ if monitor and not monitor.get('down', True) and monitor.get('last_status') == 200:
322
+ uri = info.get('uri')
323
+ if uri:
324
+ live_instances.append(uri.rstrip('/'))
325
+ except Exception:
326
+ pass
327
+ if live_instances:
328
+ print(f'[invidious] Found {len(live_instances)} live healthy instances.')
329
+ # Prioritize live instances, appending defaults just in case
330
+ instances = live_instances + [inst for inst in instances if inst not in live_instances]
331
+ except Exception as api_err:
332
+ print(f'[invidious] Failed to fetch live instances: {api_err}. Using defaults.')
333
+
334
  for instance in instances:
335
+ instance = instance.rstrip('/')
336
  try:
337
  print(f'[invidious] Querying {instance} for {vid_id} ...')
338
  api = f'{instance}/api/v1/videos/{vid_id}?fields=formatStreams'
 
669
  '-f', 'bestvideo[height<=480][ext=mp4]+bestaudio/best[height<=480]/best',
670
  '--merge-output-format', 'mp4',
671
  '--no-check-certificates',
672
+ '--proxy', 'socks5h://127.0.0.1:9050', # Tor SOCKS5 (with DNS resolved on proxy)
673
  '--socket-timeout', '20',
674
  '--retries', '1',
675
  '-o', raw_download_path
start.sh ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Start Tor daemon with custom data directory and PID file (writable by UID 1000)
3
+ echo "[startup] Starting Tor daemon..."
4
+ mkdir -p /tmp/tor-data
5
+ chmod 700 /tmp/tor-data
6
+ tor --RunAsDaemon 1 --SocksPort 127.0.0.1:9050 --DataDirectory /tmp/tor-data --PidFile /tmp/tor.pid --Log "notice file /tmp/tor.log"
7
+
8
+ # Wait for Tor to bootstrap
9
+ echo "[startup] Checking Tor connection..."
10
+ BOOTSTRAPPED=false
11
+ for i in {1..20}; do
12
+ if curl --connect-timeout 2 -sI -x socks5h://127.0.0.1:9050 https://www.google.com >/dev/null; then
13
+ echo "[startup] Tor proxy is ready and routing traffic!"
14
+ BOOTSTRAPPED=true
15
+ break
16
+ fi
17
+ echo "[startup] Tor is bootstrapping, waiting..."
18
+ sleep 2
19
+ done
20
+
21
+ if [ "$BOOTSTRAPPED" = false ]; then
22
+ echo "[startup] Tor failed to bootstrap within 40 seconds."
23
+ fi
24
+
25
+ # Print Tor log for debugging
26
+ echo "=== Tor Log Start ==="
27
+ cat /tmp/tor.log 2>/dev/null || echo "No Tor log found"
28
+ echo "=== Tor Log End ==="
29
+
30
+ # Start Gunicorn application
31
+ echo "[startup] Starting Gunicorn server..."
32
+ exec gunicorn --bind 0.0.0.0:7860 --workers 1 --threads 4 --timeout 300 --access-logfile - app:app