Offex commited on
Commit
f25e6b2
·
verified ·
1 Parent(s): 5bd89e6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -52
app.py CHANGED
@@ -2,17 +2,18 @@ from flask import Flask, render_template_string, request, send_file, after_this_
2
  import yt_dlp
3
  import os
4
  import uuid
 
5
 
6
  app = Flask(__name__)
7
 
8
- # --- PROFESSIONAL ANIMATED UI ---
9
  HTML_CODE = """
10
  <!DOCTYPE html>
11
  <html lang="en">
12
  <head>
13
  <meta charset="UTF-8">
14
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
15
- <title>Neon Downloader Pro</title>
16
  <script src="https://cdn.tailwindcss.com"></script>
17
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
18
  <style>
@@ -23,10 +24,10 @@ HTML_CODE = """
23
  /* Animated Background */
24
  .bg-animate {
25
  position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1;
26
- background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
27
  background-size: 400% 400%;
28
  animation: gradientBG 15s ease infinite;
29
- opacity: 0.2;
30
  }
31
  @keyframes gradientBG {
32
  0% { background-position: 0% 50%; }
@@ -34,30 +35,26 @@ HTML_CODE = """
34
  100% { background-position: 0% 50%; }
35
  }
36
 
37
- /* Glass Card */
38
  .glass {
39
- background: rgba(20, 20, 20, 0.7);
40
- backdrop-filter: blur(16px);
41
- -webkit-backdrop-filter: blur(16px);
42
  border: 1px solid rgba(255, 255, 255, 0.1);
43
- box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
44
  }
45
 
46
- /* Input Glow */
47
  .input-glow:focus {
48
- box-shadow: 0 0 20px rgba(0, 242, 234, 0.3);
49
- border-color: #00f2ea;
50
  }
51
 
52
- /* Button Animation */
53
  .btn-grad {
54
- background-image: linear-gradient(to right, #FF512F 0%, #DD2476 51%, #FF512F 100%);
55
  transition: 0.5s;
56
  background-size: 200% auto;
57
  }
58
  .btn-grad:hover { background-position: right center; transform: scale(1.02); }
59
 
60
- /* Loading Spinner */
61
  .loader {
62
  border: 3px solid rgba(255,255,255,0.3);
63
  border-radius: 50%;
@@ -68,7 +65,6 @@ HTML_CODE = """
68
  }
69
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
70
 
71
- /* List Item Animation */
72
  .fade-in-up { animation: fadeInUp 0.5s ease-out forwards; opacity: 0; transform: translateY(20px); }
73
  @keyframes fadeInUp { to { opacity: 1; transform: translateY(0); } }
74
  </style>
