bep40 commited on
Commit
2b0cb3a
·
verified ·
1 Parent(s): 1b03480

fix: all video links (BDP+24h) from slider open in TikTok mode, remove broken 02.m3u8 probe

Browse files
Files changed (1) hide show
  1. app_wrapper.py +89 -101
app_wrapper.py CHANGED
@@ -1,4 +1,4 @@
1
- """Entry point: shorts carousel + TikTok-style shorts playback (reusing existing JS)."""
2
  import sys, os
3
 
4
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
@@ -48,26 +48,14 @@ if "shorts-feed-mode" not in app.CSS:
48
  if "FULLWIDTH: kill all Gradio padding" not in app.CSS:
49
  app.CSS += FULLWIDTH_CSS
50
 
51
- # ══ Patch homepage slider: change bdpOpenTikTok to bdpOpen for highlight videos ══
52
- # The homepage slider uses render_video_slider_html which calls bdpOpenTikTok
53
- # But bdpOpenTikTok is broken/dead. Change it to bdpOpen so clicking a highlight
54
- # opens the article (which for 24h URLs renders as TikTok feed via our read_article patch)
55
  _orig_render_video_slider = getattr(app, 'render_video_slider_html', None)
56
  if _orig_render_video_slider:
57
  def _patched_render_video_slider(videos):
58
  html = _orig_render_video_slider(videos)
59
- # Replace bdpOpenTikTok with bdpOpen (add slug param)
60
  # bdpOpenTikTok('url','aid') → bdpOpen('url','aid','')
61
- html = html.replace("window.bdpOpenTikTok(", "window.bdpOpen(")
62
- # Fix: bdpOpen needs 3 params (url, aid, slug). Add empty slug where missing.
63
- import re as _r
64
- def _fix_bdpopen(m):
65
- # If only 2 params, add third empty one
66
- params = m.group(1)
67
- if params.count(",") < 2:
68
- params = params.rstrip(")") + ",''"
69
- return "window.bdpOpen(" + params + ")"
70
- html = _r.sub(r"window\.bdpOpen\(([^)]+)\)", _fix_bdpopen, html)
71
  return html
72
  app.render_video_slider_html = _patched_render_video_slider
73
 
@@ -91,61 +79,6 @@ def _patched_render_homepage(articles, *args, **kwargs):
91
  return html
92
  app.render_homepage_html = _patched_render_homepage
93
 
94
- # ══ Patch fetch_tiktok_feed_videos: ensure multi-part 24h videos create multiple slides ══
95
- _orig_fetch_tiktok = app.fetch_tiktok_feed_videos
96
- def _patched_fetch_tiktok():
97
- """Override to ensure multi-part 24h videos are fully expanded."""
98
- from concurrent.futures import ThreadPoolExecutor, as_completed
99
- results = []
100
-
101
- def _fetch_bdp_vid(art):
102
- vid_id = app._extract_bdp_video_id(art["link"])
103
- if not vid_id: return None
104
- embed = app.fetch_bdp_embed_data(vid_id)
105
- if not embed or not embed.get("mp4"): return None
106
- return [{"title": art["title"], "src": embed["mp4"], "poster": embed.get("poster",""),
107
- "vtype": "mp4", "source": "bdp", "link": art["link"]}]
108
-
109
- def _fetch_24h_vid(art):
110
- # Use the PLURAL version that probes for 02, 03, etc.
111
- vids = app._extract_24h_video_urls(art["link"])
112
- if not vids: return []
113
- out = []
114
- for pi, v in enumerate(vids):
115
- label = f" (Phần {pi+1})" if len(vids) > 1 else ""
116
- out.append({"title": art["title"] + label, "src": v["src"], "poster": v["poster"],
117
- "vtype": v["vtype"], "source": "24h", "link": art["link"]})
118
- return out
119
-
120
- bdp_list = app.scrape_bdp_video_list()[:12]
121
- h24_list = app.scrape_24h_video_list()[:12]
122
-
123
- bdp_results, h24_results = [], []
124
- with ThreadPoolExecutor(max_workers=8) as ex:
125
- bdp_futures = {ex.submit(_fetch_bdp_vid, a): a for a in bdp_list}
126
- h24_futures = {ex.submit(_fetch_24h_vid, a): a for a in h24_list}
127
- for f in as_completed(bdp_futures):
128
- try:
129
- r = f.result()
130
- if r: bdp_results.extend(r)
131
- except: pass
132
- for f in as_completed(h24_futures):
133
- try:
134
- r = f.result()
135
- if r: h24_results.extend(r)
136
- except: pass
137
-
138
- # Interleave
139
- bi, hi = 0, 0
140
- while bi < len(bdp_results) or hi < len(h24_results):
141
- if bi < len(bdp_results):
142
- results.append(bdp_results[bi]); bi += 1
143
- if hi < len(h24_results):
144
- results.append(h24_results[hi]); hi += 1
145
- return results[:25]
146
-
147
- app.fetch_tiktok_feed_videos = _patched_fetch_tiktok
148
-
149
  # ══ Patch render_video_page_html: fullheight button ══
