thomson99 commited on
Commit
9a404b4
·
verified ·
1 Parent(s): 8da478c

Upload 3 files

Browse files
Files changed (2) hide show
  1. app.py +90 -811
  2. requirements.txt +2 -6
app.py CHANGED
@@ -1,33 +1,14 @@
1
  import gradio as gr
2
  import yt_dlp
3
  import aiohttp
4
- from aiohttp_proxy import ProxyConnector
5
  import asyncio
6
  import os
7
  import tempfile
8
- from urllib.parse import urlparse, parse_qs, urlencode
9
  import time
10
  import mimetypes
11
  import concurrent.futures
12
  import math
13
- import json
14
- from datetime import datetime
15
- import random
16
- from bs4 import BeautifulSoup
17
- import re
18
- import urllib3
19
- import uuid
20
- import ssl
21
- import undetected_chromedriver as uc
22
- from selenium.webdriver.common.by import By
23
- from selenium.webdriver.support.ui import WebDriverWait
24
- from selenium.webdriver.support import expected_conditions as EC
25
- import threading
26
- from fake_useragent import UserAgent
27
- import requests
28
-
29
- # تعطيل تحذيرات SSL
30
- urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
31
 
32
  class FastDownloader:
33
  def __init__(self):
@@ -35,612 +16,22 @@ class FastDownloader:
35
  'format': 'best',
36
  'quiet': True,
37
  'no_warnings': True,
38
- 'concurrent_fragments': 10,
39
- 'nocheckcertificate': True,
40
  }
