Offex commited on
Commit
f0a1d36
Β·
verified Β·
1 Parent(s): 3e8ea08

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +263 -368
app.py CHANGED
@@ -5,381 +5,247 @@ import uuid
5
 
6
  app = Flask(__name__)
7
 
8
- # ---------------- HTML 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>UniLoader β€” Premium Video Downloader</title>
16
  <link rel="preconnect" href="https://fonts.googleapis.com">
17
- <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;900&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet">
18
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
19
  <style>
20
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
21
 
22
  :root {
23
- --gold: #c9a84c;
24
- --gold-light: #f0d080;
25
- --gold-dim: rgba(201,168,76,0.15);
26
- --bg-deep: #080b12;
27
- --bg-card: rgba(12, 17, 30, 0.92);
28
- --border: rgba(201,168,76,0.25);
29
- --text: #e8e0d0;
30
- --muted: #7a7060;
31
  }
32
 
33
  html, body {
34
  min-height: 100vh;
35
- background: var(--bg-deep);
36
- font-family: 'DM Sans', sans-serif;
37
  color: var(--text);
38
  overflow-x: hidden;
39
  }
40
 
41
- /* ── Animated Background ── */
42
- .bg-scene {
43
- position: fixed; inset: 0; z-index: 0;
44
- overflow: hidden;
45
- pointer-events: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  }
47
-
48
- .orb {
49
- position: absolute;
50
- border-radius: 50%;
51
- filter: blur(80px);
52
- opacity: 0.35;
53
- animation: drift 18s ease-in-out infinite alternate;
54
  }
