kinostream4/asil-tv
0
1import subprocess, requests, time, os, threading, re, tempfile2from datetime import datetime, timedelta, timezone3 4HLS_DIR_1 = "/tmp/hls1"5HLS_DIR_2 = "/tmp/hls2"6 7SOURCE_M3U8_1 = os.environ.get("SOURCE1_M3U8_URL", "")8SOURCE_M3U8_2 = os.environ.get("SOURCE2_M3U8_URL", "")9 10current_titles = {11 "stream1": {"title": "Yuklanmoqda...", "index": 0, "total": 0},12 "stream2": {"title": "Yuklanmoqda...", "index": 0, "total": 0}13}14 15schedule_lock_1 = threading.Lock()16schedule_lock_2 = threading.Lock()17program_schedule_1 = []18program_schedule_2 = []19 20DEFAULT_DURATION = 90 * 6021_real_durations_1 = {}22_real_durations_2 = {}23_dur_lock = threading.Lock()24 25 26def build_schedule_fast(items, stream_num, current_index, current_start_time):27 global program_schedule_1, program_schedule_228 now = datetime.now(timezone.utc)29 30 idx = current_index31 start_of_current = current_start_time if current_start_time else now32 33 durations = []34 for item in items:35 with _dur_lock:36 dur = _real_durations_1.get(item["url"], DEFAULT_DURATION) if stream_num == 1 else _real_durations_2.get(item["url"], DEFAULT_DURATION)37 durations.append(dur)38 39 cursor = start_of_current40 starts = [None] * len(items)41 starts[idx] = cursor42 43 for i in range(idx, len(items)):44 starts[i] = cursor45 cursor += timedelta(seconds=durations[i])46 47 cursor = start_of_current48 for i in range(idx - 1, -1, -1):49 cursor -= timedelta(seconds=durations[i])50 starts[i] = cursor51 52 sched = []53 for i, item in enumerate(items):54 start = starts[i]55 stop = start + timedelta(seconds=durations[i])56 sched.append({"title": item["title"], "start": start, "stop": stop, "logo": item.get("logo", ""), "desc": item.get("desc", ""), "trailer": item.get("trailer", "")})57 58 if stream_num == 1:59 with schedule_lock_1:60 program_schedule_1 = sched61 else:62 with schedule_lock_2:63 program_schedule_2 = sched64 65 66def get_schedule_1():67 with schedule_lock_1:68 return list(program_schedule_1)69 70def get_schedule_2():71 with schedule_lock_2:72 return list(program_schedule_2)73 74 75def parse_playlist(url):76 try:77 resp = requests.get(url, timeout=10)78 resp.raise_for_status()79 items, title, pending_dur, pending_logo, pending_desc, pending_trailer = [], "", None, None, "", ""80 for line in resp.text.splitlines():81 line = line.strip()82 if line.startswith("#EXTINF"):83 m = re.match(r'#EXTINF:([\d.-]+)', line)84 if m:85 try:86 d = float(m.group(1))87 if d > 0:88 pending_dur = d89 except Exception:90 pass91 logo_m = re.search(r'tvg-logo="([^"]*)"', line)92 pending_logo = logo_m.group(1) if logo_m else None93 desc_m = re.search(r'tvg-description="([^"]*)"', line)94 pending_desc = desc_m.group(1) if desc_m else ""95 trailer_m = re.search(r'tvg-trailer="([^"]*)"', line)96 pending_trailer = trailer_m.group(1) if trailer_m else ""97 clean = re.sub(r'#EXTINF:[^\s,]+', '', line)98 clean = re.sub(r'\w[\w-]*="[^"]*"', '', clean)99 if ',' in clean:100 title = clean.split(',', 1)[1].strip()101 else:102 title = clean.strip()103 elif line and not line.startswith("#"):104 item = {"url": line, "title": title or line.split("/")[-1], "logo": pending_logo or "", "desc": pending_desc or "", "trailer": pending_trailer or ""}105 if pending_dur:106 with _dur_lock:107 _real_durations_1[line] = pending_dur108 pending_dur = None109 items.append(item)110 title = ""111 pending_logo = None112 pending_desc = ""113 pending_trailer = ""114 return items115 except Exception as e:116 print(f"❌ Playlist xato ({url}): {e}")117 return []118 119 120def run_worker(stream_url, hls_dir, stream_name, stream_num):121 global current_titles122 os.makedirs(hls_dir, exist_ok=True)123 124 if not stream_url:125 print(f"❌ {stream_name} uchun URL topilmadi!")126 while True:127 time.sleep(60)128 129 seg_counter = 0130 131 def _next_seg_start():132 nonlocal seg_counter133 try:134 segs = [f for f in os.listdir(hls_dir) if f.startswith("seg") and f.endswith(".ts")]135 if segs:136 nums = [int(re.search(r'\d+', s).group()) for s in segs if re.search(r'\d+', s)]137 if nums:138 seg_counter = max(nums) + 1139 except Exception:140 pass141 return seg_counter142 143 def _update_seg_counter():144 nonlocal seg_counter145 try:146 segs = [f for f in os.listdir(hls_dir) if f.startswith("seg") and f.endswith(".ts")]147 if segs:148 nums = [int(re.search(r'\d+', s).group()) for s in segs if re.search(r'\d+', s)]149 if nums:150 seg_counter = max(nums) + 1151 except Exception:152 pass153 154 while True:155 try:156 items = parse_playlist(stream_url)157 if not items:158 print(f"⚠️ {stream_name} playlist bo'sh, 30s kutilmoqda...")159 time.sleep(30)160 continue161 162 for index, item in enumerate(items, 1):163 current_index = index - 1164 current_start_time = datetime.now(timezone.utc)165 166 current_titles[stream_name] = {"title": item["title"], "index": index, "total": len(items)}167 print(f"🎬 [{stream_name}] [{index}/{len(items)}] Efir: {item['title']}")168 169 build_schedule_fast(items, stream_num, current_index, current_start_time)170 171 start_num = _next_seg_start()172 cmd = [173 "ffmpeg", "-y",174 "-re",175 "-i", item["url"],176 "-c:v", "copy",177 "-c:a", "aac", "-b:a", "128k",178 "-f", "hls",179 "-hls_time", "4",180 "-hls_list_size", "10",181 "-hls_flags", "append_list+delete_segments",182 "-hls_delete_threshold", "3",183 "-start_number", str(start_num),184 "-hls_segment_filename", f"{hls_dir}/seg%d.ts",185 f"{hls_dir}/stream.m3u8"186 ]187 188 with _dur_lock:189 known_dur = _real_durations_1.get(item["url"], DEFAULT_DURATION) if stream_num == 1 else _real_durations_2.get(item["url"], DEFAULT_DURATION)190 max_allowed = known_dur * 1.5 + 300191 192 t_start = time.time()193 proc = None194 errfile = tempfile.NamedTemporaryFile(mode="w+", dir="/tmp", prefix=f"ffmpeg_err_{stream_name}_", suffix=".log", delete=False)195 try:196 proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=errfile)197 while True:198 ret = proc.poll()199 if ret is not None:200 break201 elapsed = time.time() - t_start202 if elapsed > max_allowed:203 proc.kill()204 proc.wait(timeout=10)205 break206 time.sleep(5)207 except Exception as e:208 if proc is not None:209 try:210 proc.kill()211 proc.wait(timeout=10)212 except Exception:213 pass214 finally:215 try:216 errfile.close()217 os.remove(errfile.name)218 except Exception:219 pass220 221 _update_seg_counter()222 real_dur = time.time() - t_start223 if real_dur > 10:224 with _dur_lock:225 if stream_num == 1:226 _real_durations_1[item["url"]] = real_dur227 else:228 _real_durations_2[item["url"]] = real_dur229 230 time.sleep(1)231 232 print(f"🔄 [{stream_name}] Barcha kinolar tugadi, qayta boshlanmoqda...")233 except Exception as e:234 print(f"❌ [{stream_name}] Sikl xatosi: {e}")235 time.sleep(10)236 237 238def stream_all():239 t1 = threading.Thread(target=run_worker, args=(SOURCE_M3U8_1, HLS_DIR_1, "stream1", 1), daemon=True)240 t2 = threading.Thread(target=run_worker, args=(SOURCE_M3U8_2, HLS_DIR_2, "stream2", 2), daemon=True)241 242 t1.start()243 t2.start()244 245 t1.join()246 t2.join()