41
- self.chunk_size = 1024 * 1024 * 5
42
- self.max_workers = 5
43
- self.archive_file = "download_archive.json"
44
-
45
- # إنشاء UserAgent عشوائي
46
- self.ua = UserAgent()
47
-
48
- # إعداد SSL context
49
- self.ssl_context = ssl.create_default_context()
50
- self.ssl_context.check_hostname = False
51
- self.ssl_context.verify_mode = ssl.CERT_NONE
52
-
53
- # تهيئة المتصفح
54
- self.driver = None
55
- self.driver_lock = threading.Lock()
56
-
57
- # تهيئة الأرشيف
58
- self.load_archive()
59
-
60
- # تهيئة قائمة البروكسيات
61
- self.proxies = []
62
- self.update_proxies()
63
-
64
- def update_proxies(self):
65
- """تحديث قائمة البروكسيات"""
66
- fallback_proxies = [
67
- 'http://51.159.115.233:3128',
68
- 'http://20.111.54.16:8123',
69
- 'http://51.159.115.233:3128',
70
- 'http://20.206.106.192:80',
71
- 'http://51.79.50.31:9300'
72
- ]
73
-
74
- try:
75
- # محاولة الحصول على بروكسيات من مصدر خارجي
76
- response = requests.get(
77
- 'https://proxylist.geonode.com/api/proxy-list',
78
- params={
79
- 'limit': '100',
80
- 'page': '1',
81
- 'sort_by': 'lastChecked',
82
- 'sort_type': 'desc',
83
- 'protocols': 'http,https',
84
- 'anonymityLevel': 'elite,anonymous'
85
- },
86
- timeout=10
87
- )
88
-
89
- if response.status_code == 200:
90
- data = response.json()
91
- if isinstance(data, dict) and 'data' in data:
92
- for proxy in data['data']:
93
- protocol = proxy.get('protocols', ['http'])[0]
94
- ip = proxy.get('ip')
95
- port = proxy.get('port')
96
- if ip and port:
97
- self.proxies.append(f'{protocol}://{ip}:{port}')
98
-
99
- # إضافة البروكسيات الاحتياطية
100
- self.proxies.extend(fallback_proxies)
101
-
102
- except Exception as e:
103
- print(f"Error updating proxies: {str(e)}")
104
- # استخدام البروكسيات الاحتياطية في حالة الفشل
105
- self.proxies = fallback_proxies.copy()
106
-
107
- def get_random_proxy(self):
108
- """الحصول على بروكسي عشوائي"""
109
- # تحديث البروكسيات إذا كانت القائمة فارغة
110
- if not self.proxies:
111
- self.update_proxies()
112
-
113
- # إرجاع بروكسي عشوائي إذا كانت هناك بروكسيات متاحة
114
- return random.choice(self.proxies) if self.proxies else None
115
-
116
- def get_headers(self, url):
117
- """إنشاء ترويسات عشوائية"""
118
- parsed_url = urlparse(url)
119
- domain = parsed_url.netloc.lower()
120
-
121
- headers = {
122
- 'User-Agent': self.ua.random,
123
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
124
- 'Accept-Language': 'en-US,en;q=0.9,ar;q=0.8',
125
- 'Accept-Encoding': 'gzip, deflate, br',
126
- 'Connection': 'keep-alive',
127
- 'Upgrade-Insecure-Requests': '1',
128
- 'Cache-Control': 'max-age=0',
129
- 'TE': 'Trailers',
130
- 'DNT': '1',
131
- 'Sec-GPC': '1',
132
- 'Pragma': 'no-cache'
133
- }
134
-
135
- if 'milocdn' in domain or 'anime4up' in domain:
136
- headers.update({
137
- 'Referer': f"https://{domain}/",
138
- 'Origin': f"https://{domain}",
139
- 'Host': domain,
140
- 'Sec-Fetch-Dest': 'document',
141
- 'Sec-Fetch-Mode': 'navigate',
142
- 'Sec-Fetch-Site': 'same-origin',
143
- 'Sec-Fetch-User': '?1',
144
- 'X-Requested-With': f'XMLHttpRequest-{random.randint(1000, 9999)}',
145
- 'X-Forwarded-For': f'192.168.{random.randint(1, 255)}.{random.randint(1, 255)}'
146
- })
147
-
148
- return headers
149
-
150
- def get_cookies(self, url):
151
- """إنشاء كوكيز عشوائية"""
152
- domain = urlparse(url).netloc
153
- timestamp = int(time.time())
154
- cookies = {
155
- 'cf_clearance': f"{uuid.uuid4().hex}",
156
- '_ga': f"GA1.2.{random.randint(1000000, 9999999)}.{timestamp}",
157
- '_gid': f"GA1.2.{random.randint(1000000, 9999999)}.{timestamp}",
158
- 'PHPSESSID': f"{uuid.uuid4().hex}",
159
- 'visited': '1',
160
- 'last_visit': str(timestamp),
161
- 'session_depth': str(random.randint(1, 5)),
162
- 'user_preferences': 'theme=light',
163
- 'timezone_offset': '-180',
164
- '_fbp': f"fb.1.{timestamp}.{random.randint(1000000, 9999999)}",
165
- '_ym_uid': f"{random.randint(1000000000, 9999999999)}",
166
- '_ym_d': str(timestamp),
167
- 'wordpress_test_cookie': 'WP Cookie check',
168
- 'wp-settings-time-1': str(timestamp),
169
- }
170
- return cookies
171
-
172
- def init_browser(self):
173
- """تهيئة المتصفح"""
174
- try:
175
- options = uc.ChromeOptions()
176
- options.add_argument('--headless')
177
- options.add_argument('--no-sandbox')
178
- options.add_argument('--disable-dev-shm-usage')
179
- options.add_argument('--disable-gpu')
180
- options.add_argument(f'--user-agent={self.ua.random}')
181
-
182
- # Set Chrome binary location for different platforms
183
- if os.name == 'nt': # Windows
184
- chrome_paths = [
185
- r"C:\Program Files\Google\Chrome\Application\chrome.exe",
186
- r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
187
- ]
188
- for path in chrome_paths:
189
- if os.path.exists(path):
190
- options.binary_location = path
191
- break
192
- else: # Linux/Unix
193
- chrome_paths = [
194
- "/usr/bin/google-chrome",
195
- "/usr/bin/chromium-browser",
196
- ]
197
- for path in chrome_paths:
198
- if os.path.exists(path):
199
- options.binary_location = path
200
- break
201
-
202
- with self.driver_lock:
203
- if not self.driver:
204
- self.driver = uc.Chrome(options=options)
205
- except Exception as e:
206
- print(f"Warning: Browser initialization failed: {str(e)}")
207
- self.driver = None
208
-
209
- def close_browser(self):
210
- """إغلاق المتصفح"""
211
- with self.driver_lock:
212
- if self.driver:
213
- try:
214
- self.driver.quit()
215
- except:
216
- pass
217
- self.driver = None
218
-
219
- async def extract_direct_link(self, url):
220
- """استخراج رابط التحميل المباشر"""
221
- try:
222
- # Parse URL components
223
- parsed = urlparse(url)
224
- query_params = parse_qs(parsed.query)
225
-
226
- # If URL already has a token, try to refresh it
227
- if 't' in query_params:
228
- current_time = int(time.time())
229
- # Generate new token parameters
230
- new_params = {
231
- 't': query_params.get('t', [''])[0],
232
- 's': str(current_time),
233
- 'e': '86400',
234
- 'f': query_params.get('f', [''])[0],
235
- 'sp': '1000',
236
- 'i': '0.0'
237
- }
238
-
239
- # Reconstruct URL with new parameters
240
- base_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
241
- new_url = f"{base_url}?{urlencode(new_params)}"
242
- return new_url
243
-
244
- return url
245
- except Exception:
246
- return url
247
-
248
- async def download_with_ytdlp(self, url, browser_cookies=None, use_proxy=False):
249
- """استخدام yt-dlp للتحميل"""
250
- try:
251
- temp_dir = tempfile.mkdtemp()
252
-
253
- # Try to refresh/extract direct link
254
- url = await self.extract_direct_link(url)
255
-
256
- # Enhanced headers with streaming-specific settings
257
- headers = {
258
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
259
- 'Accept': '*/*',
260
- 'Accept-Language': 'en-US,en;q=0.9',
261
- 'Accept-Encoding': 'gzip, deflate, br',
262
- 'Connection': 'keep-alive',
263
- 'Range': 'bytes=0-',
264
- 'Referer': url,
265
- 'Origin': urlparse(url).scheme + '://' + urlparse(url).netloc,
266
- 'Sec-Fetch-Dest': 'video',
267
- 'Sec-Fetch-Mode': 'cors',
268
- 'Sec-Fetch-Site': 'same-origin',
269
- }
270
-
271
- # Get cookies
272
- if not browser_cookies:
273
- browser_cookies = self.get_cookies(url)
274
-
275
- # Custom format selection
276
- format_selector = lambda ctx: [{
277
- 'format_id': 'best',
278
- 'ext': 'mp4',
279
- 'protocol': 'https'
280
- }]
281
-
282
- ydl_opts = {
283
- 'format': 'best',
284
- 'format_sort': ['res:1080', 'ext:mp4:m4a'],
285
- 'outtmpl': os.path.join(temp_dir, '%(title)s.%(ext)s'),
286
- 'quiet': True,
287
- 'no_warnings': True,
288
- 'nocheckcertificate': True,
289
- 'http_headers': headers,
290
- 'socket_timeout': 30,
291
- 'retries': 3,
292
- 'fragment_retries': 3,
293
- 'retry_sleep_functions': {'fragment': lambda n: min(5 * (n + 1), 30)},
294
- 'format_selector': format_selector,
295
- 'concurrent_fragment_downloads': 10,
296
- 'file_access_retries': 5,
297
- 'hls_prefer_native': True,
298
- 'hls_split_discontinuity': True,
299
- 'downloader': 'native',
300
- 'downloader_options': {
301
- 'http_chunk_size': 10485760, # 10MB
302
- }
303
- }
304
-
305
- # Prepare cookies
306
- cookies_file = os.path.join(temp_dir, 'cookies.txt')
307
- with open(cookies_file, 'w', encoding='utf-8') as f:
308
- domain = f".{urlparse(url).netloc}"
309
- expire_time = int(time.time()) + 86400 * 7 # 7 days
310
- for k, v in browser_cookies.items():
311
- f.write(f"{domain}\tTRUE\t/\tFALSE\t{expire_time}\t{k}\t{v}\n")
312
- ydl_opts['cookies'] = cookies_file
313
-
314
- # First try direct download
315
- try:
316
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
317
- info = ydl.extract_info(url, download=True)
318
- filename = ydl.prepare_filename(info)
319
- except Exception as e:
320
- if not use_proxy:
321
- raise
322
-
323
- # If direct download fails, try with proxies
324
- proxies = []
325
- for _ in range(3):
326
- proxy = self.get_random_proxy()
327
- if proxy and proxy not in proxies:
328
- try:
329
- async with aiohttp.ClientSession() as session:
330
- async with session.head('https://www.google.com',
331
- proxy=proxy,
332
- timeout=5,
333
- ssl=False) as resp:
334
- if resp.status == 200:
335
- proxies.append(proxy)
336
- except Exception:
337
- continue
338
-
339
- if not proxies:
340
- # Try one last time with direct connection
341
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
342
- info = ydl.extract_info(url, download=True)
343
- filename = ydl.prepare_filename(info)
344
- else:
345
- # Try each working proxy
346
- last_error = None
347
- for proxy in proxies:
348
- try:
349
- ydl_opts['proxy'] = proxy
350
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
351
- info = ydl.extract_info(url, download=True)
352
- filename = ydl.prepare_filename(info)
353
- break
354
- except Exception as e:
355
- last_error = e
356
- continue
357
- else:
358
- # If all proxies fail, try one last time without proxy
359
- ydl_opts['proxy'] = None
360
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
361
- info = ydl.extract_info(url, download=True)
362
- filename = ydl.prepare_filename(info)
363
-
364
- file_info = {
365
- "name": os.path.basename(filename),
366
- "path": filename,
367
- "mime_type": self.get_mime_type(filename),
368
- "size": os.path.getsize(filename)
369
- }
370
-
371
- archive_text = self.add_to_archive(file_info)
372
- return file_info, archive_text
373
-
374
- except Exception as e:
375
- error_msg = f"فشل التحميل باستخدام yt-dlp: {str(e)}"
376
- raise gr.Error(error_msg)
377
-
378
- async def get_session_token(self, url):
379
- """الحصول على توكن الجلسة"""
380
- try:
381
- # Parse URL and get base domain
382
- parsed = urlparse(url)
383
- base_url = f"{parsed.scheme}://{parsed.netloc}"
384
-
385
- # Setup session with browser-like headers
386
- headers = {
387
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
388
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
389
- 'Accept-Language': 'en-US,en;q=0.9',
390
- 'Accept-Encoding': 'gzip, deflate, br',
391
- 'Connection': 'keep-alive',
392
- 'Upgrade-Insecure-Requests': '1',
393
- 'Sec-Fetch-Dest': 'document',
394
- 'Sec-Fetch-Mode': 'navigate',
395
- 'Sec-Fetch-Site': 'none',
396
- 'Sec-Fetch-User': '?1',
397
- 'Cache-Control': 'max-age=0',
398
- }
399
-
400
- connector = aiohttp.TCPConnector(ssl=False)
401
- timeout = aiohttp.ClientTimeout(total=30)
402
-
403
- async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
404
- # First get the main page to get initial cookies
405
- async with session.get(base_url, headers=headers) as response:
406
- if response.status != 200:
407
- return None, None
408
-
409
- # Get cookies from response
410
- cookies = {k: v.value for k, v in response.cookies.items()}
411
-
412
- # Extract any session or token information from the response
413
- text = await response.text()
414
- token_patterns = [
415
- r'token["\']?\s*:\s*["\']([^"\']+)["\']',
416
- r'auth["\']?\s*:\s*["\']([^"\']+)["\']',
417
- r'session["\']?\s*:\s*["\']([^"\']+)["\']',
418
- ]
419
-
420
- session_token = None
421
- for pattern in token_patterns:
422
- match = re.search(pattern, text, re.IGNORECASE)
423
- if match:
424
- session_token = match.group(1)
425
- break
426
-
427
- return cookies, session_token
428
-
429
- except Exception as e:
430
- print(f"Error getting session token: {str(e)}")
431
- return None, None
432
-
433
- async def refresh_video_token(self, url):
434
- """تحديث توكن الفيديو"""
435
- try:
436
- parsed = urlparse(url)
437
- query_params = parse_qs(parsed.query)
438
-
439
- # Get current timestamp
440
- current_time = int(time.time())
441
-
442
- # Generate new token parameters
443
- new_params = {
444
- 't': query_params.get('t', [''])[0],
445
- 's': str(current_time),
446
- 'e': '86400',
447
- 'f': query_params.get('f', [''])[0],
448
- 'sp': query_params.get('sp', ['1000'])[0],
449
- 'i': '0.0',
450
- '_': str(current_time * 1000) # Add millisecond timestamp
451
- }
452
-
453
- # Add any additional parameters from original URL
454
- for k, v in query_params.items():
455
- if k not in new_params and k not in ['s', 'e', '_']:
456
- new_params[k] = v[0]
457
-
458
- # Reconstruct URL with new parameters
459
- base_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
460
- new_url = f"{base_url}?{urlencode(new_params)}"
461
-
462
- return new_url
463
-
464
- except Exception as e:
465
- print(f"Error refreshing token: {str(e)}")
466
- return url
467
-
468
- async def stream_download(self, url, output_path, headers=None, proxy=None):
469
- """تنزيل الملف باستخدام التدفق"""
470
- if not headers:
471
- headers = {
472
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
473
- 'Accept': '*/*',
474
- 'Accept-Language': 'en-US,en;q=0.9',
475
- 'Accept-Encoding': 'gzip, deflate, br',
476
- 'Range': 'bytes=0-',
477
- 'Connection': 'keep-alive',
478
- 'Sec-Fetch-Dest': 'video',
479
- 'Sec-Fetch-Mode': 'cors',
480
- 'Sec-Fetch-Site': 'cross-site',
481
- 'Pragma': 'no-cache',
482
- 'Cache-Control': 'no-cache',
483
- }
484
-
485
- # Get session token and cookies
486
- cookies, session_token = await self.get_session_token(url)
487
- if cookies:
488
- cookie_string = "; ".join([f"{k}={v}" for k, v in cookies.items()])
489
- headers['Cookie'] = cookie_string
490
-
491
- if session_token:
492
- headers['Authorization'] = f'Bearer {session_token}'
493
-
494
- # Add referer and origin from the URL
495
- parsed_url = urlparse(url)
496
- base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
497
- headers['Referer'] = base_url
498
- headers['Origin'] = base_url
499
-
500
- connector = aiohttp.TCPConnector(ssl=False, limit=1)
501
- timeout = aiohttp.ClientTimeout(total=3600, connect=60)
502
-
503
- async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
504
- try:
505
- # First try HEAD request
506
- async with session.head(url, headers=headers, proxy=proxy, allow_redirects=True) as response:
507
- if response.status == 403:
508
- # If 403, try refreshing the URL token
509
- url = await self.refresh_video_token(url)
510
- # Add updated timestamp to headers
511
- headers['X-Requested-With'] = 'XMLHttpRequest'
512
- headers['If-Modified-Since'] = time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime())
513
- total_size = int(response.headers.get('Content-Length', 0))
514
- except Exception:
515
- total_size = 0
516
-
517
- # Prepare for chunked download
518
- chunk_size = 1024 * 1024 # 1MB chunks
519
- downloaded_size = 0
520
- retries = 3
521
-
522
- while retries > 0:
523
- try:
524
- async with session.get(url, headers=headers, proxy=proxy, allow_redirects=True) as response:
525
- if response.status == 403 and retries > 1:
526
- # Try refreshing token and retry
527
- url = await self.refresh_video_token(url)
528
- retries -= 1
529
- continue
530
-
531
- if response.status != 200:
532
- raise aiohttp.ClientError(f"HTTP {response.status}")
533
-
534
- with open(output_path, 'wb') as f:
535
- async for chunk in response.content.iter_chunked(chunk_size):
536
- if chunk:
537
- f.write(chunk)
538
- downloaded_size += len(chunk)
539
-
540
- # Update progress
541
- if total_size:
542
- progress = (downloaded_size / total_size) * 100
543
- print(f"\rDownloading: {progress:.1f}%", end="")
544
- break
545
- except Exception as e:
546
- print(f"Download attempt failed: {str(e)}")
547
- retries -= 1
548
- if retries == 0:
549
- raise
550
- await asyncio.sleep(2)
551
-
552
- async def try_download(self, url, browser_cookies=None, proxy=None):
553
- """محاولة تحميل الملف"""
554
- temp_dir = tempfile.mkdtemp()
555
- output_path = os.path.join(temp_dir, "video.mp4")
556
-
557
- headers = {
558
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
559
- 'Accept': '*/*',
560
- 'Accept-Language': 'en-US,en;q=0.9',
561
- 'Accept-Encoding': 'gzip, deflate, br',
562
- 'Range': 'bytes=0-',
563
- 'Connection': 'keep-alive',
564
- 'Sec-Fetch-Dest': 'video',
565
- 'Sec-Fetch-Mode': 'cors',
566
- 'Sec-Fetch-Site': 'cross-site',
567
- 'Pragma': 'no-cache',
568
- 'Cache-Control': 'no-cache',
569
- }
570
-
571
- # Add cookies to headers
572
- if browser_cookies:
573
- cookie_string = "; ".join([f"{k}={v}" for k, v in browser_cookies.items()])
574
- headers['Cookie'] = cookie_string
575
-
576
- try:
577
- await self.stream_download(url, output_path, headers, proxy)
578
-
579
- if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
580
- file_info = {
581
- "name": os.path.basename(output_path),
582
- "path": output_path,
583
- "mime_type": self.get_mime_type(output_path),
584
- "size": os.path.getsize(output_path)
585
- }
586
- return file_info
587
-
588
- except Exception as e:
589
- print(f"Download failed: {str(e)}")
590
- if os.path.exists(output_path):
591
- os.remove(output_path)
592
-
593
- return None
594
-
595
- async def download_file(self, url):
596
- """تحميل الملف"""
597
- try:
598
- # Get cookies
599
- browser_cookies = self.get_cookies(url)
600
-
601
- # First try direct download
602
- result = await self.try_download(url, browser_cookies)
603
- if result:
604
- archive_text = self.add_to_archive(result)
605
- return result, archive_text
606
-
607
- # If direct fails, try with proxies
608
- max_retries = 3
609
- for i in range(max_retries):
610
- proxy = self.get_random_proxy()
611
- if not proxy:
612
- continue
613
-
614
- try:
615
- result = await self.try_download(url, browser_cookies, proxy)
616
- if result:
617
- archive_text = self.add_to_archive(result)
618
- return result, archive_text
619
- except Exception as e:
620
- print(f"Proxy attempt {i+1} failed: {str(e)}")
621
- continue
622
-
623
- # If all attempts fail, try yt-dlp as last resort
624
- return await self.download_with_ytdlp(url, browser_cookies, use_proxy=True)
625
-
626
- except Exception as e:
627
- error_msg = f"حدث خطأ في التحميل: {str(e)}"
628
- raise gr.Error(error_msg)
629
 
