vivekchakraverty commited on
Commit
57602ab
·
verified ·
1 Parent(s): e698ddd

Video search via Piped API with normalized engagement ranking + instance failover; Data API/yt-dlp fallback

Browse files
Files changed (1) hide show
  1. app.py +17 -32
app.py CHANGED
@@ -104,11 +104,8 @@ def _cookiefile(workdir: str) -> str | None:
104
  return path
105
 
106
 
107
- def _resolve_proxy(ui_proxy: str | None = None) -> str | None:
108
- """A per-user proxy pasted in the UI wins over the shared YT_PROXY secret, so each
109
- user can route through their own residential proxy (see tools/home_proxy_panel.py).
110
- It also backs screenshot downloads (capture_shots falls back to it)."""
111
- proxy = (ui_proxy or "").strip() or os.environ.get("YT_PROXY", "").strip()
112
  return proxy or None
113
 
114
 
@@ -125,15 +122,14 @@ def _resolve_api_key(ui_key: str | None) -> str | None:
125
  return key or None
126
 
127
 
128
- def check_access(yt_proxy=None):
129
  """Health check: which secrets are set, the egress IP, and YouTube reachability."""
130
  import requests
131
 
132
- proxy = _resolve_proxy(yt_proxy)
133
  proxies = {"http": proxy, "https": proxy} if proxy else None
134
- src = "your proxy field" if (yt_proxy or "").strip() else "YT_PROXY secret"
135
  lines = [
136
- f"- **Proxy**: {('set ✅ (' + src + ')') if proxy else 'not set ⚪'}",
137
  f"- **Media proxy** (`YT_MEDIA_PROXY`, screenshots): "
138
  f"{'set ✅' if _resolve_media_proxy() else 'not set ⚪ (falls back to YT_PROXY)'}",
139
  f"- **Cookies** (`YT_COOKIES`): {'set ✅' if os.environ.get('YT_COOKIES') else 'not set ⚪'}",
@@ -222,7 +218,7 @@ def _collect_keywords(primary_kw, secondary_kw) -> dict:
222
  return {"primary": primary, "secondary": secondary}
223
 
224
 
225
- def run_pipeline(topic, hf_token, yt_api_key, yt_proxy, llm_model, vlm_model,
226
  w_llm, w_whisper, lead, max_shots, primary_kw, secondary_kw,
227
  content_brief="", progress=gr.Progress()):
228
  """Generator yielding (status_md, ranking_df, transcript, docx_file)."""
@@ -243,17 +239,18 @@ def run_pipeline(topic, hf_token, yt_api_key, yt_proxy, llm_model, vlm_model,
243
  try:
244
  api_key = _resolve_api_key(yt_api_key)
245
  cookiefile = _cookiefile(workdir)
246
- proxy = _resolve_proxy(yt_proxy)
247
  if proxy:
248
- # Never log the proxy URL itself it carries user:pass credentials.
249
- src = "your proxy" if (yt_proxy or "").strip() else "the `YT_PROXY` secret"
250
- log.append(f"🔐 Routing transcript + screenshot requests through {src}.")
251
 
252
  # 1. Search ------------------------------------------------------------------
253
  progress(0.03, desc="Searching")
254
- yield status(f"🔍 Searching top videos for **{topic}**…"), gr.update(), gr.update(), gr.update()
255
- videos = search_mod.search_top5(topic)
256
- yield status(f"Found {len(videos)} candidate videos."), gr.update(), gr.update(), gr.update()
 
 
 
257
 
258
  # 2. Sentiment ranking (YouTube Data API comments) ---------------------------
259
  if api_key:
@@ -339,12 +336,7 @@ def build_ui():
339
  "Enter a topic, your **Hugging Face token** (LLM + vision model, billed to you) "
340
  "and a **YouTube Data API key** (for comments). The Space picks the best video by "
341
  "comment sentiment, pulls its transcript, writes an AEO-friendly tutorial, grabs "
342
- "real screenshots at the right moments, and builds a **.docx**.\n\n"
343
- "> 🏠 **YouTube blocks this Space's datacenter IP.** Run your own residential proxy "
344
- "with the **Home Proxy Panel** "
345
- "([download / source](https://github.com/vivekchakraverty/tutorialmaker-home-proxy-panel)) "
346
- "and paste its URL into **Your proxy URL** below — transcript + screenshots then route "
347
- "through your home IP. Each user brings their own proxy."
348
  )
349
  with gr.Row():
350
  with gr.Column(scale=2):
@@ -353,13 +345,6 @@ def build_ui():
353
  placeholder="hf_… (Inference Providers permission)")
354
  yt_api_key = gr.Textbox(label="YouTube Data API key", type="password",
355
  placeholder="for comments (or set the YOUTUBE_API_KEY secret)")
356
- yt_proxy = gr.Textbox(
357
- label="Your proxy URL (recommended)", type="password",
358
- placeholder="http://user:pass@bore.pub:12345 — from the Home Proxy Panel",
359
- info="Run the Home Proxy Panel (github.com/vivekchakraverty/"
360
- "tutorialmaker-home-proxy-panel) on your own machine and paste its "
361
- "proxy URL here so YouTube requests exit from your residential IP. "
362
- "Leave blank to use the Space's shared proxy, if configured.")
363
  with gr.Column(scale=1):