@@ -79,57 +75,63 @@ HTML_CODE = """
79
 
80
  <div class="glass w-full max-w-lg rounded-3xl p-8 relative overflow-hidden">
81
 
82
- <div class="absolute -top-10 -right-10 w-32 h-32 bg-blue-500 rounded-full blur-[80px] opacity-40"></div>
83
- <div class="absolute -bottom-10 -left-10 w-32 h-32 bg-pink-500 rounded-full blur-[80px] opacity-40"></div>
 
 
 
84
 
85
  <div class="text-center mb-8 relative z-10">
86
  <h1 class="text-4xl font-bold mb-1 tracking-tight">
87
- <span class="text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 to-blue-500">Video</span>
88
- <span class="text-white">Hunter</span>
89
  </h1>
90
- <p class="text-gray-400 text-sm">Ultra HDNo Watermark Fast</p>
91
  </div>
92
 
93
  <form method="POST" action="/fetch" onsubmit="showLoader()" class="space-y-5 relative z-10">
94
  <div class="relative">
95
  <i class="fa-solid fa-link absolute left-4 top-4 text-gray-400"></i>
96
  <input type="text" name="url"
97
- class="input-glow w-full bg-black/40 border border-gray-700 rounded-xl py-3.5 pl-12 pr-4 text-white placeholder-gray-500 focus:outline-none transition-all duration-300"
98
- placeholder="Paste TikTok/Reel Link here..." required>
99
  </div>
100
 
101
  <button type="submit" id="searchBtn" class="btn-grad w-full py-4 rounded-xl font-bold text-lg shadow-lg flex justify-center items-center gap-2">
102
- <span>ANALYZE VIDEO</span>
103
  <div class="loader" id="spinner"></div>
104
  </button>
105
  </form>
106
 
 
 
 
 
 
 
 
 
107
  {% if formats %}
108
- <div class="mt-8 space-y-3 relative z-10">
109
- <div class="flex items-center gap-2 mb-4">
110
- <div class="h-px bg-gray-700 flex-1"></div>
111
- <span class="text-xs text-gray-400 uppercase tracking-widest">Select Quality</span>
112
- <div class="h-px bg-gray-700 flex-1"></div>
113
- </div>
114
 
115
  {% for f in formats %}
116
- <div class="fade-in-up" style="animation-delay: {{ loop.index0 * 100 }}ms">
117
  <form method="POST" action="/download" onsubmit="showDownloadLoader(this)">
118
  <input type="hidden" name="url" value="{{ url }}">
119
  <input type="hidden" name="format_id" value="{{ f.id }}">
120
 
121
- <button type="submit" class="group w-full bg-gray-800/50 hover:bg-gray-800 border border-gray-700/50 hover:border-blue-500/50 p-4 rounded-xl transition-all duration-300 flex justify-between items-center">
122
  <div class="text-left">
123
  <div class="flex items-center gap-2">
124
- <span class="font-bold text-white group-hover:text-blue-400 transition-colors">{{ f.label }}</span>
125
- {% if 'HD' in f.label or '1080' in f.label %}
126
- <span class="bg-blue-500/20 text-blue-300 text-[10px] px-2 py-0.5 rounded font-bold">HQ</span>
127
  {% endif %}
128
  </div>
129
- <div class="text-xs text-gray-500 mt-1">{{ f.size }} • MP4</div>
130
  </div>
131
- <div class="w-10 h-10 rounded-full bg-gray-700 group-hover:bg-blue-600 flex items-center justify-center transition-all">
132
- <i class="fa-solid fa-download text-sm text-gray-300 group-hover:text-white"></i>
133
  </div>
134
  </button>
135
  </form>
@@ -139,7 +141,7 @@ HTML_CODE = """
139
  {% endif %}
140
 
141
  {% if error %}
142
- <div class="mt-6 p-4 bg-red-500/10 border border-red-500/50 rounded-xl flex items-start gap-3 fade-in-up">
143
  <i class="fa-solid fa-circle-exclamation text-red-500 mt-1"></i>
144
  <div class="text-red-200 text-sm">{{ error }}</div>
145
  </div>