150
  _orig_render_video_page = app.render_video_page_html
151
  def _patched_render_video_page():
@@ -155,59 +88,104 @@ def _patched_render_video_page():
155
  return html
156
  app.render_video_page_html = _patched_render_video_page
157
 
158
- # ══ Patch read_article: 24h URLs → shorts TikTok feed ══
159
  _orig_read_article = app.read_article
160
  def _patched_read_article(url):
161
  if not url or url == "#" or len(url) < 10:
162
  return "<p>Không tìm thấy bài viết.</p>"
 
 
163
  if "24h.com.vn" in url:
164
- result = _render_shorts_tiktok_feed(url)
165
  if result:
166
  return result
 
 
 
 
 
 
 
167
  return _orig_read_article(url)
168
 
169
- def _render_shorts_tiktok_feed(current_url):
170
- """Render shorts/24h article as TikTok feed with ALL videos from shorts list."""
171
  from concurrent.futures import ThreadPoolExecutor, as_completed
172
-
 
173
  try:
174
- all_shorts = scrape_24h_news_shorts()[:15]
175
  except:
176
- all_shorts = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
- if not all_shorts:
 
 
 
 
 
 
 
 
 
 
 
 
179
  return None
180
 
181
  def _fetch_video(art):
182
- # Use plural version to get all parts
183
- vids = app._extract_24h_video_urls(art["link"])
184
- if not vids:
185
- # Fallback to singular
186
- vid = _extract_24h_video_url(art["link"])
187
- if vid:
188
- vids = [vid]
189
- if not vids:
190
- return []
191
- out = []
192
- for pi, v in enumerate(vids):
193
- label = f" (Phần {pi+1})" if len(vids) > 1 else ""
194
- out.append({"title": art["title"] + label, "link": art["link"], "img": art.get("img",""),
195
- "src": v["src"], "poster": v["poster"], "vtype": v["vtype"]})
196
- return out
197
 
198
  videos = []
199
  with ThreadPoolExecutor(max_workers=6) as ex:
200
- futures = {ex.submit(_fetch_video, a): a for a in all_shorts}
201
  for f in as_completed(futures):
202
  try:
203
  r = f.result()
204
- if r: videos.extend(r)
205
  except: pass
206
 
207
  if not videos:
208
  return None
 
 
209
 
210
- # Reorder: clicked video (and its parts) first
 
 
211
  current_aid = app.make_id(current_url)
212
  ordered = []
213
  rest = []
@@ -218,6 +196,9 @@ def _render_shorts_tiktok_feed(current_url):
218
  rest.append(v)
219
  ordered.extend(rest)
220
 
 
 
 
221
  slides = []
222
  for vi, v in enumerate(ordered):
