bep40 commited on
Commit
f22c37c
·
verified ·
1 Parent(s): 0cefbd8

Fix VTV proxy: separate m3u8 variant proxy from segment proxy, fix content-type handling, add verify=False for self-signed certs

Browse files
Files changed (1) hide show
  1. vtv_api.py +34 -9
vtv_api.py CHANGED
@@ -61,6 +61,9 @@ FPTPLAY_URLS = {
61
  "vtv6": "https://live-a.fptplay53.net/live/media/vtv6/live247-hls-avc/vtv6-avc1_5600000=10000-mp4a_131600=20000.m3u8",
62
  }
63
 
 
 
 
64
  # Channels with no public stream available
65
  UNAVAILABLE_CHANNELS = {"vtv10", "vtvprime"}
66
 
@@ -182,10 +185,12 @@ def proxy_page(url: str = Query(...)):
182
 
183
  @router.get("/api/proxy/m3u8/vtv")
184
  def proxy_vtv_m3u8(url: str = Query(...)):
185
- """Proxy m3u8 playlist, rewriting segment URLs through our proxy."""
 
186
  try:
187
  headers = {"User-Agent": UA["User-Agent"], "Accept": "*/*", "Referer": "https://vtv.vn/"}
188
- verify = "vtvdigital.vn" in url
 
189
  r = requests.get(url, headers=headers, timeout=15, allow_redirects=True, verify=verify)
190
  if r.status_code != 200:
191
  return Response(status_code=502, content="upstream error")
@@ -193,6 +198,9 @@ def proxy_vtv_m3u8(url: str = Query(...)):
193
  lines = content.split('\n')
194
  rewritten = []
195
  base_url = url.rsplit('/', 1)[0] + '/'
 
 
 
196
  for line in lines:
197
  line = line.strip()
198
  if not line or line.startswith('#'):
@@ -201,25 +209,42 @@ def proxy_vtv_m3u8(url: str = Query(...)):
201
  seg_url = line
202
  if not seg_url.startswith('http'):
203
  seg_url = base_url + seg_url
204
- rewritten.append("/api/proxy/seg/vtv?url=" + requests.utils.quote(seg_url, safe=""))
205
- return Response(content='\n'.join(rewritten).encode("utf-8"), media_type="application/vnd.apple.mpegurl", headers={"Access-Control-Allow-Origin": "*", "Cache-Control": "no-cache"})
 
 
 
 
 
 
206
  except Exception as e:
207
  return Response(status_code=502, content="proxy error: " + str(e))
208
 
209
  @router.get("/api/proxy/seg/vtv")
210
  def proxy_vtv_segment(url: str = Query(...)):
211
- """Proxy video segments (.ts files)."""
212
  try:
213
  headers = {"User-Agent": UA["User-Agent"], "Accept": "*/*", "Referer": "https://vtv.vn/"}
214
- verify = "vtvdigital.vn" in url
 
215
  r = requests.get(url, headers=headers, timeout=30, allow_redirects=True, verify=verify)
216
  if r.status_code != 200:
217
  return Response(status_code=502, content="upstream error")
218
  data = r.content
219
- # Fix: some segments start with PNG header (188 bytes offset)
220
- if len(data) > 188 and data[0:4] == b'\x89PNG' and data[188] == 0x47:
 
 
 
 
 
 
 
 
 
 
221
  data = data[188:]
222
- return Response(content=data, media_type="video/mp2t", headers={"Access-Control-Allow-Origin": "*", "Cache-Control": "public, max-age=3600"})
223
  except:
224
  return Response(status_code=502, content="proxy error")
225
 
 
61
  "vtv6": "https://live-a.fptplay53.net/live/media/vtv6/live247-hls-avc/vtv6-avc1_5600000=10000-mp4a_131600=20000.m3u8",
62
  }
63
 
64
+ # ===== DEAD: xemtv.net (all 403 from Cloudflare, kept for reference) =====
65
+ # XEMTV_PHP_ENDPOINTS = { ... } # All dead as of 2026-06
66
+
67
  # Channels with no public stream available
