Titok / app.py
Offex's picture
Update app.py
f0a1d36 verified
Raw
History Blame Contribute Delete
16.2 kB
from flask import Flask, render_template_string, request, send_file, after_this_request
import yt_dlp
import os
import uuid
app = Flask(__name__)
HTML_CODE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UniLoader β€” Made by Deepu</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@700;900&family=Sora:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #04060f;
--card: rgba(8, 12, 26, 0.92);
--text: #e2e8f0;
--muted:#4a5568;
}
html, body {
min-height: 100vh;
background: var(--bg);
font-family: 'Sora', sans-serif;
color: var(--text);
overflow-x: hidden;
}
/* ══ RGB Animations ══ */
@keyframes rgb-glow {
0% { color:#ff0080; text-shadow:0 0 14px #ff0080,0 0 30px #ff0080; }
16% { color:#ff8c00; text-shadow:0 0 14px #ff8c00,0 0 30px #ff8c00; }
33% { color:#ffe600; text-shadow:0 0 14px #ffe600,0 0 30px #ffe600; }
50% { color:#00ff88; text-shadow:0 0 14px #00ff88,0 0 30px #00ff88; }
66% { color:#00c8ff; text-shadow:0 0 14px #00c8ff,0 0 30px #00c8ff; }
83% { color:#a855f7; text-shadow:0 0 14px #a855f7,0 0 30px #a855f7; }
100% { color:#ff0080; text-shadow:0 0 14px #ff0080,0 0 30px #ff0080; }
}
@keyframes rgb-border {
0% { border-color:#ff0080; box-shadow:0 0 14px #ff0080,inset 0 0 8px rgba(255,0,128,.07); }
16% { border-color:#ff8c00; box-shadow:0 0 14px #ff8c00,inset 0 0 8px rgba(255,140,0,.07); }
33% { border-color:#ffe600; box-shadow:0 0 14px #ffe600,inset 0 0 8px rgba(255,230,0,.07); }
50% { border-color:#00ff88; box-shadow:0 0 14px #00ff88,inset 0 0 8px rgba(0,255,136,.07); }
66% { border-color:#00c8ff; box-shadow:0 0 14px #00c8ff,inset 0 0 8px rgba(0,200,255,.07); }
83% { border-color:#a855f7; box-shadow:0 0 14px #a855f7,inset 0 0 8px rgba(168,85,247,.07); }
100% { border-color:#ff0080; box-shadow:0 0 14px #ff0080,inset 0 0 8px rgba(255,0,128,.07); }
}
@keyframes shimmer {
0% { background-position:0% center; }
100% { background-position:200% center; }
}
@keyframes line-move {
0% { background-position:0% 50%; }
50% { background-position:100% 50%; }
100% { background-position:0% 50%; }
}
@keyframes drift {
0% { transform:translate(0,0) scale(1); }
50% { transform:translate(28px,-38px) scale(1.07); }
100% { transform:translate(-18px,28px) scale(.95); }
}
@keyframes fadeDown {
from { opacity:0; transform:translateY(-20px); }
to { opacity:1; transform:translateY(0); }
}
@keyframes fadeUp {
from { opacity:0; transform:translateY(18px); }
to { opacity:1; transform:translateY(0); }
}
@keyframes spin { to { transform:rotate(360deg); } }
@keyframes pulse-badge {
0%,100% { transform:scale(1); box-shadow:0 0 18px rgba(0,200,255,.35); }
50% { transform:scale(1.03); box-shadow:0 0 36px rgba(168,85,247,.55); }
}
/* ══ Background ══ */
.bg-scene { position:fixed; inset:0; z-index:0; overflow:hidden; pointer-events:none; }
.orb { position:absolute; border-radius:50%; filter:blur(90px); opacity:.28; animation:drift 20s ease-in-out infinite alternate; }
.orb1 { width:520px;height:520px; background:radial-gradient(circle,#1a0050,transparent 70%); top:-200px;left:-150px; animation-duration:24s; }
.orb2 { width:360px;height:360px; background:radial-gradient(circle,#003060,transparent 70%); bottom:-120px;right:-100px; animation-duration:18s;animation-delay:-9s; }
.orb3 { width:260px;height:260px; background:radial-gradient(circle,#004030,transparent 70%); top:50%;left:50%; opacity:.15; animation-duration:28s;animation-delay:-14s; }
.bg-grid {
position:fixed; inset:0; z-index:0;
background-image:
linear-gradient(rgba(0,200,255,.025) 1px, transparent 1px),
linear-gradient(90deg, rgba(0,200,255,.025) 1px, transparent 1px);
background-size:56px 56px;
}
/* ══ Layout ══ */
.page-wrap {
position:relative; z-index:1;
min-height:100vh;
display:flex; flex-direction:column;
align-items:center; justify-content:center;
padding:2.5rem 1rem;
}
/* ══ Brand ══ */
.brand { text-align:center; margin-bottom:2.4rem; animation:fadeDown .7s ease both; }
.brand-eyebrow {
font-size:.63rem; font-weight:600;
letter-spacing:.22em; text-transform:uppercase;
color:#64748b; margin-bottom:.9rem;
}
.brand-title {
font-family:'Orbitron', sans-serif;
font-size:clamp(2.4rem,7vw,3.8rem);
font-weight:900; letter-spacing:.04em; line-height:1;
animation:rgb-glow 3s linear infinite;
margin-bottom:.7rem;
}
.brand-sub {
font-size:.82rem; font-weight:300;
letter-spacing:.06em; color:var(--muted);
margin-bottom:1.1rem;
}
.made-badge {
display:inline-block;
font-family:'Orbitron', sans-serif;
font-size:.68rem; font-weight:700;
letter-spacing:.3em; text-transform:uppercase;
padding:7px 26px; border:2px solid #00c8ff; border-radius:50px;
animation:rgb-glow 3s linear infinite,
rgb-border 3s linear infinite,
pulse-badge 3s ease-in-out infinite;
background:rgba(0,0,0,.15); cursor:default;
}
/* ══ Card ══ */
.card {
width:100%; max-width:460px;
background:var(--card);
border:1px solid rgba(255,255,255,.07);
border-radius:22px; padding:2.2rem 2rem;
position:relative; backdrop-filter:blur(28px);
box-shadow:0 40px 80px rgba(0,0,0,.65),
0 0 0 1px rgba(255,255,255,.04) inset;
animation:fadeUp .7s ease .15s both; overflow:hidden;
}
/* Rainbow top bar on card */
.card::before {
content:''; position:absolute; top:0;left:0;right:0; height:2px;
background:linear-gradient(90deg,#ff0080,#ff8c00,#ffe600,#00ff88,#00c8ff,#a855f7,#ff0080);
background-size:200% auto; animation:shimmer 4s linear infinite;
}
/* ══ RGB divider ══ */
.rgb-line {
height:1px; margin:1.4rem 0; opacity:.45;
background:linear-gradient(90deg,transparent,#00c8ff,#a855f7,#ff0080,transparent);
background-size:200% 200%; animation:line-move 4s ease infinite; border-radius:2px;
}
/* ══ Platform Pills ══ */
.platforms { display:flex;gap:7px;justify-content:center;flex-wrap:wrap;margin-bottom:1.3rem; }
.pill {
display:inline-flex;align-items:center;gap:5px;
font-size:.68rem;font-weight:600;letter-spacing:.04em;
padding:4px 12px;border-radius:100px;
background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);
color:#64748b;transition:color .2s,border-color .2s,background .2s;cursor:default;
}
.pill.ok { color:#00ff88;border-color:rgba(0,255,136,.25); }
.pill.warn { color:#fbbf24;border-color:rgba(251,191,36,.25); }
/* ══ Quality Tags ══ */
.quality-row { display:flex;gap:8px;margin-bottom:1.3rem; }
.q-tag {
flex:1;text-align:center;padding:8px 6px;
background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.06);
border-radius:10px;font-size:.66rem;font-weight:500;
letter-spacing:.06em;color:var(--muted);text-transform:uppercase;
}
.q-tag strong {
display:block;font-family:'Orbitron',sans-serif;
font-size:.72rem;color:#00c8ff;margin-bottom:2px;font-weight:700;
}
/* ══ Input ══ */
.input-wrap { position:relative;margin-bottom:1.1rem; }
.input-icon {
position:absolute;left:16px;top:50%;transform:translateY(-50%);
color:#00c8ff;font-size:.82rem;opacity:.6;pointer-events:none;transition:opacity .2s;
}
.url-input {
width:100%;background:rgba(255,255,255,.04);
border:1px solid rgba(255,255,255,.09);border-radius:12px;
padding:14px 16px 14px 42px;font-family:'Sora',sans-serif;
font-size:.9rem;color:var(--text);outline:none;
transition:border-color .25s,box-shadow .25s,background .25s;
}
.url-input::placeholder { color:var(--muted); }
.url-input:focus {
border-color:#00c8ff;background:rgba(0,200,255,.05);
box-shadow:0 0 0 3px rgba(0,200,255,.12);
}
/* ══ Button ══ */
.dl-btn {
width:100%;padding:15px;font-family:'Sora',sans-serif;
font-size:.92rem;font-weight:700;letter-spacing:.05em;color:#fff;
background:linear-gradient(135deg,#0ea5e9,#2563eb,#7c3aed);
background-size:200% auto;border:none;border-radius:12px;cursor:pointer;
display:flex;align-items:center;justify-content:center;gap:10px;
transition:background-position .4s,transform .15s,box-shadow .2s;
box-shadow:0 4px 24px rgba(14,165,233,.3);position:relative;overflow:hidden;
}
.dl-btn:hover { background-position:right center;box-shadow:0 6px 32px rgba(14,165,233,.5);transform:translateY(-1px); }
.dl-btn:active { transform:translateY(1px); }
.dl-btn:disabled { opacity:.65;cursor:wait; }
.spinner {
width:16px;height:16px;border:2px solid rgba(255,255,255,.2);
border-top-color:#fff;border-radius:50%;display:none;
animation:spin .7s linear infinite;
}
/* ══ Error ══ */
.error-box {
margin-top:1.2rem;background:rgba(180,50,50,.1);
border:1px solid rgba(220,60,60,.25);border-radius:10px;
padding:12px 16px;font-size:.82rem;color:#f87171;
display:flex;align-items:flex-start;gap:10px;animation:fadeUp .3s ease both;
}
/* ══ Footer ══ */
.footer-note {
margin-top:1.8rem;text-align:center;
font-size:.7rem;color:var(--muted);opacity:.65;letter-spacing:.03em;
}
.footer-name { animation:rgb-glow 3s linear infinite; }
</style>
</head>
<body>
<div class="bg-scene">
<div class="orb orb1"></div>
<div class="orb orb2"></div>
<div class="orb orb3"></div>
</div>
<div class="bg-grid"></div>
<div class="page-wrap">
<div class="brand">
<div class="brand-eyebrow">⚑ Universal Video Downloader</div>
<div class="brand-title">UniLoader</div>
<div class="brand-sub">Download anything. Instantly.</div>
<div class="made-badge">✦ Made by Deepu ✦</div>
</div>
<div class="card">
<div class="platforms">
<span class="pill ok"><i class="fab fa-youtube"></i> YouTube</span>
<span class="pill ok"><i class="fab fa-tiktok"></i> TikTok</span>
<span class="pill warn"><i class="fab fa-instagram"></i> Instagram</span>
<span class="pill ok"><i class="fab fa-facebook"></i> Facebook</span>
<span class="pill ok"><i class="fas fa-video"></i> 1000+ Sites</span>
</div>
<div class="quality-row">
<div class="q-tag"><strong>720p</strong>Max Quality</div>
<div class="q-tag"><strong>MP4</strong>Format</div>
<div class="q-tag"><strong>Fast</strong>Direct</div>
</div>
<div class="rgb-line"></div>
<form method="POST" action="/download" onsubmit="handleSubmit()">
<div class="input-wrap">
<input type="url" name="url" required class="url-input"
placeholder="Paste video link here…" autocomplete="off">
<i class="fas fa-link input-icon"></i>
</div>
<button type="submit" class="dl-btn" id="dlBtn">
<i class="fas fa-download" id="btnIcon"></i>
<span id="btnText">Download Best Quality</span>
<div class="spinner" id="spinner"></div>
</button>
</form>
{% if error %}
<div class="error-box">
<i class="fas fa-circle-exclamation"></i>
<span>{{ error }}</span>
</div>
{% endif %}
</div>
<p class="footer-note">
For personal use only &nbsp;|&nbsp; <span class="footer-name">✦ Made by Deepu ✦</span>
</p>
</div>
<script>
function handleSubmit() {
const btn = document.getElementById('dlBtn');
const icon = document.getElementById('btnIcon');
const text = document.getElementById('btnText');
const spin = document.getElementById('spinner');
btn.disabled = true;
icon.style.display = 'none';
text.textContent = 'Preparing download…';
spin.style.display = 'block';
}
</script>
</body>
</html>
"""
# ──────────────────────────────────────────
# ROUTES
# ──────────────────────────────────────────
@app.route("/", methods=["GET"])
def index():
return render_template_string(HTML_CODE)
@app.route("/download", methods=["POST"])
def download():
url = request.form.get("url", "").strip()
filename = f"video_{uuid.uuid4().hex[:8]}.mp4"
is_youtube = any(x in url for x in ["youtube.com", "youtu.be"])
is_instagram = "instagram.com" in url
is_tiktok = "tiktok.com" in url
ydl_opts = {
"format": (
"bv*[ext=mp4][height<=720]/"
"bv*[height<=720]/"
"best[height<=720]/best"
),
"outtmpl" : filename,
"merge_output_format": "mp4",
"quiet" : True,
"no_warnings" : True,
"nocheckcertificate" : True,
"retries" : 5,
"fragment_retries" : 5,
"http_headers": {
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
},
}
# YouTube β€” Android client bypass (avoids bot detection)
if is_youtube:
ydl_opts["extractor_args"] = {
"youtube": {
"player_client": ["android", "ios", "web"],
"player_skip" : ["webpage"],
}
}
ydl_opts["http_headers"]["User-Agent"] = (
"com.google.android.youtube/17.36.4 (Linux; U; Android 12; GB) gzip"
)
# Instagram β€” real app headers
if is_instagram:
ydl_opts["http_headers"] = {
"User-Agent" : "Instagram 219.0.0.12.117 Android (31/12; 420dpi; 1080x2400; samsung; SM-G991B; o1s; exynos2100; en_US; 346877738)",
"Accept" : "*/*",
"Accept-Language" : "en-US",
"X-IG-App-ID" : "936619743392459",
}
# TikTok
if is_tiktok:
ydl_opts["http_headers"]["User-Agent"] = (
"TikTok 26.2.0 rv:262018 (iPhone; iOS 14.4.2; en_US) Cronet"
)
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([url])
if not os.path.exists(filename):
raise FileNotFoundError("Download completed but output file not found.")
@after_this_request
def cleanup(resp):
try:
if os.path.exists(filename):
os.remove(filename)
except Exception:
pass
return resp
return send_file(filename, as_attachment=True)
except Exception as e:
if os.path.exists(filename):
os.remove(filename)
err = str(e).lower()
if "sign in" in err or "login" in err or "private" in err:
msg = "This video is private or requires login."
elif "429" in err or "too many" in err:
msg = "Rate limited. Please wait a few minutes and try again."
elif "instagram" in err:
msg = "Instagram blocked this download. Try uploading/downloading the video manually."
elif "copyright" in err or "blocked" in err:
msg = "This video is blocked or restricted."
else:
msg = str(e)
return render_template_string(HTML_CODE, error=msg)
# ──────────────────────────────────────────
# RUN
# ──────────────────────────────────────────
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860)