feat: 24h article videos render in TikTok 9:16 fullscreen mode
Browse files- app_wrapper.py +47 -11
app_wrapper.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""Entry point that adds shorts carousel then imports app."""
|
| 2 |
import sys, os
|
| 3 |
|
| 4 |
# 1. First import shorts_carousel module
|
|
@@ -8,32 +8,34 @@ from shorts_carousel import scrape_24h_news_shorts, render_shorts_carousel, SHOR
|
|
| 8 |
# 2. Now import and monkey-patch app
|
| 9 |
import app
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
# Patch CSS
|
| 12 |
if "vslide-shorts-item" not in app.CSS:
|
| 13 |
app.CSS += SHORTS_CSS
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
_orig_fetch_homepage = app.fetch_homepage
|
| 17 |
-
def _patched_fetch_homepage():
|
| 18 |
-
result = _orig_fetch_homepage()
|
| 19 |
-
return result
|
| 20 |
-
app.fetch_homepage = _patched_fetch_homepage
|
| 21 |
|
| 22 |
# Patch render_homepage_html to include shorts carousel
|
| 23 |
_orig_render_homepage = app.render_homepage_html
|
| 24 |
def _patched_render_homepage(articles, *args, **kwargs):
|
| 25 |
html = _orig_render_homepage(articles, *args, **kwargs)
|
| 26 |
-
# Fetch shorts and prepend carousel
|
| 27 |
try:
|
| 28 |
shorts = scrape_24h_news_shorts()[:15]
|
| 29 |
shorts_html = render_shorts_carousel(shorts, app.esc, app.safe_url, app.make_id, app.slug)
|
| 30 |
if shorts_html:
|
| 31 |
-
# Insert after bdp-wrap opening but before the video carousel
|
| 32 |
insert_point = html.find('<div class="vslide-wrap">')
|
| 33 |
if insert_point > 0:
|
| 34 |
html = html[:insert_point] + shorts_html + html[insert_point:]
|
| 35 |
else:
|
| 36 |
-
# Insert at start of bdp-wrap content
|
| 37 |
insert_point = html.find('<div class="bdp-wrap">') + len('<div class="bdp-wrap">')
|
| 38 |
if insert_point > len('<div class="bdp-wrap">'):
|
| 39 |
html = html[:insert_point] + shorts_html + html[insert_point:]
|
|
@@ -42,6 +44,40 @@ def _patched_render_homepage(articles, *args, **kwargs):
|
|
| 42 |
return html
|
| 43 |
app.render_homepage_html = _patched_render_homepage
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
# Re-create the Gradio demo with patched CSS
|
| 46 |
app.demo.css = app.CSS
|
| 47 |
|
|
|
|
| 1 |
+
"""Entry point that adds shorts carousel + TikTok-style article video, then imports app."""
|
| 2 |
import sys, os
|
| 3 |
|
| 4 |
# 1. First import shorts_carousel module
|
|
|
|
| 8 |
# 2. Now import and monkey-patch app
|
| 9 |
import app
|
| 10 |
|
| 11 |
+
# Extra CSS for TikTok-style article video (9:16)
|
| 12 |
+
TIKTOK_ARTICLE_CSS = """
|
| 13 |
+
/* TikTok-style video in article (9:16) */
|
| 14 |
+
.bdp-tiktok-article-video{position:relative;width:100%;max-width:400px;margin:14px auto;aspect-ratio:9/16;background:#000;border-radius:14px;overflow:hidden}
|
| 15 |
+
.bdp-tiktok-article-video video{width:100%;height:100%;object-fit:contain;display:block}
|
| 16 |
+
.bdp-tiktok-article-video .tiktok-unmute-hint{position:absolute;top:10px;right:10px;background:rgba(0,0,0,.6);color:#fff;font-size:11px;padding:5px 10px;border-radius:14px;cursor:pointer;z-index:4}
|
| 17 |
+
.bdp-tiktok-article-video .tiktok-seek-controls{position:absolute;bottom:50px;left:50%;transform:translateX(-50%);display:flex;gap:30px;z-index:4}
|
| 18 |
+
.bdp-tiktok-article-video .tiktok-seek-btn{background:rgba(0,0,0,.4);color:#fff;border:none;padding:6px 12px;border-radius:16px;font-size:11px;cursor:pointer;backdrop-filter:blur(4px)}
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
# Patch CSS
|
| 22 |
if "vslide-shorts-item" not in app.CSS:
|
| 23 |
app.CSS += SHORTS_CSS
|
| 24 |
+
if "bdp-tiktok-article-video" not in app.CSS:
|
| 25 |
+
app.CSS += TIKTOK_ARTICLE_CSS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
# Patch render_homepage_html to include shorts carousel
|
| 28 |
_orig_render_homepage = app.render_homepage_html
|
| 29 |
def _patched_render_homepage(articles, *args, **kwargs):
|
| 30 |
html = _orig_render_homepage(articles, *args, **kwargs)
|
|
|
|
| 31 |
try:
|
| 32 |
shorts = scrape_24h_news_shorts()[:15]
|
| 33 |
shorts_html = render_shorts_carousel(shorts, app.esc, app.safe_url, app.make_id, app.slug)
|
| 34 |
if shorts_html:
|
|
|
|
| 35 |
insert_point = html.find('<div class="vslide-wrap">')
|
| 36 |
if insert_point > 0:
|
| 37 |
html = html[:insert_point] + shorts_html + html[insert_point:]
|
| 38 |
else:
|
|
|
|
| 39 |
insert_point = html.find('<div class="bdp-wrap">') + len('<div class="bdp-wrap">')
|
| 40 |
if insert_point > len('<div class="bdp-wrap">'):
|
| 41 |
html = html[:insert_point] + shorts_html + html[insert_point:]
|
|
|
|
| 44 |
return html
|
| 45 |
app.render_homepage_html = _patched_render_homepage
|
| 46 |
|
| 47 |
+
# Patch render_article_html: when 24h article has video, show in TikTok 9:16 style
|
| 48 |
+
_orig_render_article = app.render_article_html
|
| 49 |
+
def _patched_render_article(article):
|
| 50 |
+
html = _orig_render_article(article)
|
| 51 |
+
# Only patch 24h articles that have video
|
| 52 |
+
if article.get("source") == "24h" and any(item.get("type") == "video" for item in article.get("body", [])):
|
| 53 |
+
# Replace standard video-wrap with TikTok 9:16 container
|
| 54 |
+
html = html.replace(
|
| 55 |
+
'<div class="bdp-video-wrap">',
|
| 56 |
+
'<div class="bdp-tiktok-article-video">'
|
| 57 |
+
)
|
| 58 |
+
# Add seek controls and unmute hint after each video tag
|
| 59 |
+
# Find video tags and add controls
|
| 60 |
+
import re as _re
|
| 61 |
+
def _add_controls(match):
|
| 62 |
+
video_html = match.group(0)
|
| 63 |
+
controls = (
|
| 64 |
+
'<div class="tiktok-unmute-hint" onclick="var v=this.parentElement.querySelector(\'video\');v.muted=!v.muted;this.textContent=v.muted?\'🔇 Bật tiếng\':\'🔊 Đang phát\'">🔇 Bật tiếng</div>'
|
| 65 |
+
'<div class="tiktok-seek-controls">'
|
| 66 |
+
'<button class="tiktok-seek-btn" onclick="var v=this.closest(\'.bdp-tiktok-article-video\').querySelector(\'video\');v.currentTime=Math.max(0,v.currentTime-10)">⏪ 10s</button>'
|
| 67 |
+
'<button class="tiktok-seek-btn" onclick="var v=this.closest(\'.bdp-tiktok-article-video\').querySelector(\'video\');v.currentTime=Math.min(v.duration||9999,v.currentTime+10)">10s ⏩</button>'
|
| 68 |
+
'</div>'
|
| 69 |
+
)
|
| 70 |
+
return video_html + controls
|
| 71 |
+
html = _re.sub(r'</video>', lambda m: '</video>' +
|
| 72 |
+
'<div class="tiktok-unmute-hint" onclick="var v=this.parentElement.querySelector(\'video\');v.muted=!v.muted;this.textContent=v.muted?\'🔇 Bật tiếng\':\'🔊 Đang phát\'">🔇 Bật tiếng</div>'
|
| 73 |
+
'<div class="tiktok-seek-controls">'
|
| 74 |
+
'<button class="tiktok-seek-btn" onclick="var v=this.closest(\'.bdp-tiktok-article-video\').querySelector(\'video\');v.currentTime=Math.max(0,v.currentTime-10)">⏪ 10s</button>'
|
| 75 |
+
'<button class="tiktok-seek-btn" onclick="var v=this.closest(\'.bdp-tiktok-article-video\').querySelector(\'video\');v.currentTime=Math.min(v.duration||9999,v.currentTime+10)">10s ⏩</button>'
|
| 76 |
+
'</div>',
|
| 77 |
+
html, count=1)
|
| 78 |
+
return html
|
| 79 |
+
app.render_article_html = _patched_render_article
|
| 80 |
+
|
| 81 |
# Re-create the Gradio demo with patched CSS
|
| 82 |
app.demo.css = app.CSS
|
| 83 |
|