630
  def get_filename_from_url(self, url, headers=None):
631
  try:
632
  if headers and 'content-disposition' in headers:
 
633
  matches = re.findall('filename="(.+)"', headers['content-disposition'])
634
  if matches:
635
  return matches[0]
636
 
637
  path = urlparse(url).path
638
  filename = os.path.basename(path)
639
- filename = filename.split('?')[0]
640
- filename = filename.replace('[', '').replace(']', '')
641
- if not filename or filename == '':
642
- return 'downloaded_file'
643
- return filename
644
  except:
645
  return 'downloaded_file'
646
 
@@ -648,199 +39,95 @@ class FastDownloader:
648
  mime_type, _ = mimetypes.guess_type(filename)
649
  return mime_type or 'application/octet-stream'
650
 
651
- def load_archive(self):
652
- try:
653
- if os.path.exists(self.archive_file):
654
- with open(self.archive_file, 'r', encoding='utf-8') as f:
655
- self.archive = json.load(f)
656
- else:
657
- self.archive = []
658
- except:
659
- self.archive = []
660
-
661
- def save_archive(self):
662
- with open(self.archive_file, 'w', encoding='utf-8') as f:
663
- json.dump(self.archive, f, ensure_ascii=False, indent=2)
664
-
665
- def add_to_archive(self, file_info):
666
- archive_entry = {
667
- "name": file_info["name"],
668
- "path": file_info["path"],
669
- "size": file_info["size"],
670
- "mime_type": file_info["mime_type"],
671
- "date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
672
- }
673
- self.archive.insert(0, archive_entry)
674
- self.save_archive()
675
- return self.format_archive()
676
-
677
- def format_archive(self):
678
- archive_text = "📚 أرشيف التحميلات:\n\n"
679
- for entry in self.archive:
680
- size_mb = entry["size"] / (1024 * 1024)
681
- archive_text += f"📥 {entry['name']}\n"
682
- archive_text += f" 📅 {entry['date']}\n"
683
- archive_text += f" 📦 {size_mb:.1f} MB\n"
684
- archive_text += f" 📎 {entry['mime_type']}\n"
685
- archive_text += "─" * 50 + "\n"
686
- return archive_text
687
-
688
- async def handle_successful_download(self, response):
689
- """معالجة التحميل الناجح"""
690
- filename = self.get_filename_from_url(response.url, dict(response.headers))
691
- temp_dir = tempfile.mkdtemp()
692
- file_path = os.path.join(temp_dir, filename)
693
-
694
- total_size = int(response.headers.get('content-length', 0))
695
- chunk_size = 1024 * 1024 # 1MB chunks
696
-
697
- with open(file_path, 'wb') as f:
698
- downloaded = 0
699
- async for chunk in response.content.iter_chunked(chunk_size):
700
- if chunk:
701
- f.write(chunk)
702
- downloaded += len(chunk)
703
- if total_size:
704
- progress = (downloaded / total_size) * 100
705
- print(f"\rDownloading: {progress:.1f}%", end="")
706
 