223
  poster = app.safe_url(v.get("poster", ""))
@@ -232,6 +213,10 @@ def _render_shorts_tiktok_feed(current_url):
232
  else:
233
  vtag = f'<video class="tiktok-video" playsinline preload="metadata"{poster_attr} muted loop><source src="{app.safe_url(vsrc)}" type="video/mp4"></video>'
234
 
 
 
 
 
235
  slides.append(f'''<div class="tiktok-slide" data-index="{vi}" data-aid="{aid}">
236
  {vtag}
237
  <div class="tiktok-pause-icon">\u25b6</div>
@@ -240,16 +225,18 @@ def _render_shorts_tiktok_feed(current_url):
240
  <button class="tiktok-seek-btn" onclick="event.stopPropagation();window.bdpSeek(this,10)">10s \u23e9</button>
241
  </div>
242
  <div class="tiktok-bottom">
243
- <span class="bdp-badge bdp-badge-24h">24h</span>
244
  <p class="tiktok-title">{v["title"]}</p>
245
  <div class="tiktok-actions">
246
  <button class="tiktok-action-btn" onclick="{share_js}">\U0001f4e4 Chia s\u1ebb</button>
247
  </div>
248
  </div>
 
249
  <div class="tiktok-unmute-hint" onclick="window.bdpTikTokUnmute(this)">\U0001f507 B\u1eadt ti\u1ebfng</div>
250
  <span class="tiktok-counter">{vi+1}/{len(ordered)}</span>
251
  </div>''')
252
 
 
253
  list_cards = []
254
  for v in ordered:
255
  img = app.safe_url(v.get("img") or v.get("poster",""))
@@ -258,13 +245,14 @@ def _render_shorts_tiktok_feed(current_url):
258
  click = f"window.bdpOpen('{app.esc(v['link'])}','{aid}','{sl}')"
259
  list_cards.append(f'''<div class="bdp-card" onclick="{click}">
260
  <div class="bdp-card-img"><img src="{img}" alt="" class="bdp-lazy-img"><div class="bdp-play-overlay">\u25b6</div></div>
261
- <div class="bdp-card-body"><span class="bdp-badge bdp-badge-24h">24h Shorts</span>
262
  <h3 class="bdp-card-title">{v["title"]}</h3></div></div>''')
263
 
264
- grid_html = f'''<div class="bdp-wrap"><div class="bdp-topbar"><span>\U0001f4f1 Shorts Tin T\u1ee9c</span><span>{len(ordered)} video</span></div>
265
  <div class="bdp-grid">{''.join(list_cards)}</div></div>'''
266
 
267
- return f'''<div class="tiktok-fullscreen-container shorts-feed-mode" id="tiktok-fullscreen-feed">
 
268
  <div class="tiktok-fullscreen-feed">{''.join(slides)}</div>
269
  </div>
270
  {grid_html}'''
 
1
+ """Entry point: shorts carousel + TikTok-style video playback for ALL video sources."""
2
  import sys, os
3
 
4
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
 
48
  if "FULLWIDTH: kill all Gradio padding" not in app.CSS:
49
  app.CSS += FULLWIDTH_CSS
50
 
51
+ # ══ Fix homepage slider: replace bdpOpenTikTok with bdpOpen ══
 
 
 
52
  _orig_render_video_slider = getattr(app, 'render_video_slider_html', None)
53
  if _orig_render_video_slider:
54
  def _patched_render_video_slider(videos):
55
  html = _orig_render_video_slider(videos)
56
+ # bdpOpenTikTok doesn't work. Replace with bdpOpen.
57
  # bdpOpenTikTok('url','aid') → bdpOpen('url','aid','')
58
+ html = html.replace("bdpOpenTikTok(", "bdpOpen(")
 
 
 
 
 
 
 
 
 
59
  return html
60
  app.render_video_slider_html = _patched_render_video_slider
61
 
 
79
  return html