@@ -149,8 +151,9 @@ HTML_CODE = """
149
 
150
  <script>
151
  function showLoader() {
152
- document.getElementById('searchBtn').style.opacity = '0.8';
153
- document.getElementById('searchBtn').innerHTML = 'Processing... <div class="loader" style="display:inline-block"></div>';
 
154
  }
155
  function showDownloadLoader(form) {
156
  const btn = form.querySelector('button');
@@ -170,37 +173,73 @@ def index():
170
  def fetch():
171
  url = request.form.get("url")
172
  try:
173
- # Optimized for Cloud
174
- opts = {'quiet': True, 'nocheckcertificate': True, 'user_agent': 'Mozilla/5.0'}
 
 
 
 
 
175
  with yt_dlp.YoutubeDL(opts) as ydl:
176
  info = ydl.extract_info(url, download=False)
177
  formats = []
178
  seen = set()
179
 
 
 
 
 
180
  for f in info.get('formats', []):
181
- if f.get('vcodec') != 'none' and f.get('ext') == 'mp4':
 
182
  h = f.get('height', 0)
183
- if h >= 1080: label = "1080p Full HD"
 
 
 
 
 
184
  elif h >= 720: label = "720p HD"
185
- else: label = f"{h}p SD"
 
186
 
 
187
  if label not in seen:
188
  size = f.get('filesize') or f.get('filesize_approx') or 0
189
- formats.append({'id': f['format_id'], 'label': label, 'size': f"{round(size/1e6,1)} MB", 'h': h})
 
 
 
 
 
 
 
 
190
  seen.add(label)
191
 
 
192
  formats.sort(key=lambda x: x['h'], reverse=True)
193
- return render_template_string(HTML_CODE, formats=formats, url=url)
 
194
  except Exception as e:
195
- return render_template_string(HTML_CODE, error=str(e))
196
 
197
  @app.route("/download", methods=["POST"])
198
  def dl():
199
  url = request.form.get("url")
200
  fid = request.form.get("format_id")
201
- name = f"VidHunter_{uuid.uuid4().hex[:5]}.mp4"
 
 
202
  try:
203
- opts = {'format': f'{fid}+bestaudio/best', 'outtmpl': name, 'merge_output_format': 'mp4'}
 
 
 
 
 
 
 
204
  with yt_dlp.YoutubeDL(opts) as ydl:
205
  ydl.download([url])
206
 
@@ -210,7 +249,8 @@ def dl():
210
  return r
211
  return send_file(name, as_attachment=True)
212
  except Exception as e:
213
- return str(e)
214
 
215
  if __name__ == "__main__":
216
  app.run(host='0.0.0.0', port=7860)
 
 
2
  import yt_dlp
3
  import os
4
  import uuid
5
+ import re
6
 
7
  app = Flask(__name__)
8
 
9
+ # --- UNIVERSAL DOWNLOADER UI ---
10
  HTML_CODE = """
11
  <!DOCTYPE html>
12
  <html lang="en">
13
  <head>
14
  <meta charset="UTF-8">
15
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
16
+ <title>Universal Video Downloader</title>
17
  <script src="https://cdn.tailwindcss.com"></script>
18
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
19
  <style>
 
24
  /* Animated Background */
25
  .bg-animate {
26
  position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1;
27
+ background: linear-gradient(-45deg, #ff0000, #2b2b2b, #00f2ea, #000000);
28
  background-size: 400% 400%;
29
  animation: gradientBG 15s ease infinite;
30
+ opacity: 0.3;
31
  }
32
  @keyframes gradientBG {
33
  0% { background-position: 0% 50%; }
 
35
  100% { background-position: 0% 50%; }
36
  }
37
 
 
38
  .glass {
39
+ background: rgba(15, 15, 15, 0.8);
40
+ backdrop-filter: blur(20px);
 
41
  border: 1px solid rgba(255, 255, 255, 0.1);
42
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.5);
43
  }
44
 
 
45
  .input-glow:focus {
46
+ box-shadow: 0 0 25px rgba(255, 0, 0, 0.4);
47
+ border-color: #ff0000;
48
  }
49
 