707
- file_info = {
708
- "name": filename,
709
- "path": file_path,
710
- "mime_type": self.get_mime_type(filename),
711
- "size": os.path.getsize(file_path)
712
- }
713
-
714
- archive_text = self.add_to_archive(file_info)
715
- return file_info, archive_text
716
-
717
- async def get_m3u8_segments(self, url):
718
- """Get HLS segments from m3u8 playlist"""
719
- headers = {
720
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
721
- 'Accept': '*/*',
722
- 'Accept-Language': 'en-US,en;q=0.9',
723
- 'Accept-Encoding': 'gzip, deflate, br',
724
- 'Origin': 'https://cdn-tube.xyz',
725
- 'Sec-Fetch-Site': 'cross-site',
726
- 'Sec-Fetch-Mode': 'cors',
727
- 'Sec-Fetch-Dest': 'empty',
728
- 'Referer': 'https://cdn-tube.xyz/',
729
- }
730
 
 
 
731
  try:
732
- # Convert direct video URL to m3u8 URL if needed
733
- if not url.endswith('.m3u8'):
734
- parsed = urlparse(url)
735
- path_parts = parsed.path.split('/')
736
- if len(path_parts) > 1:
737
- video_id = path_parts[-2]
738
- m3u8_url = f"{parsed.scheme}://{parsed.netloc}/hls/{video_id}/index.m3u8"
739
- # Add original query parameters
740
- m3u8_url += '?' + parsed.query if parsed.query else ''
741
- else:
742
- return None
743
-
744
  async with aiohttp.ClientSession() as session:
745
- async with session.get(m3u8_url, headers=headers) as response:
746
- if response.status != 200:
747
- return None
748
-
749
- m3u8_content = await response.text()
750
-
751
- # Parse m3u8 content
752
- segments = []
753
- base_url = m3u8_url.rsplit('/', 1)[0] + '/'
754
-
755
- for line in m3u8_content.splitlines():
756
- if line.endswith('.ts'):
757
- if line.startswith('http'):
758
- segments.append(line)
759
- else:
760
- segments.append(base_url + line)
761
-
762
- return segments
763
- except Exception as e:
764
- print(f"Error getting m3u8 segments: {str(e)}")
765
- return None
766
 
767
- async def download_with_segments(self, url, output_path):
768
- """Download video using HLS segments"""
769
- segments = await self.get_m3u8_segments(url)
770
- if not segments:
771
- return False
772
 
773
- headers = {
774
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
775
- 'Accept': '*/*',
776
- 'Accept-Language': 'en-US,en;q=0.9',
777
- 'Accept-Encoding': 'gzip, deflate, br',
778
- 'Origin': 'https://cdn-tube.xyz',
779
- 'Sec-Fetch-Site': 'cross-site',
780
- 'Sec-Fetch-Mode': 'cors',
781
- 'Sec-Fetch-Dest': 'empty',
782
- 'Referer': 'https://cdn-tube.xyz/',
783
- }
784
 