55
- .orb1 {
56
- width: 520px; height: 520px;
57
- background: radial-gradient(circle, #1a3a6e, transparent 70%);
58
- top: -180px; left: -120px;
59
- animation-duration: 22s;
60
  }
61
- .orb2 {
62
- width: 380px; height: 380px;
63
- background: radial-gradient(circle, #6e3a1a, transparent 70%);
64
- bottom: -100px; right: -80px;
65
- animation-duration: 17s;
66
- animation-delay: -8s;
67
  }
68
- .orb3 {
69
- width: 240px; height: 240px;
70
- background: radial-gradient(circle, var(--gold), transparent 70%);
71
- top: 45%; left: 55%;
72
- opacity: 0.12;
73
- animation-duration: 25s;
74
- animation-delay: -13s;
75
  }
76
-
77
- @keyframes drift {
78
- 0% { transform: translate(0, 0) scale(1); }
79
- 50% { transform: translate(30px, -40px) scale(1.08); }
80
- 100% { transform: translate(-20px, 30px) scale(0.95); }
81
  }
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
- /* Fine grid overlay */
84
  .bg-grid {
85
- position: fixed; inset: 0; z-index: 0;
86
  background-image:
87
- linear-gradient(rgba(201,168,76,0.04) 1px, transparent 1px),
88
- linear-gradient(90deg, rgba(201,168,76,0.04) 1px, transparent 1px);
89
- background-size: 52px 52px;
90
  }
91
 
92
- /* ── Layout ── */
93
  .page-wrap {
94
- position: relative; z-index: 1;
95
- min-height: 100vh;
96
- display: flex; flex-direction: column;
97
- align-items: center; justify-content: center;
98
- padding: 2rem 1rem;
99
  }
100
 
101
- /* ── Brand Header ── */
102
- .brand {
103
- text-align: center;
104
- margin-bottom: 2.8rem;
105
- animation: fadeDown 0.7s ease both;
106
- }
107
 
108
- .brand-badge {
109
- display: inline-block;
110
- font-family: 'DM Sans', sans-serif;
111
- font-size: 0.65rem;
112
- font-weight: 500;
113
- letter-spacing: 0.22em;
114
- text-transform: uppercase;
115
- color: var(--gold);
116
- background: var(--gold-dim);
117
- border: 1px solid var(--border);
118
- border-radius: 100px;
119
- padding: 4px 14px;
120
- margin-bottom: 1.1rem;
121
  }
122
 
123
  .brand-title {
124
- font-family: 'Playfair Display', serif;
125
- font-size: clamp(2.6rem, 7vw, 4.2rem);
126
- font-weight: 900;
127
- line-height: 1;
128
- letter-spacing: -0.01em;
129
- background: linear-gradient(135deg, #f0d080 0%, #c9a84c 45%, #f0d080 100%);
130
- -webkit-background-clip: text;
131
- -webkit-text-fill-color: transparent;
132
- background-clip: text;
133
- background-size: 200% auto;
134
- animation: shimmer 4s linear infinite;
135
  }
136
 
137
  .brand-sub {
138
- margin-top: 0.6rem;
139
- font-size: 0.82rem;
140
- font-weight: 300;
141
- letter-spacing: 0.06em;
142
- color: var(--muted);
143
  }
144
 
145
- @keyframes shimmer {
146
- 0% { background-position: 0% center; }
147
- 100% { background-position: 200% center; }
 
 
 
 
 
 
 
148
  }
149
 
150
- /* ── Card ── */
151
  .card {
152
- width: 100%; max-width: 480px;
153
- background: var(--bg-card);
154
- border: 1px solid var(--border);
155
- border-radius: 20px;
156
- padding: 2.2rem 2rem;
157
- position: relative;
158
- backdrop-filter: blur(24px);
159
- box-shadow:
160
- 0 0 0 1px rgba(255,255,255,0.04) inset,
161
- 0 40px 80px rgba(0,0,0,0.6),
162
- 0 0 60px rgba(201,168,76,0.06);
163
- animation: fadeUp 0.7s ease 0.15s both;
164
  }
165
 
166
- /* Top gold accent line */
167
  .card::before {
168
- content: '';
169
- position: absolute;
170
- top: 0; left: 12%; right: 12%;
171
- height: 1px;
172
- background: linear-gradient(90deg, transparent, var(--gold), transparent);
173
- border-radius: 100px;
174
  }
175
 
176
- /* ── Input Field ── */
177
- .input-wrap {
178
- position: relative;
179
- margin-bottom: 1.2rem;
 
180
  }
181
 
182
- .input-icon {
183
- position: absolute;
184
- left: 16px; top: 50%;
185
- transform: translateY(-50%);
186
- color: var(--gold);
187
- font-size: 0.85rem;
188
- opacity: 0.7;
189
- pointer-events: none;
190
- transition: opacity 0.2s;
191
  }
 
 
192
 
193
- .url-input {
194
- width: 100%;
195
- background: rgba(255,255,255,0.04);
196
- border: 1px solid rgba(201,168,76,0.2);
197
- border-radius: 12px;
198
- padding: 14px 16px 14px 42px;
199
- font-family: 'DM Sans', sans-serif;
200
- font-size: 0.9rem;
201
- font-weight: 400;
202
- color: var(--text);
203
- outline: none;
204
- transition: border-color 0.25s, box-shadow 0.25s, background 0.25s;
205
- letter-spacing: 0.01em;
206
  }
207
-
208
- .url-input::placeholder { color: var(--muted); }
209
-
210
- .url-input:focus {
211
- border-color: var(--gold);
212
- background: rgba(201,168,76,0.06);
213
- box-shadow: 0 0 0 3px rgba(201,168,76,0.12);
214
  }
215
 
216
- .url-input:focus + .input-icon { opacity: 1; }
217
-
218
- /* ── Button ── */
219
- .dl-btn {
220
- width: 100%;
221
- padding: 15px;
222
- font-family: 'DM Sans', sans-serif;
223
- font-size: 0.92rem;
224
- font-weight: 500;
225
- letter-spacing: 0.04em;
226
- color: #0a0e1a;
227
- background: linear-gradient(135deg, #f0d080 0%, #c9a84c 50%, #e8c060 100%);
228
- background-size: 200% auto;
229
- border: none;
230
- border-radius: 12px;
231
- cursor: pointer;
232
- display: flex;
233
- align-items: center;
234
- justify-content: center;
235
- gap: 10px;
236
- transition: background-position 0.4s ease, transform 0.15s, box-shadow 0.2s;
237
- box-shadow: 0 4px 24px rgba(201,168,76,0.3);
238
- position: relative;
239
- overflow: hidden;
240
  }
241
-
242
- .dl-btn::after {
243
- content: '';
244
- position: absolute; inset: 0;
245
- background: rgba(255,255,255,0);
246
- transition: background 0.2s;
247
  }
248
-
249
- .dl-btn:hover {
250
- background-position: right center;
251
- box-shadow: 0 6px 32px rgba(201,168,76,0.45);
252
- transform: translateY(-1px);
253
  }
254
 
255
- .dl-btn:hover::after { background: rgba(255,255,255,0.08); }
256
- .dl-btn:active { transform: translateY(1px); box-shadow: 0 2px 12px rgba(201,168,76,0.3); }
 
 
 
 
 
 
 
 
 
 
 
257
 
258
- /* ── Spinner ── */
259
  .spinner {
260
- width: 16px; height: 16px;
261
- border: 2px solid rgba(0,0,0,0.2);
262
- border-top-color: #0a0e1a;
263
- border-radius: 50%;
264
- display: none;
265
- animation: spin 0.7s linear infinite;
266
- }
267
-
268
- @keyframes spin { to { transform: rotate(360deg); } }
269
-
270
- /* ── Platform Pills ── */
271
- .platforms {
272
- display: flex;
273
- gap: 8px;
274
- justify-content: center;
275
- margin-bottom: 1.6rem;
276
- flex-wrap: wrap;
277
- }
278
-
279
- .pill {
280
- display: inline-flex;
281
- align-items: center;
282
- gap: 5px;
283
- font-size: 0.7rem;
284
- font-weight: 500;
285
- letter-spacing: 0.05em;
286
- padding: 4px 11px;
287
- border-radius: 100px;
288
- background: rgba(255,255,255,0.04);
289
- border: 1px solid rgba(255,255,255,0.08);
290
- color: var(--muted);
291
- transition: color 0.2s, border-color 0.2s;
292
  }
293
 
294
- .pill i { font-size: 0.65rem; }
295
-
296
- .pill:hover {
297
- color: var(--gold-light);
298
- border-color: var(--border);
299
- }
300
-
301
- /* ── Error ── */
302
  .error-box {
303
- margin-top: 1.2rem;
304
- background: rgba(180, 50, 50, 0.12);
305
- border: 1px solid rgba(220, 60, 60, 0.25);
306
- border-radius: 10px;
307
- padding: 12px 16px;
308
- font-size: 0.82rem;
309
- color: #f87171;
310
- display: flex;
311
- align-items: flex-start;
312
- gap: 10px;
313
- animation: fadeUp 0.3s ease both;
314
  }
315
 
316
- .error-box i { margin-top: 2px; flex-shrink: 0; }
317
-
318
- /* ── Footer note ── */
319
  .footer-note {
320
- margin-top: 1.6rem;
321
- text-align: center;
322
- font-size: 0.72rem;
323
- color: var(--muted);
324
- letter-spacing: 0.02em;
325
- opacity: 0.7;
326
- }
327
-
328
- .footer-note span { color: var(--gold); opacity: 0.7; }
329
-
330
- /* ── Section divider ── */
331
- .divider {
332
- display: flex;
333
- align-items: center;
334
- gap: 10px;
335
- margin-bottom: 1.4rem;
336
- opacity: 0.35;
337
- }
338
- .divider-line { flex: 1; height: 1px; background: var(--border); }
339
- .divider-dot { width: 4px; height: 4px; background: var(--gold); border-radius: 50%; }
340
-
341
- /* ── Animations ── */
342
- @keyframes fadeDown {
343
- from { opacity: 0; transform: translateY(-18px); }
344
- to { opacity: 1; transform: translateY(0); }
345
- }
346
- @keyframes fadeUp {
347
- from { opacity: 0; transform: translateY(16px); }
348
- to { opacity: 1; transform: translateY(0); }
349
- }
350
-
351
- /* ── Quality badge ── */
352
- .quality-row {
353
- display: flex;
354
- gap: 8px;
355
- margin-bottom: 1.3rem;
356
- }
357
-
358
- .q-tag {
359
- flex: 1;
360
- text-align: center;
361
- padding: 7px 6px;
362
- background: rgba(255,255,255,0.03);
363
- border: 1px solid rgba(255,255,255,0.07);
364
- border-radius: 8px;
365
- font-size: 0.68rem;
366
- font-weight: 500;
367
- letter-spacing: 0.06em;
368
- color: var(--muted);
369
- text-transform: uppercase;
370
- }
371
- .q-tag strong {
372
- display: block;
373
- font-size: 0.78rem;
374
- color: var(--gold-light);
375
- margin-bottom: 2px;
376
- font-weight: 700;
377
  }
 
378
  </style>
379
  </head>
380
-
381
  <body>
382
- <!-- Animated background -->
383
  <div class="bg-scene">
384
  <div class="orb orb1"></div>
385
  <div class="orb orb2"></div>
@@ -389,53 +255,37 @@ HTML_CODE = """
389
 
390
  <div class="page-wrap">
391
 
392
- <!-- Brand -->
393
  <div class="brand">
394
- <div class="brand-badge">&#x2605; Premium Downloader</div>
395
  <div class="brand-title">UniLoader</div>
396
- <div class="brand-sub">Crafted for quality &nbsp;&bull;&nbsp; HF Edition</div>
 
397
  </div>
398
 
399
- <!-- Card -->
400
  <div class="card">
401
 
402
- <!-- Platform pills -->
403
  <div class="platforms">
404
- <span class="pill"><i class="fab fa-youtube"></i> YouTube</span>
405
- <span class="pill"><i class="fab fa-tiktok"></i> TikTok</span>
406
- <span class="pill"><i class="fab fa-instagram"></i> Instagram</span>
407
- <span class="pill"><i class="fas fa-video"></i> 1000+ Sites</span>
 
408
  </div>
409
 
410
- <!-- Quality tags -->
411
  <div class="quality-row">
412
  <div class="q-tag"><strong>720p</strong>Max Quality</div>
413
  <div class="q-tag"><strong>MP4</strong>Format</div>
414
- <div class="q-tag"><strong>Fast</strong>Download</div>
415
- </div>
416
-
417
- <div class="divider">
418
- <div class="divider-line"></div>
419
- <div class="divider-dot"></div>
420
- <div class="divider-line"></div>
421
  </div>
422
 
423
- <!-- Form -->
424
- <form method="POST" action="/download"
425
- onsubmit="handleSubmit()">
426
 
 
427
  <div class="input-wrap">
428
- <input
429
- type="url"
430
- name="url"
431
- required
432
- class="url-input"
433
- placeholder="Paste your video link here…"
434
- autocomplete="off"
435
- >
436
  <i class="fas fa-link input-icon"></i>
437
  </div>
438
-
439
  <button type="submit" class="dl-btn" id="dlBtn">
440
  <i class="fas fa-download" id="btnIcon"></i>
441
  <span id="btnText">Download Best Quality</span>
@@ -453,22 +303,17 @@ HTML_CODE = """
453
  </div>
454
 
455
  <p class="footer-note">
456
- TikTok HD on Hugging Face may be limited by platform.
457
- &nbsp;|&nbsp; <span>Use responsibly.</span>
458
  </p>
459
-
460
  </div>
461
 
462
  <script>
463
  function handleSubmit() {
464
- const btn = document.getElementById('dlBtn');
465
  const icon = document.getElementById('btnIcon');
466
  const text = document.getElementById('btnText');
467
  const spin = document.getElementById('spinner');
468
-
469
  btn.disabled = true;
470
- btn.style.opacity = '0.75';
471
- btn.style.cursor = 'wait';
472
  icon.style.display = 'none';
473
  text.textContent = 'Preparing download…';
474
  spin.style.display = 'block';
@@ -478,58 +323,108 @@ HTML_CODE = """
478
  </html>
479
  """
480
 
481
- # ---------------- ROUTES ----------------
 
 
482
  @app.route("/", methods=["GET"])
483
  def index():
484
  return render_template_string(HTML_CODE)
485
 
 
486
  @app.route("/download", methods=["POST"])
487
  def download():
488
- url = request.form.get("url")
489
- filename = f"video_{uuid.uuid4().hex[:6]}.mp4"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
 
491
- try:
492
- ydl_opts = {
493
- # πŸ”₯ BEST POSSIBLE FORMAT (HF SAFE)
494
- "format": (
495
- "bv*[ext=mp4][height<=720]/"
496
- "bv*[height<=720]/"
497
- "best[height<=720]/best"
498
- ),
499
-
500
- "outtmpl": filename,
501
- "merge_output_format": "mp4",
502
-
503
- # πŸ”₯ Browser-like headers (slight quality improvement)
504
- "http_headers": {
505
- "User-Agent":
506
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
507
- "Accept-Language": "en-US,en;q=0.9",
508
- },
509
-
510
- "quiet": True,
511
- "no_warnings": True,
512
- "nocheckcertificate": True,
513
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
514
 
 
515
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
516
  ydl.download([url])
517
 
 
 
 
518
  @after_this_request
519
  def cleanup(resp):
520
  try:
521
  if os.path.exists(filename):
522
  os.remove(filename)
523
- except:
524
  pass
525
  return resp
526
 
527
  return send_file(filename, as_attachment=True)
528
 
529
  except Exception as e:
530
- return render_template_string(HTML_CODE, error=str(e))
531
-
532
-
533
- # ---------------- RUN ----------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
534
  if __name__ == "__main__":
535
- app.run(host="0.0.0.0", port=7860)
 
5
 
6
  app = Flask(__name__)
7
 
 
8
  HTML_CODE = """
9
  <!DOCTYPE html>
10
  <html lang="en">
11
  <head>
12
  <meta charset="UTF-8">
13
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
+ <title>UniLoader β€” Made by Deepu</title>
15
  <link rel="preconnect" href="https://fonts.googleapis.com">
16
+ <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@700;900&family=Sora:wght@300;400;500;600;700&display=swap" rel="stylesheet">
17
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
18
  <style>
19
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
20
 
21
  :root {
22
+ --bg: #04060f;
23
+ --card: rgba(8, 12, 26, 0.92);
24
+ --text: #e2e8f0;
25
+ --muted:#4a5568;
 
 
 
 
26
  }
27
 
28
  html, body {
29
  min-height: 100vh;
30
+ background: var(--bg);
31
+ font-family: 'Sora', sans-serif;
32
  color: var(--text);
33
  overflow-x: hidden;
34
  }
35
 
36
+ /* ══ RGB Animations ══ */
37
+ @keyframes rgb-glow {
38
+ 0% { color:#ff0080; text-shadow:0 0 14px #ff0080,0 0 30px #ff0080; }
39
+ 16% { color:#ff8c00; text-shadow:0 0 14px #ff8c00,0 0 30px #ff8c00; }
40
+ 33% { color:#ffe600; text-shadow:0 0 14px #ffe600,0 0 30px #ffe600; }
41
+ 50% { color:#00ff88; text-shadow:0 0 14px #00ff88,0 0 30px #00ff88; }
42
+ 66% { color:#00c8ff; text-shadow:0 0 14px #00c8ff,0 0 30px #00c8ff; }
43
+ 83% { color:#a855f7; text-shadow:0 0 14px #a855f7,0 0 30px #a855f7; }
44
+ 100% { color:#ff0080; text-shadow:0 0 14px #ff0080,0 0 30px #ff0080; }
45
+ }
46
+ @keyframes rgb-border {
47
+ 0% { border-color:#ff0080; box-shadow:0 0 14px #ff0080,inset 0 0 8px rgba(255,0,128,.07); }
48
+ 16% { border-color:#ff8c00; box-shadow:0 0 14px #ff8c00,inset 0 0 8px rgba(255,140,0,.07); }
49
+ 33% { border-color:#ffe600; box-shadow:0 0 14px #ffe600,inset 0 0 8px rgba(255,230,0,.07); }
50
+ 50% { border-color:#00ff88; box-shadow:0 0 14px #00ff88,inset 0 0 8px rgba(0,255,136,.07); }
51
+ 66% { border-color:#00c8ff; box-shadow:0 0 14px #00c8ff,inset 0 0 8px rgba(0,200,255,.07); }
52
+ 83% { border-color:#a855f7; box-shadow:0 0 14px #a855f7,inset 0 0 8px rgba(168,85,247,.07); }
53
+ 100% { border-color:#ff0080; box-shadow:0 0 14px #ff0080,inset 0 0 8px rgba(255,0,128,.07); }
54
  }
55
+ @keyframes shimmer {
56
+ 0% { background-position:0% center; }
57
+ 100% { background-position:200% center; }
 
 
 
 
58
  }
59
+ @keyframes line-move {
60
+ 0% { background-position:0% 50%; }
61
+ 50% { background-position:100% 50%; }
62
+ 100% { background-position:0% 50%; }
 
63
  }
64
+ @keyframes drift {
65
+ 0% { transform:translate(0,0) scale(1); }
66
+ 50% { transform:translate(28px,-38px) scale(1.07); }
67
+ 100% { transform:translate(-18px,28px) scale(.95); }
 
 
68
  }
69
+ @keyframes fadeDown {
70
+ from { opacity:0; transform:translateY(-20px); }
71
+ to { opacity:1; transform:translateY(0); }
 
 
 
 
72
  }
73
+ @keyframes fadeUp {
74
+ from { opacity:0; transform:translateY(18px); }
75
+ to { opacity:1; transform:translateY(0); }
 
 
76
  }
77
+ @keyframes spin { to { transform:rotate(360deg); } }
78
+ @keyframes pulse-badge {
79
+ 0%,100% { transform:scale(1); box-shadow:0 0 18px rgba(0,200,255,.35); }
80
+ 50% { transform:scale(1.03); box-shadow:0 0 36px rgba(168,85,247,.55); }
81
+ }
82
+
83
+ /* ══ Background ══ */
84
+ .bg-scene { position:fixed; inset:0; z-index:0; overflow:hidden; pointer-events:none; }
85
+ .orb { position:absolute; border-radius:50%; filter:blur(90px); opacity:.28; animation:drift 20s ease-in-out infinite alternate; }
86
+ .orb1 { width:520px;height:520px; background:radial-gradient(circle,#1a0050,transparent 70%); top:-200px;left:-150px; animation-duration:24s; }
87
+ .orb2 { width:360px;height:360px; background:radial-gradient(circle,#003060,transparent 70%); bottom:-120px;right:-100px; animation-duration:18s;animation-delay:-9s; }
88
+ .orb3 { width:260px;height:260px; background:radial-gradient(circle,#004030,transparent 70%); top:50%;left:50%; opacity:.15; animation-duration:28s;animation-delay:-14s; }
89
 
 
90
  .bg-grid {
91
+ position:fixed; inset:0; z-index:0;
92
  background-image:
93
+ linear-gradient(rgba(0,200,255,.025) 1px, transparent 1px),
94
+ linear-gradient(90deg, rgba(0,200,255,.025) 1px, transparent 1px);
95
+ background-size:56px 56px;
96
  }
97
 
98
+ /* ══ Layout ══ */
99
  .page-wrap {
100
+ position:relative; z-index:1;
101
+ min-height:100vh;
102
+ display:flex; flex-direction:column;
103
+ align-items:center; justify-content:center;
104
+ padding:2.5rem 1rem;
105
  }
106
 
107
+ /* ══ Brand ══ */
108
+ .brand { text-align:center; margin-bottom:2.4rem; animation:fadeDown .7s ease both; }
 
 
 
 
109
 
110
+ .brand-eyebrow {
111
+ font-size:.63rem; font-weight:600;
112
+ letter-spacing:.22em; text-transform:uppercase;
113
+ color:#64748b; margin-bottom:.9rem;
 
 
 
 
 
 
 
 
 
114
  }
115
 
116
  .brand-title {
117
+ font-family:'Orbitron', sans-serif;
118
+ font-size:clamp(2.4rem,7vw,3.8rem);
119
+ font-weight:900; letter-spacing:.04em; line-height:1;
120
+ animation:rgb-glow 3s linear infinite;
121
+ margin-bottom:.7rem;
 
 
 
 
 
 
122
  }
123
 
124
  .brand-sub {
125
+ font-size:.82rem; font-weight:300;
126
+ letter-spacing:.06em; color:var(--muted);
127
+ margin-bottom:1.1rem;
 
 
128
  }
129
 
130
+ .made-badge {
131
+ display:inline-block;
132
+ font-family:'Orbitron', sans-serif;
133
+ font-size:.68rem; font-weight:700;
134
+ letter-spacing:.3em; text-transform:uppercase;
135
+ padding:7px 26px; border:2px solid #00c8ff; border-radius:50px;
136
+ animation:rgb-glow 3s linear infinite,
137
+ rgb-border 3s linear infinite,
138
+ pulse-badge 3s ease-in-out infinite;
139
+ background:rgba(0,0,0,.15); cursor:default;
140
  }
141
 
142
+ /* ══ Card ══ */
143
  .card {
144
+ width:100%; max-width:460px;
145
+ background:var(--card);
146
+ border:1px solid rgba(255,255,255,.07);
147
+ border-radius:22px; padding:2.2rem 2rem;
148
+ position:relative; backdrop-filter:blur(28px);
149
+ box-shadow:0 40px 80px rgba(0,0,0,.65),
150
+ 0 0 0 1px rgba(255,255,255,.04) inset;
151
+ animation:fadeUp .7s ease .15s both; overflow:hidden;
 
 
 
 
152
  }
153
 
154
+ /* Rainbow top bar on card */
155
  .card::before {
156
+ content:''; position:absolute; top:0;left:0;right:0; height:2px;
157
+ background:linear-gradient(90deg,#ff0080,#ff8c00,#ffe600,#00ff88,#00c8ff,#a855f7,#ff0080);
158
+ background-size:200% auto; animation:shimmer 4s linear infinite;
 
 
 
159
  }
160
 
161
+ /* ══ RGB divider ══ */
162
+ .rgb-line {
163
+ height:1px; margin:1.4rem 0; opacity:.45;
164
+ background:linear-gradient(90deg,transparent,#00c8ff,#a855f7,#ff0080,transparent);
165
+ background-size:200% 200%; animation:line-move 4s ease infinite; border-radius:2px;
166
  }
167
 
168
+ /* ══ Platform Pills ══ */
169
+ .platforms { display:flex;gap:7px;justify-content:center;flex-wrap:wrap;margin-bottom:1.3rem; }
170
+ .pill {
171
+ display:inline-flex;align-items:center;gap:5px;
172
+ font-size:.68rem;font-weight:600;letter-spacing:.04em;
173
+ padding:4px 12px;border-radius:100px;
174
+ background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);
175
+ color:#64748b;transition:color .2s,border-color .2s,background .2s;cursor:default;
 
176
  }
177
+ .pill.ok { color:#00ff88;border-color:rgba(0,255,136,.25); }
178
+ .pill.warn { color:#fbbf24;border-color:rgba(251,191,36,.25); }
179
 
180
+ /* ══ Quality Tags ══ */
181
+ .quality-row { display:flex;gap:8px;margin-bottom:1.3rem; }
182
+ .q-tag {
183
+ flex:1;text-align:center;padding:8px 6px;
184
+ background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.06);
185
+ border-radius:10px;font-size:.66rem;font-weight:500;
186
+ letter-spacing:.06em;color:var(--muted);text-transform:uppercase;
 
 
 
 
 
 
187
  }
188
+ .q-tag strong {
189
+ display:block;font-family:'Orbitron',sans-serif;
190
+ font-size:.72rem;color:#00c8ff;margin-bottom:2px;font-weight:700;
 
 
 
 
191
  }
192
 
193
+ /* ══ Input ══ */
194
+ .input-wrap { position:relative;margin-bottom:1.1rem; }
195
+ .input-icon {
196
+ position:absolute;left:16px;top:50%;transform:translateY(-50%);
197
+ color:#00c8ff;font-size:.82rem;opacity:.6;pointer-events:none;transition:opacity .2s;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  }
199
+ .url-input {
200
+ width:100%;background:rgba(255,255,255,.04);
201
+ border:1px solid rgba(255,255,255,.09);border-radius:12px;
202
+ padding:14px 16px 14px 42px;font-family:'Sora',sans-serif;
203
+ font-size:.9rem;color:var(--text);outline:none;
204
+ transition:border-color .25s,box-shadow .25s,background .25s;
205
  }
206
+ .url-input::placeholder { color:var(--muted); }
207
+ .url-input:focus {
208
+ border-color:#00c8ff;background:rgba(0,200,255,.05);
209
+ box-shadow:0 0 0 3px rgba(0,200,255,.12);
 
210
  }
211
 
212
+ /* ══ Button ══ */
213
+ .dl-btn {
214
+ width:100%;padding:15px;font-family:'Sora',sans-serif;
215
+ font-size:.92rem;font-weight:700;letter-spacing:.05em;color:#fff;
216
+ background:linear-gradient(135deg,#0ea5e9,#2563eb,#7c3aed);
217
+ background-size:200% auto;border:none;border-radius:12px;cursor:pointer;
218
+ display:flex;align-items:center;justify-content:center;gap:10px;
219
+ transition:background-position .4s,transform .15s,box-shadow .2s;
220
+ box-shadow:0 4px 24px rgba(14,165,233,.3);position:relative;overflow:hidden;
221
+ }
222
+ .dl-btn:hover { background-position:right center;box-shadow:0 6px 32px rgba(14,165,233,.5);transform:translateY(-1px); }
223
+ .dl-btn:active { transform:translateY(1px); }
224
+ .dl-btn:disabled { opacity:.65;cursor:wait; }
225
 
 
226
  .spinner {
227
+ width:16px;height:16px;border:2px solid rgba(255,255,255,.2);
228
+ border-top-color:#fff;border-radius:50%;display:none;
229
+ animation:spin .7s linear infinite;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  }
231
 
232
+ /* ══ Error ══ */
 
 
 
 
 
 
 
233
  .error-box {
234
+ margin-top:1.2rem;background:rgba(180,50,50,.1);
235
+ border:1px solid rgba(220,60,60,.25);border-radius:10px;
236
+ padding:12px 16px;font-size:.82rem;color:#f87171;
237
+ display:flex;align-items:flex-start;gap:10px;animation:fadeUp .3s ease both;
 
 
 
 
 
 
 
238
  }
239
 
240
+ /* ══ Footer ══ */
 
 
241
  .footer-note {
242
+ margin-top:1.8rem;text-align:center;
243
+ font-size:.7rem;color:var(--muted);opacity:.65;letter-spacing:.03em;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  }
245
+ .footer-name { animation:rgb-glow 3s linear infinite; }
246
  </style>
247
  </head>
 
248
  <body>
 
249
  <div class="bg-scene">
250
  <div class="orb orb1"></div>
251
  <div class="orb orb2"></div>
 
255
 
256
  <div class="page-wrap">
257
 
 
258
  <div class="brand">
259
+ <div class="brand-eyebrow">⚑ Universal Video Downloader</div>
260
  <div class="brand-title">UniLoader</div>
261
+ <div class="brand-sub">Download anything. Instantly.</div>
262
+ <div class="made-badge">✦ Made by Deepu ✦</div>
263
  </div>
264
 
 
265
  <div class="card">
266
 
 
267
  <div class="platforms">
268
+ <span class="pill ok"><i class="fab fa-youtube"></i> YouTube</span>
269
+ <span class="pill ok"><i class="fab fa-tiktok"></i> TikTok</span>
270
+ <span class="pill warn"><i class="fab fa-instagram"></i> Instagram</span>
271
+ <span class="pill ok"><i class="fab fa-facebook"></i> Facebook</span>
272
+ <span class="pill ok"><i class="fas fa-video"></i> 1000+ Sites</span>
273
  </div>
274
 
 
275
  <div class="quality-row">
276
  <div class="q-tag"><strong>720p</strong>Max Quality</div>
277
  <div class="q-tag"><strong>MP4</strong>Format</div>
278
+ <div class="q-tag"><strong>Fast</strong>Direct</div>
 
 
 
 
 
 
279
  </div>
280
 
281
+ <div class="rgb-line"></div>
 
 
282
 
283
+ <form method="POST" action="/download" onsubmit="handleSubmit()">
284
  <div class="input-wrap">
285
+ <input type="url" name="url" required class="url-input"
286
+ placeholder="Paste video link here…" autocomplete="off">
 
 
 
 
 
 
287
  <i class="fas fa-link input-icon"></i>
288
  </div>
 
289
  <button type="submit" class="dl-btn" id="dlBtn">
290
  <i class="fas fa-download" id="btnIcon"></i>
291
  <span id="btnText">Download Best Quality</span>
 
303
  </div>
304
 
305
  <p class="footer-note">
306
+ For personal use only &nbsp;|&nbsp; <span class="footer-name">✦ Made by Deepu ✦</span>
 
307
  </p>
 
308
  </div>
309
 
310
  <script>
311
  function handleSubmit() {
312
+ const btn = document.getElementById('dlBtn');
313
  const icon = document.getElementById('btnIcon');
314
  const text = document.getElementById('btnText');
315
  const spin = document.getElementById('spinner');
 
316
  btn.disabled = true;
 
 
317
  icon.style.display = 'none';
318
  text.textContent = 'Preparing download…';
319
  spin.style.display = 'block';
 
323
  </html>
324
  """
325
 
326
+ # ──────────────────────────────────────────
327
+ # ROUTES
328
+ # ──────────────────────────────────────────
329
  @app.route("/", methods=["GET"])
330
  def index():
331
  return render_template_string(HTML_CODE)
332
 
333
+
334
  @app.route("/download", methods=["POST"])
335
  def download():
336
+ url = request.form.get("url", "").strip()
337
+ filename = f"video_{uuid.uuid4().hex[:8]}.mp4"
338
+
339
+ is_youtube = any(x in url for x in ["youtube.com", "youtu.be"])
340
+ is_instagram = "instagram.com" in url
341
+ is_tiktok = "tiktok.com" in url
342
+
343
+ ydl_opts = {
344
+ "format": (
345
+ "bv*[ext=mp4][height<=720]/"
346
+ "bv*[height<=720]/"
347
+ "best[height<=720]/best"
348
+ ),
349
+ "outtmpl" : filename,
350
+ "merge_output_format": "mp4",
351
+ "quiet" : True,
352
+ "no_warnings" : True,
353
+ "nocheckcertificate" : True,
354
+ "retries" : 5,
355
+ "fragment_retries" : 5,
356
+ "http_headers": {
357
+ "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
358
+ "Accept-Language": "en-US,en;q=0.9",
359
+ },
360
+ }
361
 
362
+ # YouTube β€” Android client bypass (avoids bot detection)
363
+ if is_youtube:
364
+ ydl_opts["extractor_args"] = {
365
+ "youtube": {
366
+ "player_client": ["android", "ios", "web"],
367
+ "player_skip" : ["webpage"],
368
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  }
370
+ ydl_opts["http_headers"]["User-Agent"] = (
371
+ "com.google.android.youtube/17.36.4 (Linux; U; Android 12; GB) gzip"
372
+ )
373
+
374
+ # Instagram β€” real app headers
375
+ if is_instagram:
376
+ ydl_opts["http_headers"] = {
377
+ "User-Agent" : "Instagram 219.0.0.12.117 Android (31/12; 420dpi; 1080x2400; samsung; SM-G991B; o1s; exynos2100; en_US; 346877738)",
378
+ "Accept" : "*/*",
379
+ "Accept-Language" : "en-US",
380
+ "X-IG-App-ID" : "936619743392459",
381
+ }
382
+
383
+ # TikTok
384
+ if is_tiktok:
385
+ ydl_opts["http_headers"]["User-Agent"] = (
386
+ "TikTok 26.2.0 rv:262018 (iPhone; iOS 14.4.2; en_US) Cronet"
387
+ )
388
 
389
+ try:
390
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
391
  ydl.download([url])
392
 
393
+ if not os.path.exists(filename):
394
+ raise FileNotFoundError("Download completed but output file not found.")
395
+
396
  @after_this_request
397
  def cleanup(resp):
398
  try:
399
  if os.path.exists(filename):
400
  os.remove(filename)
401
+ except Exception:
402
  pass
403
  return resp
404
 
405
  return send_file(filename, as_attachment=True)
406
 
407
  except Exception as e:
408
+ if os.path.exists(filename):
409
+ os.remove(filename)
410
+
411
+ err = str(e).lower()
412
+ if "sign in" in err or "login" in err or "private" in err:
413
+ msg = "This video is private or requires login."
414
+ elif "429" in err or "too many" in err:
415
+ msg = "Rate limited. Please wait a few minutes and try again."
416
+ elif "instagram" in err:
417
+ msg = "Instagram blocked this download. Try uploading/downloading the video manually."
418
+ elif "copyright" in err or "blocked" in err:
419
+ msg = "This video is blocked or restricted."
420
+ else:
421
+ msg = str(e)
422
+
423
+ return render_template_string(HTML_CODE, error=msg)
424
+
425
+
426
+ # ──────────────────────────────────────────
427
+ # RUN
428
+ # ──────────────────────────────────────────
429
  if __name__ == "__main__":
430
+ app.run(host="0.0.0.0", port=7860)