80
  app.render_homepage_html = _patched_render_homepage
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  # ══ Patch render_video_page_html: fullheight button ══
83
  _orig_render_video_page = app.render_video_page_html
84
  def _patched_render_video_page():
 
88
  return html
89
  app.render_video_page_html = _patched_render_video_page
90
 
91
+ # ══ Patch read_article: video URLs → TikTok feed ══
92
  _orig_read_article = app.read_article
93
  def _patched_read_article(url):
94
  if not url or url == "#" or len(url) < 10:
95
  return "<p>Không tìm thấy bài viết.</p>"
96
+
97
+ # 24h URLs → shorts/highlight TikTok feed
98
  if "24h.com.vn" in url:
99
+ result = _render_24h_tiktok_feed(url)
100
  if result:
101
  return result
102
+
103
+ # BDP video URLs → BDP TikTok feed
104
+ if "bongdaplus.vn/video/" in url:
105
+ result = _render_bdp_tiktok_feed(url)
106
+ if result:
107
+ return result
108
+
109
  return _orig_read_article(url)
110
 
111
+ def _render_bdp_tiktok_feed(current_url):
112
+ """Render BDP video in TikTok feed style (with other BDP videos)."""
113
  from concurrent.futures import ThreadPoolExecutor, as_completed
114
+
115
+ # Get BDP video list
116
  try:
117
+ bdp_list = app.scrape_bdp_video_list()[:15]
118
  except:
119
+ return None
120
+
121
+ if not bdp_list:
122
+ return None
123
+
124
+ def _fetch_bdp(art):
125
+ vid_id = app._extract_bdp_video_id(art["link"])
126
+ if not vid_id: return None
127
+ embed = app.fetch_bdp_embed_data(vid_id)
128
+ if not embed or not embed.get("mp4"): return None
129
+ return {"title": art["title"], "link": art["link"], "img": art.get("img",""),
130
+ "src": embed["mp4"], "poster": embed.get("poster",""), "vtype": "mp4"}
131
+
132
+ videos = []
133
+ with ThreadPoolExecutor(max_workers=6) as ex:
134
+ futures = {ex.submit(_fetch_bdp, a): a for a in bdp_list}
135
+ for f in as_completed(futures):
136
+ try:
137
+ r = f.result()
138
+ if r: videos.append(r)
139
+ except: pass
140
+
141
+ if not videos:
142
+ return None
143
+
144
+ return _build_tiktok_html(videos, current_url, "bdp")
145
+
146
+ def _render_24h_tiktok_feed(current_url):
147
+ """Render 24h video in TikTok feed style (with other 24h videos)."""
148
+ from concurrent.futures import ThreadPoolExecutor, as_completed
149
 
150
+ # Try shorts list first, then highlights
151
+ try:
152
+ all_articles = scrape_24h_news_shorts()[:15]
153
+ except:
154
+ all_articles = []
155
+
156
+ if not all_articles:
157
+ try:
158
+ all_articles = app.scrape_24h_video_list()[:15]
159
+ except:
160
+ all_articles = []
161
+
162
+ if not all_articles:
163
  return None
164
 
165
  def _fetch_video(art):
166
+ vid = _extract_24h_video_url(art["link"])
167
+ if vid:
168
+ return {"title": art["title"], "link": art["link"], "img": art.get("img",""),
169
+ "src": vid["src"], "poster": vid["poster"], "vtype": vid["vtype"]}
170
+ return None
 
 
 
 
 
 
 
 
 
 
171
 
172
  videos = []
173
  with ThreadPoolExecutor(max_workers=6) as ex:
174
+ futures = {ex.submit(_fetch_video, a): a for a in all_articles}
175
  for f in as_completed(futures):
176
  try:
177
  r = f.result()
178
+ if r: videos.append(r)
179
  except: pass
180
 
181
  if not videos:
182
  return None
183
+
184
+ return _build_tiktok_html(videos, current_url, "24h")
185
 