785
- total_segments = len(segments)
786
- downloaded_segments = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
787
 
788
- try:
789
- with open(output_path, 'wb') as outfile:
790
- async with aiohttp.ClientSession() as session:
791
- for segment_url in segments:
792
- retries = 3
793
- while retries > 0:
794
- try:
795
- async with session.get(segment_url, headers=headers) as response:
796
- if response.status == 200:
797
- chunk = await response.read()
798
- outfile.write(chunk)
799
- downloaded_segments += 1
800
- progress = (downloaded_segments / total_segments) * 100
801
- print(f"\rDownloading: {progress:.1f}%", end="")
802
- break
803
- elif response.status == 403:
804
- # Try refreshing token
805
- segment_url = await self.refresh_video_token(segment_url)
806
- retries -= 1
807
- await asyncio.sleep(1)
808
- continue
809
- else:
810
- retries -= 1
811
- except Exception:
812
- retries -= 1
813
- await asyncio.sleep(1)
814
-
815
- if retries == 0:
816
- print(f"\nFailed to download segment: {segment_url}")
817
- return False
818
-
819
- return True
820
  except Exception as e:
821
- print(f"Error downloading segments: {str(e)}")
822
- return False
823
-
824
- async def try_download(self, url, browser_cookies=None, proxy=None):
825
- """محاولة تحميل الملف"""
826
- temp_dir = tempfile.mkdtemp()
827
- output_path = os.path.join(temp_dir, "video.mp4")
828
-
829
- # Try HLS download first
830
- if await self.download_with_segments(url, output_path):
831
- return output_path, temp_dir
832
 
