Spaces:
Running
Running
| """ | |
| VTV Channels Scraper | |
| Fetches stream URLs from hd.xemtv.net PHP endpoints for VTV1-VTV10 + VTV Cần Thơ | |
| """ | |
| import requests, re, time, threading | |
| UA = { | |
| "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", | |
| "Accept-Language": "vi-VN,vi;q=0.9", | |
| "Referer": "https://hd.xemtv.net/", | |
| } | |
| XEMTV_PHP_ENDPOINTS = { | |
| "vtv1": "https://hd.xemtv.net/kenh/vtv1.php", | |
| "vtv2": "https://hd.xemtv.net/kenh/vtv2.php", | |
| "vtv3": "https://hd.xemtv.net/kenh/vtv3.php", | |
| "vtv4": "https://hd.xemtv.net/kenh/vtv4.php", | |
| "vtv5": "https://hd.xemtv.net/kenh/vtv5.php", | |
| "vtv6": "https://hd.xemtv.net/kenh/vtv6.php", | |
| "vtv7": "https://hd.xemtv.net/kenh/vtv7.php", | |
| "vtv8": "https://hd.xemtv.net/kenh/vtv8.php", | |
| "vtv9": "https://hd.xemtv.net/kenh/vtv9.php", | |
| "vtv10": "https://hd.xemtv.net/kenh/vtv10.php", | |
| } | |
| CHANNEL_NAMES = { | |
| "vtv1": "VTV1", "vtv2": "VTV2", "vtv3": "VTV3", "vtv4": "VTV4", | |
| "vtv5": "VTV5", "vtv6": "VTV6", "vtv7": "VTV7", "vtv8": "VTV8", | |
| "vtv9": "VTV9", "vtv10": "VTV Cần Thơ", | |
| } | |
| CDN_FALLBACK = { | |
| "vtv1": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv1-manifest.m3u8", | |
| "vtv2": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv2-manifest.m3u8", | |
| "vtv3": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv3-manifest.m3u8", | |
| "vtv4": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv4-manifest.m3u8", | |
| "vtv5": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv5-manifest.m3u8", | |
| "vtv6": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv6-manifest.m3u8", | |
| "vtv7": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv7-manifest.m3u8", | |
| "vtv8": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv8-manifest.m3u8", | |
| "vtv9": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv9-manifest.m3u8", | |
| "vtv10": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv10-manifest.m3u8", | |
| "_fpt_vtv1": "https://live.fptplay53.net/fnxch2/vtv1hd_abr.smil/chunklist.m3u8", | |
| "_fpt_vtv2": "https://live.fptplay53.net/fnxch2/vtv2hd_abr.smil/chunklist.m3u8", | |
| "_fpt_vtv3": "https://live.fptplay53.net/fnxch2/vtv3hd_abr.smil/chunklist.m3u8", | |
| "_fpt_vtv4": "https://live.fptplay53.net/fnxch2/vtv4hd_abr.smil/chunklist.m3u8", | |
| "_fpt_vtv5": "https://live-a.fptplay53.net/live/media/VTV5HD/live_hls_avc/index.m3u8", | |
| "_fpt_vtv6": "https://live.fptplay53.net/fnxch2/vtv6hd_abr.smil/chunklist.m3u8", | |
| "_fpt_vtv7": "https://live.fptplay53.net/fnxhd1/vtv7hd_vhls.smil/chunklist_b5000000.m3u8", | |
| "_fpt_vtv8": "https://live.fptplay53.net/epzhd1/vtv8hd_vhls.smil/chunklist.m3u8", | |
| "_fpt_vtv9": "https://live.fptplay53.net/fnxhd1/vtv9hd_vhls.smil/chunklist.m3u8", | |
| "_fpt_vtv10": "https://live.fptplay53.net/fnxch2/vtvcantho_abr.smil/chunklist.m3u8", | |
| } | |
| _vtv_cache = {} | |
| _vtv_lock = threading.Lock() | |
| _CACHE_TTL = 180 | |
| def _cached(key): | |
| with _vtv_lock: | |
| if key in _vtv_cache and time.time() - _vtv_cache[key]['t'] < _CACHE_TTL: | |
| return _vtv_cache[key]['d'] | |
| return None | |
| def _set_cache(key, data): | |
| with _vtv_lock: | |
| _vtv_cache[key] = {'t': time.time(), 'd': data} | |
| def extract_m3u8_from_html(html): | |
| if not html: | |
| return None | |
| m = re.search(r"file\s*:\s*['\"]([^'\"]*\.m3u8[^'\"]*)['\"]", html, re.IGNORECASE) | |
| if m: | |
| url = m.group(1).strip() | |
| if len(url) > 20: | |
| return url | |
| m = re.search(r"(https?://[^\s\"'<>\\]+\.m3u8[^\s\"'<>\\]*)", html, re.IGNORECASE) | |
| if m: | |
| url = m.group(1).strip() | |
| if len(url) > 20: | |
| return url | |
| return None | |
| def fetch_vtv_stream(channel_id): | |
| channel_id = channel_id.lower().strip() | |
| name_map = { | |
| 'vtvct': 'vtv10', 'vtv-can-tho': 'vtv10', 'vtv can tho': 'vtv10', | |
| 'vtv_can_tho': 'vtv10', 'cantho': 'vtv10', 'cần thơ': 'vtv10', | |
| 'vietnam_vtv1': 'vtv1', 'vietnam_vtv2': 'vtv2', 'vietnam_vtv3': 'vtv3', | |
| 'vietnam_vtv4': 'vtv4', 'vietnam_vtv5': 'vtv5', 'vietnam_vtv6': 'vtv6', | |
| 'vietnam_vtv7': 'vtv7', 'vietnam_vtv8': 'vtv8', 'vietnam_vtv9': 'vtv9', | |
| } | |
| channel_id = name_map.get(channel_id, channel_id) | |
| cached = _cached(channel_id) | |
| if cached: | |
| return cached | |
| vtvgourl = CDN_FALLBACK.get(channel_id) | |
| if vtvgourl: | |
| _set_cache(channel_id, vtvgourl) | |
| return vtvgourl | |
| php_url = XEMTV_PHP_ENDPOINTS.get(channel_id) | |
| if php_url: | |
| try: | |
| r = requests.get(php_url, headers=UA, timeout=15, allow_redirects=True, verify=False) | |
| if r.status_code == 200: | |
| m3u8 = extract_m3u8_from_html(r.text) | |
| if m3u8: | |
| _set_cache(channel_id, m3u8) | |
| return m3u8 | |
| except: | |
| pass | |
| fpt_key = f"_fpt_{channel_id}" | |
| fpt_url = CDN_FALLBACK.get(fpt_key) | |
| if fpt_url: | |
| _set_cache(channel_id, fpt_url) | |
| return fpt_url | |
| return None | |
| def get_all_vtv_streams(): | |
| channels = [] | |
| for ch_id, php_url in XEMTV_PHP_ENDPOINTS.items(): | |
| stream_url = fetch_vtv_stream(ch_id) | |
| channels.append({ | |
| 'id': ch_id, | |
| 'name': CHANNEL_NAMES.get(ch_id, ch_id.upper()), | |
| 'stream_url': stream_url, | |
| }) | |
| return channels | |
| XEMTV_CHANNELS = {v: k for k, v in CHANNEL_NAMES.items()} | |
| CDN_STREAMS = {v: k for k, v in CDN_FALLBACK.items()} | |