186
+ def _build_tiktok_html(videos, current_url, source_label):
187
+ """Build TikTok feed HTML from video list. Clicked video placed first."""
188
+ # Reorder: clicked video first
189
  current_aid = app.make_id(current_url)
190
  ordered = []
191
  rest = []
 
196
  rest.append(v)
197
  ordered.extend(rest)
198
 
199
+ badge_cls = "bdp-badge-24h" if source_label == "24h" else "bdp-badge-bdp"
200
+ is_shorts = source_label == "24h"
201
+
202
  slides = []
203
  for vi, v in enumerate(ordered):
204
  poster = app.safe_url(v.get("poster", ""))
 
213
  else:
214
  vtag = f'<video class="tiktok-video" playsinline preload="metadata"{poster_attr} muted loop><source src="{app.safe_url(vsrc)}" type="video/mp4"></video>'
215
 
216
+ fullheight_btn = ''
217
+ if not is_shorts:
218
+ fullheight_btn = '<button class="tiktok-fullheight-btn" onclick="event.stopPropagation();var c=this.closest(\'.tiktok-fullscreen-container\');c.classList.toggle(\'fullheight-mode\');var on=c.classList.contains(\'fullheight-mode\');c.querySelectorAll(\'.tiktok-fullheight-btn\').forEach(function(b){b.classList.toggle(\'active\',on);b.textContent=on?\'\\u2B07 Contain\':\'\\u2922 Fullheight\';});">\u2922 Fullheight</button>'
219
+
220
  slides.append(f'''<div class="tiktok-slide" data-index="{vi}" data-aid="{aid}">
221
  {vtag}
222
  <div class="tiktok-pause-icon">\u25b6</div>
 
225
  <button class="tiktok-seek-btn" onclick="event.stopPropagation();window.bdpSeek(this,10)">10s \u23e9</button>
226
  </div>
227
  <div class="tiktok-bottom">
228
+ <span class="bdp-badge {badge_cls}">{source_label}</span>
229
  <p class="tiktok-title">{v["title"]}</p>
230
  <div class="tiktok-actions">
231
  <button class="tiktok-action-btn" onclick="{share_js}">\U0001f4e4 Chia s\u1ebb</button>
232
  </div>
233
  </div>
234
+ {fullheight_btn}
235
  <div class="tiktok-unmute-hint" onclick="window.bdpTikTokUnmute(this)">\U0001f507 B\u1eadt ti\u1ebfng</div>
236
  <span class="tiktok-counter">{vi+1}/{len(ordered)}</span>
237
  </div>''')
238
 
239
+ # Video list cards below
240
  list_cards = []
241
  for v in ordered:
242
  img = app.safe_url(v.get("img") or v.get("poster",""))
 
245
  click = f"window.bdpOpen('{app.esc(v['link'])}','{aid}','{sl}')"
246
  list_cards.append(f'''<div class="bdp-card" onclick="{click}">
247
  <div class="bdp-card-img"><img src="{img}" alt="" class="bdp-lazy-img"><div class="bdp-play-overlay">\u25b6</div></div>
248
+ <div class="bdp-card-body"><span class="bdp-badge {badge_cls}">{source_label}</span>
249
  <h3 class="bdp-card-title">{v["title"]}</h3></div></div>''')
250
 
251
+ grid_html = f'''<div class="bdp-wrap"><div class="bdp-topbar"><span>\U0001f3ac Video</span><span>{len(ordered)} video</span></div>
252
  <div class="bdp-grid">{''.join(list_cards)}</div></div>'''
253
 
254
+ mode_class = "shorts-feed-mode" if is_shorts else ""
255
+ return f'''<div class="tiktok-fullscreen-container {mode_class}" id="tiktok-fullscreen-feed">
256
  <div class="tiktok-fullscreen-feed">{''.join(slides)}</div>
257
  </div>
258
  {grid_html}'''