bep40 commited on
Commit
e2d6476
·
verified ·
1 Parent(s): 495756e

Upload vtv_scraper.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. vtv_scraper.py +156 -0
vtv_scraper.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ VTV Channels Scraper
3
+ Fetches stream URLs from hd.xemtv.net PHP endpoints for VTV1-VTV10 + VTV Cần Thơ
4
+ The PHP endpoints return jwplayer config with fresh stream URLs (important for VTV6 which has expiring signatures)
5
+ """
6
+ import requests, re, time, threading
7
+
8
+ UA = {
9
+ "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",
10
+ "Accept-Language": "vi-VN,vi;q=0.9",
11
+ "Referer": "https://hd.xemtv.net/",
12
+ }
13
+
14
+ # Channel ID -> xemtv.net PHP endpoint mapping
15
+ # These PHP pages return jwplayer config with fresh stream URLs
16
+ XEMTV_PHP_ENDPOINTS = {
17
+ "vtv1": "https://hd.xemtv.net/kenh/vtv1.php",
18
+ "vtv2": "https://hd.xemtv.net/kenh/vtv2.php",
19
+ "vtv3": "https://hd.xemtv.net/kenh/vtv3.php",
20
+ "vtv4": "https://hd.xemtv.net/kenh/vtv4.php",
21
+ "vtv5": "https://hd.xemtv.net/kenh/vtv5.php",
22
+ "vtv6": "https://hd.xemtv.net/kenh/vtv6.php",
23
+ "vtv7": "https://hd.xemtv.net/kenh/vtv7.php",
24
+ "vtv8": "https://hd.xemtv.net/kenh/vtv8.php",
25
+ "vtv9": "https://hd.xemtv.net/kenh/vtv9.php",
26
+ "vtv10": "https://hd.xemtv.net/kenh/vtv10.php", # VTV Cần Thơ
27
+ }
28
+
29
+ # Channel display names
30
+ CHANNEL_NAMES = {
31
+ "vtv1": "VTV1",
32
+ "vtv2": "VTV2",
33
+ "vtv3": "VTV3",
34
+ "vtv4": "VTV4",
35
+ "vtv5": "VTV5",
36
+ "vtv6": "VTV6",
37
+ "vtv7": "VTV7",
38
+ "vtv8": "VTV8",
39
+ "vtv9": "VTV9",
40
+ "vtv10": "VTV Cần Thơ",
41
+ }
42
+
43
+ # Fallback CDN streams (fptplay) — used when xemtv scraping fails
44
+ CDN_FALLBACK = {
45
+ "vtv1": "https://live.fptplay53.net/fnxch2/vtv1hd_abr.smil/chunklist.m3u8",
46
+ "vtv2": "https://live.fptplay53.net/fnxch2/vtv2hd_abr.smil/chunklist.m3u8",
47
+ "vtv3": "https://live.fptplay53.net/fnxch2/vtv3hd_abr.smil/chunklist.m3u8",
48
+ "vtv4": "https://live.fptplay53.net/fnxch2/vtv4hd_abr.smil/chunklist.m3u8",
49
+ "vtv5": "https://live-a.fptplay53.net/live/media/VTV5HD/live_hls_avc/index.m3u8",
50
+ "vtv7": "https://live.fptplay53.net/fnxhd1/vtv7hd_vhls.smil/chunklist_b5000000.m3u8",
51
+ "vtv8": "https://live.fptplay53.net/epzhd1/vtv8hd_vhls.smil/chunklist.m3u8",
52
+ "vtv9": "https://live.fptplay53.net/fnxhd1/vtv9hd_vhls.smil/chunklist.m3u8",
53
+ "vtv10": "https://live.fptplay53.net/fnxch2/vtvcantho_abr.smil/chunklist.m3u8",
54
+ # VTV6 fallback: try canthotv as alternative
55
+ "vtv6": "https://live.canthotv.vn/live/tv/chunklist.m3u8",
56
+ }
57
+
58
+ _vtv_cache = {}
59
+ _vtv_lock = threading.Lock()
60
+ _CACHE_TTL = 180 # 3 minutes — refresh frequently for VTV6 sign URLs
61
+
62
+
63
+ def _cached(key):
64
+ with _vtv_lock:
65
+ if key in _vtv_cache and time.time() - _vtv_cache[key]['t'] < _CACHE_TTL:
66
+ return _vtv_cache[key]['d']
67
+ return None
68
+
69
+
70
+ def _set_cache(key, data):
71
+ with _vtv_lock:
72
+ _vtv_cache[key] = {'t': time.time(), 'd': data}
73
+
74
+
75
+ def extract_m3u8_from_html(html):
76
+ """Extract m3u8 URL from xemtv PHP page (jwplayer config)."""
77
+ if not html:
78
+ return None
79
+ # jwplayer config: file: 'URL'
80
+ m = re.search(r"file\s*:\s*['\"]([^'\"]*\.m3u8[^'\"]*)['\"]", html, re.IGNORECASE)
81
+ if m:
82
+ url = m.group(1).strip()
83
+ if len(url) > 20:
84
+ return url
85
+ # Generic m3u8 pattern
86
+ m = re.search(r"(https?://[^\s\"'<>\\]+\.m3u8[^\s\"'<>\\]*)", html, re.IGNORECASE)
87
+ if m:
88
+ url = m.group(1).strip()
89
+ if len(url) > 20:
90
+ return url
91
+ return None
92
+
93
+
94
+ def fetch_vtv_stream(channel_id):
95
+ """Fetch m3u8 stream URL for a VTV channel by scraping xemtv.net PHP endpoint."""
96
+ channel_id = channel_id.lower().strip()
97
+
98
+ # Normalize name
99
+ name_map = {
100
+ 'vtvct': 'vtv10', 'vtv-can-tho': 'vtv10', 'vtv can tho': 'vtv10',
101
+ 'vtv_can_tho': 'vtv10', 'cantho': 'vtv10', 'cần thơ': 'vtv10',
102
+ 'vietnam_vtv1': 'vtv1', 'vietnam_vtv2': 'vtv2', 'vietnam_vtv3': 'vtv3',
103
+ 'vietnam_vtv4': 'vtv4', 'vietnam_vtv5': 'vtv5', 'vietnam_vtv6': 'vtv6',
104
+ 'vietnam_vtv7': 'vtv7', 'vietnam_vtv8': 'vtv8', 'vietnam_vtv9': 'vtv9',
105
+ }
106
+ channel_id = name_map.get(channel_id, channel_id)
107
+
108
+ # Check cache
109
+ cached = _cached(channel_id)
110
+ if cached:
111
+ return cached
112
+
113
+ php_url = XEMTV_PHP_ENDPOINTS.get(channel_id)
114
+ if not php_url:
115
+ # Fallback
116
+ fallback = CDN_FALLBACK.get(channel_id)
117
+ if fallback:
118
+ _set_cache(channel_id, fallback)
119
+ return fallback
120
+ return None
121
+
122
+ try:
123
+ r = requests.get(php_url, headers=UA, timeout=15, allow_redirects=True)
124
+ if r.status_code == 200:
125
+ m3u8 = extract_m3u8_from_html(r.text)
126
+ if m3u8:
127
+ _set_cache(channel_id, m3u8)
128
+ return m3u8
129
+ except:
130
+ pass
131
+
132
+ # Fallback to CDN
133
+ fallback = CDN_FALLBACK.get(channel_id)
134
+ if fallback:
135
+ _set_cache(channel_id, fallback)
136
+ return fallback
137
+
138
+ return None
139
+
140
+
141
+ def get_all_vtv_streams():
142
+ """Fetch all VTV channel streams. Returns list of {id, name, stream_url}."""
143
+ channels = []
144
+ for ch_id, php_url in XEMTV_PHP_ENDPOINTS.items():
145
+ stream_url = fetch_vtv_stream(ch_id)
146
+ channels.append({
147
+ 'id': ch_id,
148
+ 'name': CHANNEL_NAMES.get(ch_id, ch_id.upper()),
149
+ 'stream_url': stream_url,
150
+ })
151
+ return channels
152
+
153
+
154
+ # Legacy compatibility
155
+ XEMTV_CHANNELS = {v: k for k, v in CHANNEL_NAMES.items()}
156
+ CDN_STREAMS = {v: k for k, v in CDN_FALLBACK.items()}