Spaces:
Running
Running
Upload vtv_api.py
Browse files- vtv_api.py +34 -48
vtv_api.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# VTV Stream —
|
| 2 |
import re, time, threading, json, requests
|
| 3 |
from fastapi import APIRouter, Query
|
| 4 |
from fastapi.responses import JSONResponse, Response, StreamingResponse
|
|
@@ -18,18 +18,18 @@ HARDCODED_URLS = {"vtv6": "https://live.fptplay53.net/live/media/v6abr/live247-h
|
|
| 18 |
CHANNEL_URLS = {k: (HARDCODED_URLS[k] if k in HARDCODED_URLS else None) for k in ["vtv1","vtv2","vtv3","vtv4","vtv5","vtv6","vtv7","vtv8","vtv9","vtv10"]}
|
| 19 |
CHANNEL_NAMES = {"vtv1":"VTV1","vtv2":"VTV2","vtv3":"VTV3","vtv4":"VTV4","vtv5":"VTV5","vtv6":"VTV6","vtv7":"VTV7","vtv8":"VTV8","vtv9":"VTV9","vtv10":"VTV10","vtvprime":"VTVPrime"}
|
| 20 |
|
| 21 |
-
#
|
| 22 |
-
|
| 23 |
-
"vtv1": "https://
|
| 24 |
-
"vtv2": "https://
|
| 25 |
-
"vtv3": "https://
|
| 26 |
-
"vtv4": "https://
|
| 27 |
-
"vtv5": "https://
|
| 28 |
-
"vtv6": "https://
|
| 29 |
-
"vtv7": "https://
|
| 30 |
-
"vtv8": "https://
|
| 31 |
-
"vtv9": "https://
|
| 32 |
-
"vtv10": "https://
|
| 33 |
}
|
| 34 |
|
| 35 |
M3U_URLS = [
|
|
@@ -88,50 +88,36 @@ def get_channel_url(channel_id):
|
|
| 88 |
_fetch_m3u()
|
| 89 |
with _channel_urls_lock: return CHANNEL_URLS.get(channel_id)
|
| 90 |
|
| 91 |
-
# =====
|
| 92 |
-
@router.get("/api/proxy/
|
| 93 |
-
def
|
| 94 |
-
"""Proxy
|
| 95 |
try:
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
}, timeout=15)
|
| 101 |
if r.status_code != 200:
|
| 102 |
-
return Response(content=f"<html><body><p>
|
| 103 |
media_type="text/html", headers={"Access-Control-Allow-Origin": "*"})
|
| 104 |
html = r.text
|
| 105 |
# Rewrite relative URLs
|
| 106 |
-
html = html.replace('src="/', 'src="https://
|
| 107 |
-
html = html.replace("src='/", "src='https://
|
| 108 |
-
html = html.replace('href="/', 'href="https://
|
| 109 |
-
html = html.replace("href='/", "href='https://
|
| 110 |
-
#
|
| 111 |
-
html = re.sub(r'<meta[^>]*http-equiv=["\']X-Frame-Options["\'][^>]*>', '', html, flags=re.IGNORECASE)
|
| 112 |
-
# Inject CSS: force player fullscreen, hide chrome
|
| 113 |
html = html.replace('</head>', '''
|
| 114 |
<style>
|
| 115 |
-
*{margin:0;padding:0
|
| 116 |
body{background:#000;overflow:hidden;margin:0;padding:0}
|
| 117 |
-
|
| 118 |
-
#app,#root,.player,.container,.streaming-dimensions,#videojs,#player,#my-player,
|
| 119 |
-
.player-container,.video-container,.channel-container,.live-player,
|
| 120 |
-
[class*="player"],[class*="video"],[class*="stream"],[class*="channel"],
|
| 121 |
-
video,iframe,object,embed{
|
| 122 |
-
width:100%!important;height:100%!important;max-width:100%!important;min-height:0!important;
|
| 123 |
-
position:relative;
|
| 124 |
-
}
|
| 125 |
-
.app,.app-wrapper,#app,#root{position:fixed;inset:0;width:100vw;height:100vh}
|
| 126 |
-
header,nav,footer,.header,.footer,.nav,.sidebar,.menu,.top-bar,.bottom-bar,
|
| 127 |
-
[class*="header"],[class*="nav"],[class*="footer"],[class*="sidebar"],[class*="menu"],
|
| 128 |
-
[class*="top"],[class*="bottom"],[data-testid*="header"],[data-testid*="footer"]{display:none!important}
|
| 129 |
</style>
|
| 130 |
</head>''')
|
| 131 |
return Response(content=html.encode('utf-8'), media_type="text/html",
|
| 132 |
headers={"Access-Control-Allow-Origin": "*", "X-Frame-Options": "ALLOWALL", "Content-Security-Policy": "frame-ancestors *"})
|
| 133 |
except Exception as e:
|
| 134 |
-
return Response(content=f"<html><body><p>
|
| 135 |
media_type="text/html", headers={"Access-Control-Allow-Origin": "*"})
|
| 136 |
|
| 137 |
# ===================== API ENDPOINTS =====================
|
|
@@ -142,7 +128,7 @@ def api_vtv_streams():
|
|
| 142 |
result = {}
|
| 143 |
for ch_id in CHANNEL_NAMES:
|
| 144 |
stream_url = urls.get(ch_id)
|
| 145 |
-
|
| 146 |
is_flv = stream_url and ".flv" in stream_url
|
| 147 |
result[ch_id] = {
|
| 148 |
"name": CHANNEL_NAMES[ch_id],
|
|
@@ -150,7 +136,7 @@ def api_vtv_streams():
|
|
| 150 |
"proxy_url": f"/api/proxy/flv?url={quote(stream_url, safe='')}" if stream_url and is_flv else "",
|
| 151 |
"proxy_url_hls": f"/api/proxy/stream?url={quote(stream_url, safe='')}" if stream_url and not is_flv else "",
|
| 152 |
"is_flv": is_flv,
|
| 153 |
-
"proxy_iframe_url": f"/api/proxy/
|
| 154 |
"status": "ok"
|
| 155 |
}
|
| 156 |
return JSONResponse(result)
|
|
@@ -160,7 +146,7 @@ def api_vtv_stream(channel_id: str):
|
|
| 160 |
channel_id = channel_id.lower().strip()
|
| 161 |
if channel_id not in CHANNEL_NAMES: return JSONResponse({"error":"not found"}, status_code=404)
|
| 162 |
stream_url = get_channel_url(channel_id)
|
| 163 |
-
|
| 164 |
is_flv = stream_url and ".flv" in stream_url
|
| 165 |
return JSONResponse({
|
| 166 |
"name": CHANNEL_NAMES[channel_id],
|
|
@@ -168,7 +154,7 @@ def api_vtv_stream(channel_id: str):
|
|
| 168 |
"proxy_url": f"/api/proxy/flv?url={quote(stream_url, safe='')}" if stream_url and is_flv else "",
|
| 169 |
"proxy_url_hls": f"/api/proxy/stream?url={quote(stream_url, safe='')}" if stream_url and not is_flv else "",
|
| 170 |
"is_flv": is_flv,
|
| 171 |
-
"proxy_iframe_url": f"/api/proxy/
|
| 172 |
"status": "ok"
|
| 173 |
})
|
| 174 |
|
|
|
|
| 1 |
+
# VTV Stream — SV2 iframe cho tất cả kênh + M3U fallback + EPG
|
| 2 |
import re, time, threading, json, requests
|
| 3 |
from fastapi import APIRouter, Query
|
| 4 |
from fastapi.responses import JSONResponse, Response, StreamingResponse
|
|
|
|
| 18 |
CHANNEL_URLS = {k: (HARDCODED_URLS[k] if k in HARDCODED_URLS else None) for k in ["vtv1","vtv2","vtv3","vtv4","vtv5","vtv6","vtv7","vtv8","vtv9","vtv10"]}
|
| 19 |
CHANNEL_NAMES = {"vtv1":"VTV1","vtv2":"VTV2","vtv3":"VTV3","vtv4":"VTV4","vtv5":"VTV5","vtv6":"VTV6","vtv7":"VTV7","vtv8":"VTV8","vtv9":"VTV9","vtv10":"VTV10","vtvprime":"VTVPrime"}
|
| 20 |
|
| 21 |
+
# SV2 iframe URLs — có player thật, hỗ trợ mobile
|
| 22 |
+
SV2_URLS = {
|
| 23 |
+
"vtv1": "https://sv2.xemtivitop.com/live/hot/vtv1.php",
|
| 24 |
+
"vtv2": "https://sv2.xemtivitop.com/live/hot/vtv2.php",
|
| 25 |
+
"vtv3": "https://sv2.xemtivitop.com/live/hot/vtv3.php",
|
| 26 |
+
"vtv4": "https://sv2.xemtivitop.com/live/hot/vtv4.php",
|
| 27 |
+
"vtv5": "https://sv2.xemtivitop.com/live/hot/vtv5.php",
|
| 28 |
+
"vtv6": "https://sv2.xemtivitop.com/live/hot/vtv6.php",
|
| 29 |
+
"vtv7": "https://sv2.xemtivitop.com/live/hot/vtv7.php",
|
| 30 |
+
"vtv8": "https://sv2.xemtivitop.com/live/hot/vtv8.php",
|
| 31 |
+
"vtv9": "https://sv2.xemtivitop.com/live/hot/vtv9.php",
|
| 32 |
+
"vtv10": "https://sv2.xemtivitop.com/live/hot/vtv10.php",
|
| 33 |
}
|
| 34 |
|
| 35 |
M3U_URLS = [
|
|
|
|
| 88 |
_fetch_m3u()
|
| 89 |
with _channel_urls_lock: return CHANNEL_URLS.get(channel_id)
|
| 90 |
|
| 91 |
+
# ===== SV2 PROXY (IFRAME-FRIENDLY) =====
|
| 92 |
+
@router.get("/api/proxy/sv2/iframe")
|
| 93 |
+
def proxy_sv2_iframe(url: str = Query(...)):
|
| 94 |
+
"""Proxy SV2 iframe — rewrite relative URLs, cho phép iframe"""
|
| 95 |
try:
|
| 96 |
+
s = requests.Session()
|
| 97 |
+
try: s.get("https://sv2.xemtivitop.com/", headers=UA, timeout=5, verify=False)
|
| 98 |
+
except: pass
|
| 99 |
+
r = s.get(url, headers={**UA, "Referer": "https://sv2.xemtivitop.com/"}, timeout=15, verify=False)
|
|
|
|
| 100 |
if r.status_code != 200:
|
| 101 |
+
return Response(content=f"<html><body><p>sv2 error: {r.status_code}</p></body></html>",
|
| 102 |
media_type="text/html", headers={"Access-Control-Allow-Origin": "*"})
|
| 103 |
html = r.text
|
| 104 |
# Rewrite relative URLs
|
| 105 |
+
html = html.replace('src="/', 'src="https://sv2.xemtivitop.com/')
|
| 106 |
+
html = html.replace("src='/", "src='https://sv2.xemtivitop.com/")
|
| 107 |
+
html = html.replace('href="/', 'href="https://sv2.xemtivitop.com/')
|
| 108 |
+
html = html.replace("href='/", "href='https://sv2.xemtivitop.com/")
|
| 109 |
+
# Force fullscreen, hide margins
|
|
|
|
|
|
|
| 110 |
html = html.replace('</head>', '''
|
| 111 |
<style>
|
| 112 |
+
*{margin:0;padding:0}
|
| 113 |
body{background:#000;overflow:hidden;margin:0;padding:0}
|
| 114 |
+
iframe,video,.video-js,#player,.player{width:100%!important;height:100%!important;max-width:100%!important}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
</style>
|
| 116 |
</head>''')
|
| 117 |
return Response(content=html.encode('utf-8'), media_type="text/html",
|
| 118 |
headers={"Access-Control-Allow-Origin": "*", "X-Frame-Options": "ALLOWALL", "Content-Security-Policy": "frame-ancestors *"})
|
| 119 |
except Exception as e:
|
| 120 |
+
return Response(content=f"<html><body><p>sv2 proxy error: {str(e)[:100]}</p></body></html>",
|
| 121 |
media_type="text/html", headers={"Access-Control-Allow-Origin": "*"})
|
| 122 |
|
| 123 |
# ===================== API ENDPOINTS =====================
|
|
|
|
| 128 |
result = {}
|
| 129 |
for ch_id in CHANNEL_NAMES:
|
| 130 |
stream_url = urls.get(ch_id)
|
| 131 |
+
sv2_url = SV2_URLS.get(ch_id)
|
| 132 |
is_flv = stream_url and ".flv" in stream_url
|
| 133 |
result[ch_id] = {
|
| 134 |
"name": CHANNEL_NAMES[ch_id],
|
|
|
|
| 136 |
"proxy_url": f"/api/proxy/flv?url={quote(stream_url, safe='')}" if stream_url and is_flv else "",
|
| 137 |
"proxy_url_hls": f"/api/proxy/stream?url={quote(stream_url, safe='')}" if stream_url and not is_flv else "",
|
| 138 |
"is_flv": is_flv,
|
| 139 |
+
"proxy_iframe_url": f"/api/proxy/sv2/iframe?url={quote(sv2_url, safe='')}" if sv2_url else "",
|
| 140 |
"status": "ok"
|
| 141 |
}
|
| 142 |
return JSONResponse(result)
|
|
|
|
| 146 |
channel_id = channel_id.lower().strip()
|
| 147 |
if channel_id not in CHANNEL_NAMES: return JSONResponse({"error":"not found"}, status_code=404)
|
| 148 |
stream_url = get_channel_url(channel_id)
|
| 149 |
+
sv2_url = SV2_URLS.get(channel_id)
|
| 150 |
is_flv = stream_url and ".flv" in stream_url
|
| 151 |
return JSONResponse({
|
| 152 |
"name": CHANNEL_NAMES[channel_id],
|
|
|
|
| 154 |
"proxy_url": f"/api/proxy/flv?url={quote(stream_url, safe='')}" if stream_url and is_flv else "",
|
| 155 |
"proxy_url_hls": f"/api/proxy/stream?url={quote(stream_url, safe='')}" if stream_url and not is_flv else "",
|
| 156 |
"is_flv": is_flv,
|
| 157 |
+
"proxy_iframe_url": f"/api/proxy/sv2/iframe?url={quote(sv2_url, safe='')}" if sv2_url else "",
|
| 158 |
"status": "ok"
|
| 159 |
})
|
| 160 |
|