lexicalspace commited on
Commit
07bb909
·
verified ·
1 Parent(s): 01d7fc7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +156 -182
app.py CHANGED
@@ -2,18 +2,45 @@ import yt_dlp
2
  import os
3
  import gradio as gr
4
  import random
5
- import time
6
  import threading
7
- import datetime
8
- import re
9
  import requests
10
  from gradio_client import Client
11
  from huggingface_hub import InferenceClient
12
 
13
  # =====================================================
14
- # CONFIG & SECRETS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  # =====================================================
16
- # Downloader Config
17
  PROXY_SPACE_URL = "lexicalspace/Proxy-Server"
18
  OUTPUT_DIR = "downloads"
19
  os.makedirs(OUTPUT_DIR, exist_ok=True)
@@ -22,108 +49,75 @@ MAX_CONCURRENCY = 6
22
  MAX_QUEUE = 20
23
 
24
  # SEO Config
25
- # Ensure 'HF_TOKEN' is set in your Space Settings > Secrets
26
  HF_TOKEN = os.getenv("HF_TOKEN")
27
  SEO_MODEL_ID = "Qwen/Qwen2.5-Coder-32B-Instruct"
28
 
29
- # Initialize SEO Client
30
- # We wrap this in a try/except to prevent crash if token is missing
31
  try:
32
  seo_client = InferenceClient(api_key=HF_TOKEN)
33
- except Exception:
34
  seo_client = None
35
 
36
  # =====================================================
37
- # GLOBAL STATE (Downloader)
38
  # =====================================================
39
  active_jobs = {}
40
  job_lock = threading.Lock()
41
 
42
  # =====================================================
43
- # PART 1: DOWNLOADER LOGIC
44
  # =====================================================
45
  def get_proxy_batch():
46
  try:
47
  client = Client(PROXY_SPACE_URL)
48
- result = client.predict(api_name="/get_proxies_api")
49
- proxies = result[0]
50
- if proxies:
51
- random.shuffle(proxies)
52
- return [f"http://{p}" for p in proxies[:6]]
53
- except:
54
- pass
55
- return []
56
 
57
  def get_best_proxy():
58
  try:
59
- client = Client(PROXY_SPACE_URL)
60
- result = client.predict(api_name="/get_proxies_api")
61
- proxies = result[0]
62
- if proxies:
63
- return f"http://{random.choice(proxies)}"
64
- except:
65
- pass
66
- return None
67
-
68
- def parse_time(time_str):
69
- if not time_str: return None
70
- try:
71
- parts = list(map(int, time_str.split(':')))
72
- if len(parts) == 1: return parts[0]
73
- if len(parts) == 2: return parts[0] * 60 + parts[1]
74
- if len(parts) == 3: return parts[0] * 3600 + parts[1] * 60 + parts[2]
75
- except:
76
- return None
77
- return None
78
 
79
  def build_format_selector(quality_mode):
80
- if quality_mode == "Audio Only (Best)":
81
- return {'format': 'bestaudio/best', 'postprocessors': [{'key': 'FFmpegExtractAudio','preferredcodec': 'mp3'}], 'ext': 'mp3'}
82
- elif quality_mode == "Video (4K / Best)":
83
- return {'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best', 'ext': 'mp4'}
84
- elif quality_mode == "Video (1080p)":
85
- return {'format': 'bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/best[height<=1080][ext=mp4]', 'ext': 'mp4'}
86
- elif quality_mode == "Video (720p)":
87
- return {'format': 'bestvideo[height<=720][ext=mp4]+bestaudio[ext=m4a]/best[height<=720][ext=mp4]', 'ext': 'mp4'}
88
- elif quality_mode == "Video (360p / Data Saver)":
89
- return {'format': 'bestvideo[height<=360][ext=mp4]+bestaudio[ext=m4a]/best[height<=360][ext=mp4]', 'ext': 'mp4'}
90
- return {}
91
 
92
  def fetch_metadata(url):
93
- if not url: return None, "⚠️ Please enter a URL first.", None, None, None
94
- api_url = f"https://noembed.com/embed?url={url}"
95
  try:
96
- response = requests.get(api_url, timeout=10)
97
- data = response.json()
98
- if "error" in data: return None, f"❌ API Error: {data.get('error')}", gr.update(interactive=False), None, None
99
 
