""" 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()}