bep40 commited on
Commit
722da1a
·
verified ·
1 Parent(s): 9c54318

Upload vtv_api.py

Browse files
Files changed (1) hide show
  1. vtv_api.py +176 -0
vtv_api.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ VTV Channels API - Backend endpoints for VTV1-VTV10 + VTVPrime
3
+ Fetches stream URLs from hd.xemtv.net PHP endpoints and VTVPrime
4
+ """
5
+ import re, time, threading
6
+ import requests
7
+ from fastapi import APIRouter, Query
8
+ from fastapi.responses import JSONResponse, Response
9
+
10
+ router = APIRouter()
11
+
12
+ UA = {
13
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
14
+ "Accept-Language": "vi-VN,vi;q=0.9",
15
+ "Referer": "https://hd.xemtv.net/",
16
+ }
17
+
18
+ # Channel ID -> xemtv.net PHP endpoint mapping
19
+ XEMTV_PHP_ENDPOINTS = {
20
+ "vtv1": "https://hd.xemtv.net/kenh/vtv1.php",
21
+ "vtv2": "https://hd.xemtv.net/kenh/vtv2.php",
22
+ "vtv3": "https://hd.xemtv.net/kenh/vtv3.php",
23
+ "vtv4": "https://hd.xemtv.net/kenh/vtv4.php",
24
+ "vtv5": "https://hd.xemtv.net/kenh/vtv5.php",
25
+ "vtv6": "https://hd.xemtv.net/kenh/vtv6.php",
26
+ "vtv7": "https://hd.xemtv.net/kenh/vtv7.php",
27
+ "vtv8": "https://hd.xemtv.net/kenh/vtv8.php",
28
+ "vtv9": "https://hd.xemtv.net/kenh/vtv9.php",
29
+ "vtv10": "https://hd.xemtv.net/kenh/vtv10.php",
30
+ "vtvprime": "https://hd.xemtv.net/kenh/vtvprime.php",
31
+ }
32
+
33
+ # Channel display names
34
+ CHANNEL_NAMES = {
35
+ "vtv1": "VTV1",
36
+ "vtv2": "VTV2",
37
+ "vtv3": "VTV3",
38
+ "vtv4": "VTV4",
39
+ "vtv5": "VTV5",
40
+ "vtv6": "VTV6",
41
+ "vtv7": "VTV7",
42
+ "vtv8": "VTV8",
43
+ "vtv9": "VTV9",
44
+ "vtv10": "VTV10",
45
+ "vtvprime": "VTVPrime",
46
+ }
47
+
48
+ # Fallback CDN streams (fptplay / other sources)
49
+ CDN_FALLBACK = {
50
+ "vtv1": "https://live.fptplay53.net/fnxch2/vtv1hd_abr.smil/chunklist.m3u8",
51
+ "vtv2": "https://live.fptplay53.net/fnxch2/vtv2hd_abr.smil/chunklist.m3u8",
52
+ "vtv3": "https://live.fptplay53.net/fnxch2/vtv3hd_abr.smil/chunklist.m3u8",
53
+ "vtv4": "https://live.fptplay53.net/fnxch2/vtv4hd_abr.smil/chunklist.m3u8",
54
+ "vtv5": "https://live-a.fptplay53.net/live/media/VTV5HD/live_hls_avc/index.m3u8",
55
+ "vtv6": "https://live.fptplay53.net/fnxch2/vtv6hd_abr.smil/chunklist.m3u8",
56
+ "vtv7": "https://live.fptplay53.net/fnxhd1/vtv7hd_vhls.smil/chunklist_b5000000.m3u8",
57
+ "vtv8": "https://live.fptplay53.net/epzhd1/vtv8hd_vhls.smil/chunklist.m3u8",
58
+ "vtv9": "https://live.fptplay53.net/fnxhd1/vtv9hd_vhls.smil/chunklist.m3u8",
59
+ "vtv10": "https://live.fptplay53.net/fnxch2/vtvcantho_abr.smil/chunklist.m3u8",
60
+ }
61
+
62
+ _vtv_cache = {}
63
+ _vtv_lock = threading.Lock()
64
+ _CACHE_TTL = 180 # 3 minutes
65
+
66
+
67
+ def _cached(key):
68
+ with _vtv_lock:
69
+ if key in _vtv_cache and time.time() - _vtv_cache[key]['t'] < _CACHE_TTL:
70
+ return _vtv_cache[key]['d']
71
+ return None
72
+
73
+
74
+ def _set_cache(key, data):
75
+ with _vtv_lock:
76
+ _vtv_cache[key] = {'t': time.time(), 'd': data}
77
+
78
+
79
+ def extract_m3u8_from_html(html):
80
+ """Extract m3u8 URL from xemtv PHP page (jwplayer config)."""
81
+ if not html:
82
+ return None
83
+ # jwplayer config: file: 'URL'
84
+ m = re.search(r"file\s*:\s*['\"]([^'\"]*\.m3u8[^'\"]*)['\"]", html, re.IGNORECASE)
85
+ if m:
86
+ url = m.group(1).strip()
87
+ if len(url) > 20:
88
+ return url
89
+ # Generic m3u8 pattern
90
+ m = re.search(r"(https?://[^\s\"'<>\\]+\.m3u8[^\s\"'<>\\]*)", html, re.IGNORECASE)
91
+ if m:
92
+ url = m.group(1).strip()
93
+ if len(url) > 20:
94
+ return url
95
+ return None
96
+
97
+
98
+ def fetch_vtv_stream(channel_id):
99
+ """Fetch m3u8 stream URL for a VTV channel."""
100
+ channel_id = channel_id.lower().strip()
101
+
102
+ # Normalize name
103
+ name_map = {
104
+ 'vtvct': 'vtv10', 'vtv-can-tho': 'vtv10', 'vtv can tho': 'vtv10',
105
+ 'vtv_can_tho': 'vtv10', 'cantho': 'vtv10',
106
+ 'vietnam_vtv1': 'vtv1', 'vietnam_vtv2': 'vtv2', 'vietnam_vtv3': 'vtv3',
107
+ 'vietnam_vtv4': 'vtv4', 'vietnam_vtv5': 'vtv5', 'vietnam_vtv6': 'vtv6',
108
+ 'vietnam_vtv7': 'vtv7', 'vietnam_vtv8': 'vtv8', 'vietnam_vtv9': 'vtv9',
109
+ }
110
+ channel_id = name_map.get(channel_id, channel_id)
111
+
112
+ # Check cache
113
+ cached = _cached(channel_id)
114
+ if cached:
115
+ return cached
116
+
117
+ php_url = XEMTV_PHP_ENDPOINTS.get(channel_id)
118
+ if php_url:
119
+ try:
120
+ r = requests.get(php_url, headers=UA, timeout=15, allow_redirects=True)
121
+ if r.status_code == 200:
122
+ m3u8 = extract_m3u8_from_html(r.text)
123
+ if m3u8:
124
+ _set_cache(channel_id, m3u8)
125
+ return m3u8
126
+ except:
127
+ pass
128
+
129
+ # Fallback to CDN
130
+ fallback = CDN_FALLBACK.get(channel_id)
131
+ if fallback:
132
+ _set_cache(channel_id, fallback)
133
+ return fallback
134
+
135
+ return None
136
+
137
+
138
+ @router.get("/api/vtv/streams")
139
+ def api_vtv_streams():
140
+ """Get all VTV channel stream URLs. Returns {channel_id: {name, stream_url}}."""
141
+ result = {}
142
+ for ch_id in CHANNEL_NAMES:
143
+ stream_url = fetch_vtv_stream(ch_id)
144
+ result[ch_id] = {
145
+ "name": CHANNEL_NAMES[ch_id],
146
+ "stream_url": stream_url,
147
+ }
148
+ return JSONResponse(result)
149
+
150
+
151
+ @router.get("/api/vtv/stream/{channel_id}")
152
+ def api_vtv_stream(channel_id: str):
153
+ """Get stream URL for a specific VTV channel."""
154
+ stream_url = fetch_vtv_stream(channel_id)
155
+ if stream_url:
156
+ return JSONResponse({"stream_url": stream_url})
157
+ return JSONResponse({"error": "stream not found"}, status_code=404)
158
+
159
+
160
+ @router.get("/api/proxy/page")
161
+ def proxy_page(url: str = Query(...)):
162
+ """Proxy a web page and return its HTML content. Used for scraping xemtv PHP pages."""
163
+ try:
164
+ headers = {**UA}
165
+ if "xemtv.net" in url:
166
+ headers["Referer"] = "https://hd.xemtv.net/"
167
+ r = requests.get(url, headers=headers, timeout=15, allow_redirects=True)
168
+ if r.status_code != 200:
169
+ return Response(status_code=502, content="upstream error")
170
+ return Response(
171
+ content=r.text.encode("utf-8"),
172
+ media_type="text/html; charset=utf-8",
173
+ headers={"Access-Control-Allow-Origin": "*"}
174
+ )
175
+ except:
176
+ return Response(status_code=502, content="proxy error")