68
  UNAVAILABLE_CHANNELS = {"vtv10", "vtvprime"}
69
 
 
185
 
186
  @router.get("/api/proxy/m3u8/vtv")
187
  def proxy_vtv_m3u8(url: str = Query(...)):
188
+ """Proxy m3u8 playlist. Rewrites .ts segments and .m3u8 variants through our proxy chain.
189
+ Uses fully-qualified absolute URLs so HLS.js resolves them correctly."""
190
  try:
191
  headers = {"User-Agent": UA["User-Agent"], "Accept": "*/*", "Referer": "https://vtv.vn/"}
192
+ # Determine if we need verify=False (self-signed cert on some CDNs)
193
+ verify = "vtvdigital.vn" not in url
194
  r = requests.get(url, headers=headers, timeout=15, allow_redirects=True, verify=verify)
195
  if r.status_code != 200:
196
  return Response(status_code=502, content="upstream error")
 
198
  lines = content.split('\n')
199
  rewritten = []
200
  base_url = url.rsplit('/', 1)[0] + '/'
201
+ # Build the base URL for this proxy endpoint
202
+ proxy_base = "/api/proxy/m3u8/vtv?url="
203
+ seg_proxy_base = "/api/proxy/seg/vtv?url="
204
  for line in lines:
205
  line = line.strip()
206
  if not line or line.startswith('#'):
 
209
  seg_url = line
210
  if not seg_url.startswith('http'):
211
  seg_url = base_url + seg_url
212
+ # Distinguish between .m3u8 variant playlists and .ts segments
213
+ if seg_url.endswith('.m3u8'):
214
+ # Variant playlist → proxy through this same m3u8 endpoint (full URL)
215
+ rewritten.append(proxy_base + requests.utils.quote(seg_url, safe=""))
216
+ else:
217
+ # .ts segment → proxy through segment endpoint (full URL)
218
+ rewritten.append(seg_proxy_base + requests.utils.quote(seg_url, safe=""))
219
+ return Response(content='\n'.join(rewritten).encode("utf-8"), media_type="application/vnd.apple.mpegurl", headers={"Access-Control-Allow-Origin": "*", "Cache-Control": "no-cache, no-store", "Access-Control-Allow-Headers": "*"})
220
  except Exception as e:
221
  return Response(status_code=502, content="proxy error: " + str(e))
222
 
223
  @router.get("/api/proxy/seg/vtv")
224
  def proxy_vtv_segment(url: str = Query(...)):
225
+ """Proxy video segments (.ts files) and other binary content."""
226
  try:
227
  headers = {"User-Agent": UA["User-Agent"], "Accept": "*/*", "Referer": "https://vtv.vn/"}
228
+ # Determine if we need verify=False (self-signed cert on some CDNs)
229
+ verify = "vtvdigital.vn" not in url
230
  r = requests.get(url, headers=headers, timeout=30, allow_redirects=True, verify=verify)
231
  if r.status_code != 200:
232
  return Response(status_code=502, content="upstream error")
233
  data = r.content
234
+ # Determine content type from URL
235
+ content_type = "video/mp2t" # default for .ts
236
+ if url.endswith('.m3u8') or url.endswith('.m3u'):
237
+ content_type = "application/vnd.apple.mpegurl"
238
+ elif url.endswith('.mp4'):
239
+ content_type = "video/mp4"
240
+ elif url.endswith('.aac'):
241
+ content_type = "audio/aac"
242
+ elif url.endswith('.webm'):
243
+ content_type = "video/webm"
244
+ # Fix: some TS segments start with PNG header (188 bytes offset)
245
+ if content_type == "video/mp2t" and len(data) > 188 and data[0:4] == b'\x89PNG' and data[188] == 0x47:
246
  data = data[188:]
247
+ return Response(content=data, media_type=content_type, headers={"Access-Control-Allow-Origin": "*", "Cache-Control": "public, max-age=3600", "Access-Control-Allow-Headers": "*"})
248
  except:
249
  return Response(status_code=502, content="proxy error")
250