100
- title = data.get('title', 'Unknown Title')
101
- uploader = data.get('author_name', 'Unknown Channel')
102
- thumb = data.get('thumbnail_url', None)
103
- desc = f"### 🎬 {title}\n**👤 Channel:** {uploader}\n**⏱️ Duration:** N/A (API Limit)\n**👁️ Views:** N/A (API Limit)"
104
  safe_title = "".join([c for c in title if c.isalnum() or c in " ._-"])
105
- return thumb, desc, gr.update(interactive=True), gr.update(value=safe_title), "✅ Metadata fetched"
106
  except Exception as e:
107
- return None, f"❌ Error: {str(e)}", gr.update(interactive=False), None, None
108
 
109
- def run_downloader(engine_name, url, quality, start_time, end_time, custom_name, cookies, progress):
110
- log = [f"🚀 Engine: {engine_name} | Quality: {quality}"]
 
 
 
 
111
  with job_lock:
112
- if active_jobs.get(url) == "running": return None, "⛔ Already running", "Blocked"
113
  active_jobs[url] = "running"
114
 
115
- format_opts = build_format_selector(quality)
116
- filename_tmpl = f"{custom_name}.%(ext)s" if custom_name else '%(title)s.%(ext)s'
 
117
 
 
118
  proxies = get_proxy_batch() if "V1" in engine_name else ([get_best_proxy()] if get_best_proxy() else [None])
119
- strategies = ["android", "ios", "web"] if "V1" in engine_name else ["android", "ios", "web", "tv"]
120
 
121
- s_sec, e_sec = parse_time(start_time), parse_time(end_time)
122
- download_ranges = (lambda info, ydl: [{'start_time': s_sec or 0, 'end_time': e_sec or float('inf')}]) if (s_sec or e_sec) else None
123
-
124
- success, final_file, cookie_path = False, None, None
125
- if cookies:
126
- cookie_path = f"cookies_{random.randint(1000,9999)}.txt"
127
  with open(cookie_path, "w") as f: f.write(cookies)
128
 
129
  for proxy in proxies:
@@ -131,145 +125,125 @@ def run_downloader(engine_name, url, quality, start_time, end_time, custom_name,
131
  for strat in strategies:
132
  if success: break
133
  ydl_opts = {
134
- 'outtmpl': os.path.join(OUTPUT_DIR, filename_tmpl), 'noplaylist': True, 'quiet': True, 'force_ipv4': True,
135
- 'extractor_args': {'youtube': {'player_client': [strat]}}, **format_opts
 
 
136
  }
137
  if proxy: ydl_opts['proxy'] = proxy
138
- if download_ranges: ydl_opts['download_ranges'] = download_ranges
139
  if cookie_path: ydl_opts['cookiefile'] = cookie_path
140
-
141
- def hook(d):
142
- if d['status'] == 'downloading':
143
- total = d.get('total_bytes') or d.get('total_bytes_estimate')
144
- if total: progress(d.get('downloaded_bytes', 0) / total)
145
- ydl_opts['progress_hooks'] = [hook]
146
 
147
  try:
148
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
149
  info = ydl.extract_info(url, download=True)
150
- name = ydl.prepare_filename(info)
151
- base, _ = os.path.splitext(name)
152
- final_file = f"{base}.{format_opts.get('ext', 'mp4')}"
153
- if not os.path.exists(final_file):
154
- for f in os.listdir(OUTPUT_DIR):
155
- if f.startswith(os.path.basename(base)): final_file = os.path.join(OUTPUT_DIR, f); break
156
  if os.path.exists(final_file): success = True
157
  except: continue
158
-
159
  if cookie_path and os.path.exists(cookie_path): os.remove(cookie_path)
160
  with job_lock: active_jobs.pop(url, None)
161
- return (final_file, "✅ Complete", "\n".join(log)) if success else (None, "❌ Failed", "\n".join(log))
162
-
163
- def download_router(mode, quality, start, end, custom_name, cookies, url, progress=gr.Progress()):
164
- engine = "V2 • Fast Engine" if "V2" in mode else "V1 • Stable Engine"
165
- if "Auto" in mode: engine = "V2 • Fast Engine" if (len(url) > 70 or "list=" in url) else "V1 • Stable Engine"
166
- return run_downloader(engine, url, quality, start, end, custom_name, cookies, progress)
167
 
168
  # =====================================================
169
- # PART 2: SEO GENERATOR LOGIC
170
  # =====================================================
171
- def generate_seo(code_snippet, file_type):
172
- if not seo_client: return "🔒 Error: 'HF_TOKEN' not found in Secrets."
173
- if not code_snippet.strip(): return "⚠️ Error: Please paste some code first."
174
-
175
- system_instruction = f"""
176
- You are an expert Technical SEO Specialist. Analyze the user's {file_type} code.
177
- Your Goal: Generate Google-compliant JSON-LD structured data and SEO meta tags.
178
- Output Format (Strict Markdown):
179
- ## SEO Metadata
180
- **Title:** [Engaging Title, max 60 chars]
181
- **Description:** [Summary including keywords, max 160 chars]
182
- **Keywords:** [5-8 comma-separated keywords]
183
- ## JSON-LD Structured Data
184
- ```json
185
- [Insert VALID JSON-LD here]
186
- ```
187
- """
188
  try:
189
- response = seo_client.chat_completion(
190
- model=SEO_MODEL_ID,
191
- messages=[{"role": "system", "content": system_instruction}, {"role": "user", "content": f"Analyze:\n{code_snippet}"}],
192
- max_tokens=1500, temperature=0.2
193
- )
194
- return response.choices[0].message.content
195
- except Exception as e:
196
- return f"❌ Error: {str(e)}"
197
 
198
  # =====================================================
199
- # PART 3: COMBINED UI WITH ROUTING
200
  # =====================================================
201
- def handle_deep_link(request: gr.Request):
202
  """
203
- Reads the URL parameters to decide which tab to open.
204
- Example: ?mode=seo -> Opens SEO Tab
205
- Example: ?mode=downloader -> Opens Downloader Tab
206
  """
207
  params = request.query_params
208
- mode = params.get("mode") or params.get("tool")
209
-
210
- if mode == "seo":
211
- return gr.Tabs(selected="seo_tab")
 
 
 
 
 
 
 
212
  else:
213
- return gr.Tabs(selected="dl_tab")
 
214
 
215
- # MAIN APP BLOCK
216
- with gr.Blocks(title="Lexical Space Tools", theme=gr.themes.Soft()) as app:
 
 
217
 
218
- # We assign IDs to tabs so we can select them programmatically
219
- with gr.Tabs() as main_tabs:
 
220
 
221
- # --- TAB 1: DOWNLOADER ---
222
- with gr.TabItem("🚀 UltraMax Downloader", id="dl_tab"):
223
- gr.Markdown("### 📥 Universal Media Downloader")
224
- with gr.Row(variant="panel"):
225
- url_input = gr.Textbox(label="Media URL", placeholder="Paste YouTube/Insta/TikTok link...", show_label=False, scale=4)
226
- fetch_btn = gr.Button("🔍 Fetch Info", variant="primary", scale=1)
227
-
228
  with gr.Row():
229
- thumb_preview = gr.Image(label="Thumbnail", interactive=False, height=180)
230
- with gr.Column(scale=3):
231
- info_display = gr.Markdown("### ⏳ Waiting for link...")
232
- fetch_status = gr.Label(value="Idle", show_label=False)
233
-
234
- with gr.Row(variant="panel"):
235
- quality_select = gr.Dropdown(["Audio Only (Best)", "Video (4K / Best)", "Video (1080p)", "Video (720p)", "Video (360p / Data Saver)"], value="Audio Only (Best)", label="Quality")
236
- engine_mode = gr.Radio(["Auto (Smart)", "V1 (Stable)", "V2 (Fast)"], value="Auto (Smart)", label="Engine")
237
-
238
- with gr.Accordion("⚙️ Advanced (Cut / Rename / Auth)", open=False):
239
- with gr.Row():
240
- start_time = gr.Textbox(label="Start", placeholder="00:00")
241
- end_time = gr.Textbox(label="End", placeholder="01:30")
242
- custom_filename = gr.Textbox(label="Filename", placeholder="MyFile")
243
- cookies_input = gr.Textbox(label="Cookies", placeholder="Netscape format...", lines=1)
244
-
245
- dl_btn = gr.Button("⬇️ START DOWNLOAD", variant="stop", interactive=False, size="lg")
246
- with gr.Accordion("📂 Output & Logs", open=True):
247
- with gr.Row():
248
- file_out = gr.File(label="Output")
249
- log_out = gr.Textbox(label="Logs", lines=4)
250
-
251
- # Downloader Events
252
- fetch_btn.click(fetch_metadata, [url_input], [thumb_preview, info_display, dl_btn, custom_filename, fetch_status])
253
- dl_btn.click(download_router, [engine_mode, quality_select, start_time, end_time, custom_filename, cookies_input, url_input], [file_out, fetch_status, log_out], concurrency_limit=MAX_CONCURRENCY)
254
-
255
- # --- TAB 2: SEO GENERATOR ---
256
- with gr.TabItem("⚡ SEO Generator", id="seo_tab"):
257
- gr.Markdown("### 🔍 AI SEO & JSON-LD Generator")
258
  with gr.Row():
 
259
  with gr.Column():
260
- input_type = gr.Radio(["python", "html"], label="File Type", value="python")
261
- code_input = gr.Code(language="python", label="Paste Code", lines=15)
262
- seo_btn = gr.Button("✨ Generate SEO Data", variant="primary", size="lg")
263
- with gr.Column():
264
- output_markdown = gr.Markdown(label="Results")
265
 
266
- # SEO Events
267
- input_type.change(lambda x: gr.Code(language=x), inputs=input_type, outputs=code_input)
268
- seo_btn.click(generate_seo, [code_input, input_type], [output_markdown])
 
 
 
269
 
270
- # --- DEEP LINKING EVENT ---
271
- # This runs when the page loads to check the URL
272
- app.load(fn=handle_deep_link, inputs=None, outputs=main_tabs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
274
  if __name__ == "__main__":
275
- app.queue(max_size=MAX_QUEUE).launch()
 
2
  import os
3
  import gradio as gr
4
  import random
 
5
  import threading
 
 
6
  import requests
7
  from gradio_client import Client
8
  from huggingface_hub import InferenceClient
9
 
10
  # =====================================================
11
+ # 🎨 CSS & UI CONFIGURATION (RESPONSIVE)
12
+ # =====================================================
13
+ custom_css = """
14
+ /* Hide the footer for a cleaner look */
15
+ footer {visibility: hidden}
16
+
17
+ /* PC Styles */
18
+ .container { max-width: 1200px; margin: auto; padding-top: 20px; }
19
+ .card { border-radius: 12px; border: 1px solid #e5e7eb; padding: 20px; margin-bottom: 20px; }
20
+
21
+ /* Mobile Response */
22
+ @media (max-width: 768px) {
23
+ .gradio-container { padding: 5px !important; }
24
+ .group-container { padding: 10px !important; }
25
+ button { min-height: 50px; }
26
+ }
27
+
28
+ /* Error Message Styling */
29
+ .error-box {
30
+ text-align: center;
31
+ padding: 50px;
32
+ background: #FEF2F2;
33
+ border: 2px solid #F87171;
34
+ border-radius: 10px;
35
+ color: #991B1B;
36
+ font-size: 1.2rem;
37
+ font-weight: bold;
38
+ }
39
+ """
40
+
41
+ # =====================================================
42
+ # ⚙️ CONFIG & CLIENTS
43
  # =====================================================
 
44
  PROXY_SPACE_URL = "lexicalspace/Proxy-Server"
45
  OUTPUT_DIR = "downloads"
46
  os.makedirs(OUTPUT_DIR, exist_ok=True)
 
49
  MAX_QUEUE = 20
50
 
51
  # SEO Config
 
52
  HF_TOKEN = os.getenv("HF_TOKEN")
53
  SEO_MODEL_ID = "Qwen/Qwen2.5-Coder-32B-Instruct"
54
 
 
 
55
  try:
56
  seo_client = InferenceClient(api_key=HF_TOKEN)
57
+ except:
58
  seo_client = None
59
 
60
  # =====================================================
61
+ # 🔄 GLOBAL STATE
62
  # =====================================================
63
  active_jobs = {}
64
  job_lock = threading.Lock()
65
 
66
  # =====================================================
67
+ # 📥 PART 1: DOWNLOADER LOGIC
68
  # =====================================================
69
  def get_proxy_batch():
70
  try:
71
  client = Client(PROXY_SPACE_URL)
72
+ return [f"http://{p}" for p in client.predict(api_name="/get_proxies_api")[0][:6]]
73
+ except: return []
 
 
 
 
 
 
74
 
75
  def get_best_proxy():
76
  try:
77
+ proxies = Client(PROXY_SPACE_URL).predict(api_name="/get_proxies_api")[0]
78
+ return f"http://{random.choice(proxies)}" if proxies else None
79
+ except: return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
  def build_format_selector(quality_mode):
82
+ if "Audio" in quality_mode: return {'format': 'bestaudio/best', 'postprocessors': [{'key': 'FFmpegExtractAudio','preferredcodec': 'mp3'}], 'ext': 'mp3'}
83
+ if "4K" in quality_mode: return {'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best', 'ext': 'mp4'}
84
+ height = 1080 if "1080" in quality_mode else 720 if "720" in quality_mode else 360
85
+ return {'format': f'bestvideo[height<={height}][ext=mp4]+bestaudio[ext=m4a]/best[height<={height}][ext=mp4]', 'ext': 'mp4'}
 
 
 
 
 
 
 
86
 
87
  def fetch_metadata(url):
88
+ if not url: return None, "⚠️ Please enter a URL.", None, None, None
 
89
  try:
90
+ data = requests.get(f"https://noembed.com/embed?url={url}", timeout=5).json()
91
+ if "error" in data: return None, f"❌ Error: {data['error']}", None, None, None
 
92
 
93
+ title = data.get('title', 'Media File')
94
+ desc = f"### 🎬 {title}\n**👤 Author:** {data.get('author_name', 'Unknown')}"
 
 
95
  safe_title = "".join([c for c in title if c.isalnum() or c in " ._-"])
96
+ return data.get('thumbnail_url'), desc, gr.update(interactive=True), safe_title, "✅ Ready"
97
  except Exception as e:
98
+ return None, f"❌ Error: {str(e)}", None, None, None
99
 
100
+ def run_downloader(engine_mode, url, quality, custom_name, cookies, progress=gr.Progress()):
101
+ if not url: return None, "⚠️ No URL provided", ""
102
+
103
+ # Engine Selection
104
+ engine_name = "V2 (Fast)" if "V2" in engine_mode or ("Auto" in engine_mode and len(url) > 60) else "V1 (Stable)"
105
+
106
  with job_lock:
107
+ if active_jobs.get(url) == "running": return None, "⛔ Already running", ""
108
  active_jobs[url] = "running"
109
 
110
+ log = [f"🚀 Engine: {engine_name} | Q: {quality}"]
111
+ opts = build_format_selector(quality)
112
+ fname = f"{custom_name}.%(ext)s" if custom_name else '%(title)s.%(ext)s'
113
 
114
+ # Setup Proxy & Client
115
  proxies = get_proxy_batch() if "V1" in engine_name else ([get_best_proxy()] if get_best_proxy() else [None])
116
+ strategies = ["android", "web", "ios"]
117
 
118
+ success, final_file = False, None
119
+ cookie_path = f"cookies_{random.randint(1000,9999)}.txt" if cookies else None
120
+ if cookies:
 
 
 
121
  with open(cookie_path, "w") as f: f.write(cookies)
122
 
123
  for proxy in proxies:
 
125
  for strat in strategies:
126
  if success: break
127
  ydl_opts = {
128
+ 'outtmpl': os.path.join(OUTPUT_DIR, fname),
129
+ 'quiet': True, 'noplaylist': True,
130
+ 'extractor_args': {'youtube': {'player_client': [strat]}},
131
+ **opts
132
  }
133
  if proxy: ydl_opts['proxy'] = proxy
 
134
  if cookie_path: ydl_opts['cookiefile'] = cookie_path
 
 
 
 
 
 
135
 
136
  try:
137
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
138
  info = ydl.extract_info(url, download=True)
139
+ fpath = ydl.prepare_filename(info)
140
+ final_file = fpath.rsplit('.', 1)[0] + '.' + opts['ext']
 
 
 
 
141
  if os.path.exists(final_file): success = True
142
  except: continue
143
+
144
  if cookie_path and os.path.exists(cookie_path): os.remove(cookie_path)
145
  with job_lock: active_jobs.pop(url, None)
146
+
147
+ return (final_file, "✅ Download Complete", "\n".join(log)) if success else (None, "❌ Failed", "\n".join(log))
 
 
 
 
148
 
149
  # =====================================================
150
+ # PART 2: SEO LOGIC
151
  # =====================================================
152
+ def generate_seo(code, ftype):
153
+ if not seo_client: return "🔒 Error: HF_TOKEN missing in Secrets."
154
+ if not code: return "⚠️ Please paste code."
155
+
156
+ prompt = f"Analyze this {ftype} code. Return valid JSON-LD and SEO Title/Description/Keywords."
 
 
 
 
 
 
 
 
 
 
 
 
157
  try:
158
+ return seo_client.chat_completion("Qwen/Qwen2.5-Coder-32B-Instruct", messages=[{"role": "user", "content": prompt}], max_tokens=1000).choices[0].message.content
159
+ except Exception as e: return f"❌ Error: {str(e)}"
 
 
 
 
 
 
160
 
161
  # =====================================================
162
+ # 🧭 PART 3: ROUTING & UI
163
  # =====================================================
164
+ def router(request: gr.Request):
165
  """
166
+ Determines which tool to show based on ?mode= URL parameter.
 
 
167
  """
168
  params = request.query_params
169
+ mode = params.get("mode")
170
+
171
+ # STRICT MODE: If mode is missing or wrong, show Error Page
172
+ if not mode:
173
+ # Default fallback (Optional: Change to 'dl_col' if you want a default tool)
174
+ return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
175
+
176
+ if mode == "downloader":
177
+ return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
178
+ elif mode == "seo":
179
+ return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
180
  else:
181
+ # WRONG URL -> Error Page
182
+ return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)
183
 
184
+ # =====================================================
185
+ # 🖥️ MAIN APP
186
+ # =====================================================
187
+ with gr.Blocks(css=custom_css, title="Lexical Space Tools", theme=gr.themes.Soft()) as app:
188
 
189
+ # --- 1. DOWNLOADER UI ---
190
+ with gr.Column(visible=False, elem_classes="container") as dl_col:
191
+ gr.Markdown("# 🚀 UltraMax Downloader")
192
 
193
+ with gr.Group(elem_classes="card"):
 
 
 
 
 
 
194
  with gr.Row():
195
+ url_input = gr.Textbox(label="URL", placeholder="Paste link...", scale=3, show_label=False)
196
+ fetch_btn = gr.Button("🔍 Fetch", variant="primary", scale=1)
197
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  with gr.Row():
199
+ thumb = gr.Image(label="Preview", height=150, interactive=False, show_label=False)
200
  with gr.Column():
201
+ desc = gr.Markdown("### Ready")
202
+ status = gr.Label(value="Idle", show_label=False)
 
 
 
203
 
204
+ with gr.Accordion("⚙️ Settings (Quality / Rename)", open=False):
205
+ with gr.Row():
206
+ qual = gr.Dropdown(["Audio Only (Best)", "Video (1080p)", "Video (720p)"], value="Audio Only (Best)", label="Quality")
207
+ eng = gr.Radio(["Auto", "V1 (Stable)", "V2 (Fast)"], value="Auto", label="Engine")
208
+ fname = gr.Textbox(label="Filename (Optional)", placeholder="MyFile")
209
+ cookies = gr.Textbox(label="Cookies", placeholder="Netscape format...", lines=1)
210
 
211
+ dl_btn = gr.Button("⬇️ Download Now", variant="stop", interactive=False, size="lg")
212
+
213
+ with gr.Group(elem_classes="card"):
214
+ out_file = gr.File(label="Download")
215
+ out_log = gr.Textbox(label="Logs", lines=2)
216
+
217
+ # Events
218
+ fetch_btn.click(fetch_metadata, [url_input], [thumb, desc, dl_btn, fname, status])
219
+ dl_btn.click(run_downloader, [eng, url_input, qual, fname, cookies], [out_file, status, out_log])
220
+
221
+ # --- 2. SEO UI ---
222
+ with gr.Column(visible=False, elem_classes="container") as seo_col:
223
+ gr.Markdown("# ⚡ AI SEO Generator")
224
+ with gr.Group(elem_classes="card"):
225
+ ftype = gr.Radio(["python", "html"], label="Type", value="python")
226
+ code_in = gr.Code(language="python", lines=15, label="Code")
227
+ seo_btn = gr.Button("✨ Generate", variant="primary")
228
+
229
+ res = gr.Markdown(label="Result")
230
+
231
+ ftype.change(lambda x: gr.Code(language=x), inputs=ftype, outputs=code_in)
232
+ seo_btn.click(generate_seo, [code_in, ftype], [res])
233
+
234
+ # --- 3. ERROR / 404 UI ---
235
+ with gr.Column(visible=False, elem_classes="container") as error_col:
236
+ gr.HTML("""
237
+ <div class="error-box">
238
+ <h1>⚠️ Access Denied</h1>
239
+ <p>No tool is available at this URL.</p>
240
+ <p style="font-size: 1rem; color: #555;">Please check your link or contact the administrator.</p>
241
+ </div>
242
+ """)
243
+
244
+ # --- ROUTING HANDLER ---
245
+ # Checks URL on load and toggles visibility of columns
246
+ app.load(fn=router, inputs=None, outputs=[dl_col, seo_col, error_col])
247
 
248
  if __name__ == "__main__":
249
+ app.queue(max_size=20).launch()