833
- # If HLS fails, try direct download
 
834
  try:
835
- await self.stream_download(url, output_path, proxy=proxy)
836
- return output_path, temp_dir
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
837
  except Exception as e:
838
- # If both methods fail, try yt-dlp
839
- try:
840
- await self.download_with_ytdlp(url, output_path)
841
- return output_path, temp_dir
842
- except Exception as ytdlp_error:
843
- raise Exception(f"All download methods failed. Last error: {str(ytdlp_error)}")
844
 
845
  def create_interface():
846
  downloader = FastDownloader()
@@ -868,40 +155,32 @@ def create_interface():
868
  with gr.Row():
869
  status = gr.Textbox(label="الحالة", value="جاهز للتحميل", interactive=False)
870
  file_output = gr.File(label="الملف المحمل")
871
-
872
- with gr.Row():
873
- archive = gr.Textbox(
874
- label="أرشيف التحميلات",
875
- value=downloader.format_archive(),
876
- interactive=False,
877
- lines=10
878
- )
879
 
880
  def download_and_update(url):
881
  if not url:
882
- return "الرجاء إدخال رابط صحيح", None, downloader.format_archive()
883
 
884
  try:
885
  domain = urlparse(url).netloc
886
 
887
  if 'youtube.com' in domain or 'youtu.be' in domain:
888
- file_info, archive_text = downloader.download_youtube(url)
889
  else:
 
890
  loop = asyncio.new_event_loop()
891
  asyncio.set_event_loop(loop)
892
- file_info, archive_text = loop.run_until_complete(downloader.download_file(url))
893
  loop.close()
894
 
895
- return "تم التحميل بنجاح!", file_info["path"], archive_text
896
 
897
  except Exception as e:
898
- error_msg = f"حدث خطأ: {str(e)}"
899
- raise gr.Error(error_msg)
900
 
901
  download_btn.click(
902
  fn=download_and_update,
903
  inputs=url_input,
904
- outputs=[status, file_output, archive]
905
  )
906
 
907
  return demo
 
1
  import gradio as gr
2
  import yt_dlp
3
  import aiohttp
 
4
  import asyncio
5
  import os
6
  import tempfile
7
+ from urllib.parse import urlparse
8
  import time
9
  import mimetypes
10
  import concurrent.futures
11
  import math
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  class FastDownloader:
14
  def __init__(self):
 
16
  'format': 'best',
17
  'quiet': True,
18
  'no_warnings': True,
19
+ 'concurrent_fragments': 10, # زيادة عدد التحميلات المتزامنة
 
20
  }
21
+ self.chunk_size = 1024 * 1024 * 5 # 5MB chunks
22
+ self.max_workers = 5 # عدد العمليات المتزامنة
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
  def get_filename_from_url(self, url, headers=None):
25
  try:
26
  if headers and 'content-disposition' in headers:
27
+ import re
28
  matches = re.findall('filename="(.+)"', headers['content-disposition'])
29
  if matches:
30
  return matches[0]
31
 
32
  path = urlparse(url).path
33
  filename = os.path.basename(path)
34
+ return filename if filename else 'downloaded_file'
 
 
 
 
35
  except:
36
  return 'downloaded_file'
37
 
 
39
  mime_type, _ = mimetypes.guess_type(filename)
40
  return mime_type or 'application/octet-stream'
41
 
42
+ async def download_chunk(self, session, url, start, end, file, semaphore):
43
+ """تحميل جزء من الملف"""
44
+ headers = {'Range': f'bytes={start}-{end}'}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
+ async with semaphore:
47
+ try:
48
+ async with session.get(url, headers=headers) as response:
49
+ response.raise_for_status()
50
+ chunk = await response.read()
51
+ with open(file, 'r+b') as f:
52
+ f.seek(start)
53
+ f.write(chunk)
54
+ return len(chunk)
55
+ except Exception as e:
56
+ print(f"Error downloading chunk {start}-{end}: {str(e)}")
57
+ return 0
 
 
 
 
 
 
 
 
 
 
 