50
+ /* Universal Button Gradient */
51
  .btn-grad {
52
+ background-image: linear-gradient(to right, #E50914 0%, #DB4437 51%, #E50914 100%); /* YouTube Red Colors */
53
  transition: 0.5s;
54
  background-size: 200% auto;
55
  }
56
  .btn-grad:hover { background-position: right center; transform: scale(1.02); }
57
 
 
58
  .loader {
59
  border: 3px solid rgba(255,255,255,0.3);
60
  border-radius: 50%;
 
65
  }
66
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
67
 
 
68
  .fade-in-up { animation: fadeInUp 0.5s ease-out forwards; opacity: 0; transform: translateY(20px); }
69
  @keyframes fadeInUp { to { opacity: 1; transform: translateY(0); } }
70
  </style>
 
75
 
76
  <div class="glass w-full max-w-lg rounded-3xl p-8 relative overflow-hidden">
77
 
78
+ <div class="absolute top-4 right-4 flex gap-3 opacity-50">
79
+ <i class="fa-brands fa-youtube text-2xl text-red-500"></i>
80
+ <i class="fa-brands fa-tiktok text-2xl text-pink-500"></i>
81
+ <i class="fa-brands fa-instagram text-2xl text-purple-500"></i>
82
+ </div>
83
 
84
  <div class="text-center mb-8 relative z-10">
85
  <h1 class="text-4xl font-bold mb-1 tracking-tight">
86
+ <span class="text-white">Uni</span><span class="text-red-500">Loader</span>
 
87
  </h1>
88
+ <p class="text-gray-400 text-sm">YouTube TikTok InstagramShorts</p>
89
  </div>
90
 
91
  <form method="POST" action="/fetch" onsubmit="showLoader()" class="space-y-5 relative z-10">
92
  <div class="relative">
93
  <i class="fa-solid fa-link absolute left-4 top-4 text-gray-400"></i>
94
  <input type="text" name="url"
95
+ class="input-glow w-full bg-black/50 border border-gray-700 rounded-xl py-3.5 pl-12 pr-4 text-white placeholder-gray-500 focus:outline-none transition-all duration-300"
96
+ placeholder="Paste YouTube or TikTok Link..." required>
97
  </div>
98
 
99
  <button type="submit" id="searchBtn" class="btn-grad w-full py-4 rounded-xl font-bold text-lg shadow-lg flex justify-center items-center gap-2">
100
+ <span>FETCH VIDEO</span>
101
  <div class="loader" id="spinner"></div>
102
  </button>
103
  </form>
104
 
105
+ {% if title %}
106
+ <div class="mt-6 p-4 bg-gray-800/50 rounded-xl border border-gray-700 fade-in-up">
107
+ <div class="text-sm text-gray-400 mb-1">Video Found:</div>
108
+ <div class="font-bold text-white truncate">{{ title }}</div>
109
+ <div class="text-xs text-blue-400 mt-1"><i class="fa-solid fa-clock"></i> {{ duration }}</div>
110
+ </div>
111
+ {% endif %}
112
+
113
  {% if formats %}
114
+ <div class="mt-6 space-y-3 relative z-10">
115
+ <div class="text-xs text-gray-400 uppercase tracking-widest text-center mb-4">Select Quality</div>
 
 
 
 
116
 
117
  {% for f in formats %}
118
+ <div class="fade-in-up" style="animation-delay: {{ loop.index0 * 50 }}ms">
119
  <form method="POST" action="/download" onsubmit="showDownloadLoader(this)">
120
  <input type="hidden" name="url" value="{{ url }}">
121
  <input type="hidden" name="format_id" value="{{ f.id }}">
122
 
123
+ <button type="submit" class="group w-full bg-gray-900/80 hover:bg-gray-800 border border-gray-700 hover:border-red-500 p-4 rounded-xl transition-all duration-300 flex justify-between items-center">
124
  <div class="text-left">
125
  <div class="flex items-center gap-2">
126
+ <span class="font-bold text-xl text-white">{{ f.label }}</span>
127
+ {% if '4K' in f.label or '1080' in f.label %}
128
+ <span class="bg-red-600 text-white text-[10px] px-2 py-0.5 rounded font-bold">HD</span>
129
  {% endif %}
130
  </div>
131
+ <div class="text-xs text-gray-500 mt-1">{{ f.ext }} • {{ f.size }}</div>
132
  </div>
133
+ <div class="w-10 h-10 rounded-full bg-gray-800 group-hover:bg-red-600 flex items-center justify-center transition-all">
134
+ <i class="fa-solid fa-download text-sm text-gray-400 group-hover:text-white"></i>
135
  </div>
136
  </button>
137
  </form>
 
141
  {% endif %}
142
 
143
  {% if error %}
144
+ <div class="mt-6 p-4 bg-red-900/30 border border-red-500 rounded-xl flex items-start gap-3 fade-in-up">
145
  <i class="fa-solid fa-circle-exclamation text-red-500 mt-1"></i>
146
  <div class="text-red-200 text-sm">{{ error }}</div>
147
  </div>
 
151
 
152
  <script>
153
  function showLoader() {
154
+ const btn = document.getElementById('searchBtn');
155
+ btn.style.opacity = '0.8';
156
+ btn.innerHTML = 'Analyzing... <div class="loader" style="display:inline-block"></div>';
157
  }
158
  function showDownloadLoader(form) {
159
  const btn = form.querySelector('button');
 
173
  def fetch():
174
  url = request.form.get("url")
175
  try:
176
+ # Options optimized for YouTube & TikTok
177
+ opts = {
178
+ 'quiet': True,
179
+ 'nocheckcertificate': True,
180
+ 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
181
+ }
182
+
183
  with yt_dlp.YoutubeDL(opts) as ydl:
184
  info = ydl.extract_info(url, download=False)
185
  formats = []
186
  seen = set()
187
 
188
+ # Metadata
189
+ video_title = info.get('title', 'Unknown Video')
190
+ duration = info.get('duration_string', 'Unknown')
191
+
192
  for f in info.get('formats', []):
193
+ # Filter for Video streams
194
+ if f.get('vcodec') != 'none':
195
  h = f.get('height', 0)
196
+ if not h: continue
197
+
198
+ # Smart Labeling
199
+ if h >= 2160: label = "4K Ultra HD"
200
+ elif h >= 1440: label = "2K Quad HD"
201
+ elif h >= 1080: label = "1080p Full HD"
202
  elif h >= 720: label = "720p HD"
203
+ elif h >= 480: label = "480p SD"
204
+ else: label = f"{h}p Low"
205
 
206
+ # Avoid duplicates
207
  if label not in seen:
208
  size = f.get('filesize') or f.get('filesize_approx') or 0
209
+ size_str = f"{round(size/1e6,1)} MB" if size > 0 else "N/A"
210
+
211
+ formats.append({
212
+ 'id': f['format_id'],
213
+ 'label': label,
214
+ 'size': size_str,
215
+ 'h': h,
216
+ 'ext': f['ext'].upper()
217
+ })
218
  seen.add(label)
219
 
220
+ # Sort highest quality first
221
  formats.sort(key=lambda x: x['h'], reverse=True)
222
+ return render_template_string(HTML_CODE, formats=formats, url=url, title=video_title, duration=duration)
223
+
224
  except Exception as e:
225
+ return render_template_string(HTML_CODE, error=f"Error: {str(e)}")
226
 
227
  @app.route("/download", methods=["POST"])
228
  def dl():
229
  url = request.form.get("url")
230
  fid = request.form.get("format_id")
231
+ # Clean filename
232
+ name = f"Video_{uuid.uuid4().hex[:5]}.mp4"
233
+
234
  try:
235
+ # YouTube Logic: Download Video+Audio separately and Merge
236
+ opts = {
237
+ 'format': f'{fid}+bestaudio/best', # This magic line handles 4K YouTube audio merging
238
+ 'outtmpl': name,
239
+ 'merge_output_format': 'mp4',
240
+ 'nocheckcertificate': True,
241
+ }
242
+
243
  with yt_dlp.YoutubeDL(opts) as ydl:
244
  ydl.download([url])
245
 
 
249
  return r
250
  return send_file(name, as_attachment=True)
251
  except Exception as e:
252
+ return f"Download Failed: {str(e)}"
253
 
254
  if __name__ == "__main__":
255
  app.run(host='0.0.0.0', port=7860)
256
+