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

fix: multi-part videos + slider click opens correct video in TikTok feed

Browse files
Files changed (1) hide show
  1. app_wrapper.py +99 -22
app_wrapper.py CHANGED
@@ -18,7 +18,6 @@ SHORTS_EXTRA_CSS = """
18
  .tiktok-fullheight-btn.active{background:rgba(92,184,122,.5)}
19
  """
20
 
21
- # Kill ALL padding at every Gradio container level
22
  FULLWIDTH_CSS = """
23
  /* ══ FULLWIDTH: kill all Gradio padding ══ */
24
  body,.gradio-container,.main,.contain{margin:0!important;padding:0!important;max-width:100%!important;width:100%!important}
@@ -28,11 +27,8 @@ body,.gradio-container,.main,.contain{margin:0!important;padding:0!important;max
28
  .gradio-container>.main>.contain>div>div{padding:0!important}
29
  #component-0,#component-1,#component-2,#component-3,#component-4,#component-5{padding:0!important;margin:0!important}
30
  .gap{gap:0!important}
31
- .panel{padding:0!important}
32
- .form{padding:0!important}
33
- .block{padding:0!important}
34
  div[class*="svelte"]{padding-left:0!important;padding-right:0!important}
35
- /* Content areas get minimal padding */
36
  .bdp-wrap{padding:6px 4px!important}
37
  .bdp-article{padding:12px 8px 30px!important;max-width:100%!important}
38
  .controls-row{padding:0 4px!important}
@@ -52,6 +48,29 @@ if "shorts-feed-mode" not in app.CSS:
52
  if "FULLWIDTH: kill all Gradio padding" not in app.CSS:
53
  app.CSS += FULLWIDTH_CSS
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  # ══ Patch render_homepage_html: add shorts carousel ══
56
  _orig_render_homepage = app.render_homepage_html
57
  def _patched_render_homepage(articles, *args, **kwargs):
@@ -72,23 +91,70 @@ def _patched_render_homepage(articles, *args, **kwargs):
72
  return html
73
  app.render_homepage_html = _patched_render_homepage
74
 
75
- # ══ Patch render_video_page_html: fullheight button + ensure multi-part ══
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  _orig_render_video_page = app.render_video_page_html
77
  def _patched_render_video_page():
78
  html = _orig_render_video_page()
79
- # Add fullheight toggle button in each highlight slide
80
  toggle_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>'
81
  html = html.replace('<div class="tiktok-unmute-hint"', toggle_btn + '<div class="tiktok-unmute-hint"')
82
  return html
83
  app.render_video_page_html = _patched_render_video_page
84
 
85
- # ══ Ensure _fetch_24h_vid in fetch_tiktok_feed_videos creates multiple slides per multi-part article ══
86
- # The app.py already has _extract_24h_video_urls which probes 02, 03 etc.
87
- # And _fetch_24h_vid returns a list when multiple parts found.
88
- # Verify this is working by checking the function exists:
89
- if hasattr(app, '_extract_24h_video_urls'):
90
- print("[app_wrapper] Multi-part 24h extraction confirmed available")
91
-
92
  # ══ Patch read_article: 24h URLs → shorts TikTok feed ══
93
  _orig_read_article = app.read_article
94
  def _patched_read_article(url):
@@ -101,7 +167,7 @@ def _patched_read_article(url):
101
  return _orig_read_article(url)
102
 
103
  def _render_shorts_tiktok_feed(current_url):
104
- """Render shorts as TikTok feed using SAME classes as highlight TikTok."""
105
  from concurrent.futures import ThreadPoolExecutor, as_completed
106
 
107
  try:
@@ -113,11 +179,21 @@ def _render_shorts_tiktok_feed(current_url):
113
  return None
114
 
115
  def _fetch_video(art):
116
- vid = _extract_24h_video_url(art["link"])
117
- if vid:
118
- return {"title": art["title"], "link": art["link"], "img": art.get("img",""),
119
- "src": vid["src"], "poster": vid["poster"], "vtype": vid["vtype"]}
120
- return None
 
 
 
 
 
 
 
 
 
 
121
 
122
  videos = []
123
  with ThreadPoolExecutor(max_workers=6) as ex:
@@ -125,18 +201,19 @@ def _render_shorts_tiktok_feed(current_url):
125
  for f in as_completed(futures):
126
  try:
127
  r = f.result()
128
- if r: videos.append(r)
129
  except: pass
130
 
131
  if not videos:
132
  return None
133
 
 
134
  current_aid = app.make_id(current_url)
135
  ordered = []
136
  rest = []
137
  for v in videos:
138
  if app.make_id(v["link"]) == current_aid:
139
- ordered.insert(0, v)
140
  else:
141
  rest.append(v)
142
  ordered.extend(rest)
 
18
  .tiktok-fullheight-btn.active{background:rgba(92,184,122,.5)}
19
  """
20
 
 
21
  FULLWIDTH_CSS = """
22
  /* ══ FULLWIDTH: kill all Gradio padding ══ */
23
  body,.gradio-container,.main,.contain{margin:0!important;padding:0!important;max-width:100%!important;width:100%!important}
 
27
  .gradio-container>.main>.contain>div>div{padding:0!important}
28
  #component-0,#component-1,#component-2,#component-3,#component-4,#component-5{padding:0!important;margin:0!important}
29
  .gap{gap:0!important}
30
+ .panel,.form,.block{padding:0!important}
 
 
31
  div[class*="svelte"]{padding-left:0!important;padding-right:0!important}
 
32
  .bdp-wrap{padding:6px 4px!important}
33
  .bdp-article{padding:12px 8px 30px!important;max-width:100%!important}
34
  .controls-row{padding:0 4px!important}
 
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
+
74
  # ══ Patch render_homepage_html: add shorts carousel ══
75
  _orig_render_homepage = app.render_homepage_html
76
  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():
152
  html = _orig_render_video_page()
 
153
  toggle_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>'
154
  html = html.replace('<div class="tiktok-unmute-hint"', toggle_btn + '<div class="tiktok-unmute-hint"')
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):
 
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:
 
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:
 
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 = []
214
  for v in videos:
215
  if app.make_id(v["link"]) == current_aid:
216
+ ordered.append(v)
217
  else:
218
  rest.append(v)
219
  ordered.extend(rest)