bep40 commited on
Commit
f5a57e2
·
verified ·
1 Parent(s): 413f2b5

Fix shorts/video classification: tag is_short deterministically from Jina channel tabs (no YouTube HEAD), override /api/shorts

Browse files
Files changed (1) hide show
  1. app_v2_entry.py +69 -0
app_v2_entry.py CHANGED
@@ -165,6 +165,75 @@ def _search_all(topic,limit=36):
165
  for s in srcs:
166
  if i<len(s) and s[i].get('url') and s[i]['url'] not in seen:seen.add(s[i]['url']);out.append(s[i])
167
  return out[:limit]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  # Override article endpoint
169
  app.router.routes=[r for r in app.router.routes if not(getattr(r,'path',None)=='/api/article' and 'GET' in getattr(r,'methods',set()))]
170
  _JINA_BOILER_STOP=['facebooktelegram','copy link','follow us','bài viết liên quan','tin liên quan',
 
165
  for s in srcs:
166
  if i<len(s) and s[i].get('url') and s[i]['url'] not in seen:seen.add(s[i]['url']);out.append(s[i])
167
  return out[:limit]
168
+
169
+ # ===== YOUTUBE SHORTS — reliable short/video classification (override) =====
170
+ # Datacenter IPs (HF Space) get an EMPTY feeds/videos.xml from YouTube and YouTube
171
+ # HEAD requests time out, so main.py's HEAD-based _classify_short always defaulted to
172
+ # is_short=True -> every Dân trí/SKĐS item showed as "Short" and the 1:1 toggle never
173
+ # appeared. Fix: classify deterministically from the Jina-rendered channel tabs —
174
+ # a link under /shorts/<id> is a Short, a link under /watch?v=<id> on the /videos tab
175
+ # is a normal (horizontal) video. No request to youtube.com is needed.
176
+ from main import (_yt_jina_page, _yt_rss_shorts, SHORTS_FALLBACK,
177
+ _YT_CHANNEL_IDS, _cached, _cache_ttl_yt)
178
+
179
+ def _yt_jina_shorts_classified(channel, count=24):
180
+ """Newest videos for a channel via Jina channel tabs, each tagged is_short.
181
+ /shorts tab -> Short (is_short=True). /videos tab -> normal video (is_short=False)
182
+ unless the rendered link itself is a /shorts/ URL."""
183
+ items=[];seen=set()
184
+ # (tab, default_is_short_for_watch_links)
185
+ for path, tab_short in (("videos", False), ("shorts", True)):
186
+ md=_yt_jina_page(channel, path)
187
+ if not md:continue
188
+ for m in re.finditer(r'\[([^\]\[]{8,160})\]\(https?://(?:www\.)?youtube\.com/(shorts/|watch\?v=)([A-Za-z0-9_-]{11})[^)]*\)', md):
189
+ title=html_lib.unescape(m.group(1)).strip().strip('"')
190
+ form=m.group(2); vid=m.group(3)
191
+ if vid in seen:continue
192
+ tl=title.lower()
193
+ if tl in ("home home","shorts shorts","image") or title.startswith("!") or title.startswith("Image"):continue
194
+ # A /shorts/ URL is always a Short; a /watch?v= link inherits the tab default.
195
+ is_short = True if form.startswith("shorts/") else tab_short
196
+ seen.add(vid)
197
+ items.append({"title":title,"link":f"https://www.youtube.com/watch?v={vid}",
198
+ "img":f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg","source":"yt",
199
+ "id":vid,"channel":channel,"is_short":is_short})
200
+ if len(items)>=count:break
201
+ return items
202
+
203
+ def _scrape_shorts_v2():
204
+ """Stable Dân trí & SKĐS feed with RELIABLE is_short tags (Jina tabs), newest-first.
205
+ Falls back to RSS (tagged short) then the static list so the slider never empties."""
206
+ vids=[]
207
+ with ThreadPoolExecutor(2) as ex:
208
+ futs=[ex.submit(_yt_jina_shorts_classified,ch,24) for ch in ["baodantri7941","baosuckhoedoisongboyte"]]
209
+ for f in as_completed(futs):
210
+ try:
211
+ r=f.result()
212
+ if r:vids.extend(r)
213
+ except:pass
214
+ # If Jina returned nothing for a channel, fill from RSS (newest, tagged short by default).
215
+ have=set(v.get("channel") for v in vids)
216
+ for ch in ["baodantri7941","baosuckhoedoisongboyte"]:
217
+ if ch not in have:
218
+ for v in _yt_rss_shorts(ch,24):
219
+ v.setdefault("is_short",True);vids.append(v)
220
+ merged=[];seen=set()
221
+ for v in vids+[dict(x) for x in SHORTS_FALLBACK]:
222
+ vid=v.get("id")
223
+ if not vid or vid in seen:continue
224
+ # Static fallback items: infer from title (#shorts -> short, else normal video).
225
+ if "is_short" not in v:
226
+ t=(v.get("title","") or "").lower()
227
+ v["is_short"]= ("#shorts" in t) or ("short" in t)
228
+ seen.add(vid);merged.append(v)
229
+ return merged[:40]
230
+
231
+ # Override main.py's /api/shorts so the slider uses the reliable classifier.
232
+ app.router.routes=[r for r in app.router.routes if not(getattr(r,'path',None)=='/api/shorts' and 'GET' in getattr(r,'methods',set()))]
233
+ @app.get('/api/shorts')
234
+ def api_shorts_v2():
235
+ return JSONResponse(_cached("yt_shorts_v4",_scrape_shorts_v2,ttl=_cache_ttl_yt))
236
+
237
  # Override article endpoint
238
  app.router.routes=[r for r in app.router.routes if not(getattr(r,'path',None)=='/api/article' and 'GET' in getattr(r,'methods',set()))]
239
  _JINA_BOILER_STOP=['facebooktelegram','copy link','follow us','bài viết liên quan','tin liên quan',