364
  llm_model = gr.Dropdown(LLM_CHOICES, value=LLM_CHOICES[0],
365
  label="Tutorial LLM", allow_custom_value=True)
@@ -411,10 +396,10 @@ def build_ui():
411
  transcript_box = gr.Textbox(label="Transcript preview", lines=10, max_lines=20)
412
  docx_file = gr.File(label="Download tutorial (.docx)")
413
 
414
- health_btn.click(check_access, inputs=[yt_proxy], outputs=health_md)
415
  run_btn.click(
416
  run_pipeline,
417
- inputs=[topic, hf_token, yt_api_key, yt_proxy, llm_model, vlm_model,
418
  w_llm, w_whisper, lead, max_shots, primary_kw, secondary_kw,
419
  content_brief],
420
  outputs=[status_md, ranking_df, transcript_box, docx_file],
 
104
  return path
105
 
106
 
107
+ def _resolve_proxy() -> str | None:
108
+ proxy = os.environ.get("YT_PROXY", "").strip()
 
 
 
109
  return proxy or None
110
 
111
 
 
122
  return key or None
123
 
124
 
125
+ def check_access():
126
  """Health check: which secrets are set, the egress IP, and YouTube reachability."""
127
  import requests
128
 
129
+ proxy = _resolve_proxy()
130
  proxies = {"http": proxy, "https": proxy} if proxy else None
 
131
  lines = [
132
+ f"- **Proxy** (`YT_PROXY`): {'set ✅' if proxy else 'not set ⚪'}",
133
  f"- **Media proxy** (`YT_MEDIA_PROXY`, screenshots): "
134
  f"{'set ✅' if _resolve_media_proxy() else 'not set ⚪ (falls back to YT_PROXY)'}",
135
  f"- **Cookies** (`YT_COOKIES`): {'set ✅' if os.environ.get('YT_COOKIES') else 'not set ⚪'}",
 
218
  return {"primary": primary, "secondary": secondary}
219
 
220
 
221
+ def run_pipeline(topic, hf_token, yt_api_key, llm_model, vlm_model,
222
  w_llm, w_whisper, lead, max_shots, primary_kw, secondary_kw,
223
  content_brief="", progress=gr.Progress()):
224
  """Generator yielding (status_md, ranking_df, transcript, docx_file)."""
 
239
  try:
240
  api_key = _resolve_api_key(yt_api_key)
241
  cookiefile = _cookiefile(workdir)
242
+ proxy = _resolve_proxy()
243
  if proxy:
244
+ log.append("🔐 `YT_PROXY` is set transcript + stream requests route through it.")
 
 
245
 
246
  # 1. Search ------------------------------------------------------------------
247
  progress(0.03, desc="Searching")
248
+ yield status(f"🔍 Searching top videos for **{topic}** (engagement-ranked)…"), gr.update(), gr.update(), gr.update()
249
+ videos = search_mod.search_top5(topic, api_key=api_key, proxy=proxy)
250
+ eng_note = ""
251
+ if videos and "engagement" in videos[0]:
252
+ eng_note = " • ranked by likes+comments+subscribers−dislikes (normalized)"
253
+ yield status(f"Found {len(videos)} candidate videos{eng_note}."), gr.update(), gr.update(), gr.update()
254
 
255
  # 2. Sentiment ranking (YouTube Data API comments) ---------------------------
256
  if api_key:
 
336
  "Enter a topic, your **Hugging Face token** (LLM + vision model, billed to you) "
337
  "and a **YouTube Data API key** (for comments). The Space picks the best video by "
338
  "comment sentiment, pulls its transcript, writes an AEO-friendly tutorial, grabs "
339
+ "real screenshots at the right moments, and builds a **.docx**."
 
 
 
 
 
340
  )
341
  with gr.Row():
342
  with gr.Column(scale=2):
 
345
  placeholder="hf_… (Inference Providers permission)")
346
  yt_api_key = gr.Textbox(label="YouTube Data API key", type="password",
347
  placeholder="for comments (or set the YOUTUBE_API_KEY secret)")
 
 
 
 
 
 
 
348
  with gr.Column(scale=1):
349
  llm_model = gr.Dropdown(LLM_CHOICES, value=LLM_CHOICES[0],
350
  label="Tutorial LLM", allow_custom_value=True)
 
396
  transcript_box = gr.Textbox(label="Transcript preview", lines=10, max_lines=20)
397
  docx_file = gr.File(label="Download tutorial (.docx)")
398
 
399
+ health_btn.click(check_access, outputs=health_md)
400
  run_btn.click(
401
  run_pipeline,
402
+ inputs=[topic, hf_token, yt_api_key, llm_model, vlm_model,
403
  w_llm, w_whisper, lead, max_shots, primary_kw, secondary_kw,
404
  content_brief],
405
  outputs=[status_md, ranking_df, transcript_box, docx_file],