58
 
59
+ async def download_file(self, url):
60
+ """تحميل الملف بشكل متزامن"""
61
  try:
62
+ # إنشاء جلسة HTTP
 
 
 
 
 
 
 
 
 
 
 
63
  async with aiohttp.ClientSession() as session:
64
+ # الحصول على معلومات الملف
65
+ async with session.head(url) as response:
66
+ response.raise_for_status()
67
+ total_size = int(response.headers.get('content-length', 0))
68
+ filename = self.get_filename_from_url(url, response.headers)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
+ if total_size == 0:
71
+ raise gr.Error("لا يمكن تحديد حجم الملف")
 
 
 
72
 
73
+ # إنشاء ملف مؤقت
74
+ temp_dir = tempfile.mkdtemp()
75
+ file_path = os.path.join(temp_dir, filename)
 
 
 
 
 
 
 
 
76
 
77
+ # تحديد حجم كل جزء
78
+ chunk_size = math.ceil(total_size / self.max_workers)
79
+
80
+ # إنشاء الملف بالحجم المطلوب
81
+ with open(file_path, 'wb') as f:
82
+ f.seek(total_size - 1)
83
+ f.write(b'\0')
84
+
85
+ # إنشاء مهام التحميل
86
+ semaphore = asyncio.Semaphore(self.max_workers)
87
+ tasks = []
88
+
89
+ for i in range(self.max_workers):
90
+ start = i * chunk_size
91
+ end = min(start + chunk_size - 1, total_size - 1)
92
+ task = self.download_chunk(session, url, start, end, file_path, semaphore)
93
+ tasks.append(task)
94
+
95
+ # تنفيذ المهام بشكل متزامن
96
+ await asyncio.gather(*tasks)
97
+
98
+ return {
99
+ "name": filename,
100
+ "path": file_path,
101
+ "mime_type": self.get_mime_type(filename),
102
+ "size": os.path.getsize(file_path)
103
+ }
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  except Exception as e:
106
+ raise gr.Error(f"حدث خطأ في التحميل: {str(e)}")
 
 
 
 
 
 
 
 
 
 
107
 
108
+ def download_youtube(self, url):
109
+ """تحميل فيديو يوتيوب"""
110
  try:
111
+ temp_dir = tempfile.mkdtemp()
112
+
113
+ opts = dict(self.ydl_opts)
114
+ opts.update({
115
+ 'outtmpl': os.path.join(temp_dir, '%(title)s.%(ext)s'),
116
+ })
117
+
118
+ with yt_dlp.YoutubeDL(opts) as ydl:
119
+ info = ydl.extract_info(url, download=True)
120
+ filename = ydl.prepare_filename(info)
121
+
122
+ return {
123
+ "name": os.path.basename(filename),
124
+ "path": filename,
125
+ "mime_type": self.get_mime_type(filename),
126
+ "size": os.path.getsize(filename)
127
+ }
128
+
129
  except Exception as e:
130
+ raise gr.Error(f"حدث خطأ في تحميل الفيديو: {str(e)}")
 
 
 
 
 
131
 
132
  def create_interface():
133
  downloader = FastDownloader()
 
155
  with gr.Row():
156
  status = gr.Textbox(label="الحالة", value="جاهز للتحميل", interactive=False)
157
  file_output = gr.File(label="الملف المحمل")
 
 
 
 
 
 
 
 
158
 
159
  def download_and_update(url):
160
  if not url:
161
+ return "الرجاء إدخال رابط صحيح", None
162
 
163
  try:
164
  domain = urlparse(url).netloc
165
 
166
  if 'youtube.com' in domain or 'youtu.be' in domain:
167
+ file_info = downloader.download_youtube(url)
168
  else:
169
+ # استخدام asyncio للتحميل المتزامن
170
  loop = asyncio.new_event_loop()
171
  asyncio.set_event_loop(loop)
172
+ file_info = loop.run_until_complete(downloader.download_file(url))
173
  loop.close()
174
 
175
+ return "تم التحميل بنجاح!", file_info["path"]
176
 
177
  except Exception as e:
178
+ return f"حدث خطأ: {str(e)}", None
 
179
 
180
  download_btn.click(
181
  fn=download_and_update,
182
  inputs=url_input,
183
+ outputs=[status, file_output]
184
  )
185
 
186
  return demo
requirements.txt CHANGED
@@ -1,9 +1,5 @@
1
  gradio>=4.7.1
2
  yt-dlp>=2023.12.30
3
  aiohttp>=3.9.1
4
- selenium>=4.16.0
5
- undetected-chromedriver>=3.5.4
6
- beautifulsoup4>=4.12.2
7
- urllib3<2.0.0
8
- aiohttp-proxy>=0.1.2
9
- fake-useragent>=1.4.0
 
1
  gradio>=4.7.1
2
  yt-dlp>=2023.12.30
3
  aiohttp>=3.9.1
4
+ requests>=2.31.0
5
+ pytube>=15.0.0