import re import requests from collections import OrderedDict from datetime import datetime from concurrent.futures import ThreadPoolExecutor, as_completed tv_urls = [ "https://qu.ax/vUBde.txt", "http://bxtv.3a.ink/live.m3u", "http://live.nctv.top/x.txt", "https://aktv.space/live.m3u", "http://tot.totalh.net/tttt.txt", "https://m3u.ibert.me/fmml_ipv6.m3u", "https://json.doube.eu.org/XingHuo.txt", "https://raw.githubusercontent.com/zwc456baby/iptv_alive/master/live.txt", "https://raw.githubusercontent.com/zwc456baby/iptv_alive/master/live.m3u", "https://raw.githubusercontent.com/BurningC4/Chinese-IPTV/master/TV-IPV4.m3u", "https://raw.githubusercontent.com/YueChan/Live/refs/heads/main/APTV.m3u", "https://raw.githubusercontent.com/Wirili/IPTV/refs/heads/main/live.m3u", "https://raw.githubusercontent.com/wwb521/live/refs/heads/main/tv.m3u", "https://raw.githubusercontent.com/Kimentanm/aptv/master/m3u/iptv.m3u", "https://raw.githubusercontent.com/Ftindy/IPTV-URL/main/IPV6.m3u", "https://live.zbds.top/tv/iptv4.m3u", "https://live.zbds.top/tv/iptv6.m3u", "https://raw.githubusercontent.com/wind005/TVlive/refs/heads/main/m3u/%E6%B9%96%E5%8D%97%E7%A7%BB%E5%8A%A8.m3u", "https://raw.githubusercontent.com/hanhan8127/TVBox/refs/heads/main/live.txt", "https://raw.githubusercontent.com/hujingguang/ChinaIPTV/main/cnTV_AutoUpdate.m3u8", "https://raw.githubusercontent.com/suxuang/myIPTV/refs/heads/main/ipv4.m3u", "https://raw.githubusercontent.com/suxuang/myIPTV/refs/heads/main/ipv6.m3u", "https://raw.githubusercontent.com/Guovin/iptv-api/gd/output/ipv4/result.m3u", "https://raw.githubusercontent.com/Guovin/iptv-api/gd/output/ipv6/result.m3u", ] def parse_template(template_file): template_channels = OrderedDict() current_category = None with open(template_file, "r", encoding="utf-8") as f: for line in f: line = line.strip() if line and not line.startswith("#"): if "#genre#" in line: current_category = line.split(",")[0].strip() template_channels[current_category] = [] elif current_category: channel_name = line.split(",")[0].strip() template_channels[current_category].append(channel_name) return template_channels def fetch_channels(url): channels = OrderedDict() try: response = requests.get(url, timeout=120) response.raise_for_status() response.encoding = "utf-8" lines = response.text.split("\n") is_m3u = any("#EXTINF" in line for line in lines[:5]) current_category = None if is_m3u: channel_name = "" for line in lines: line = line.strip() if line.startswith("#EXTINF"): match = re.search(r'group-title="(.*?)",(.*)', line) if match: current_category = match.group(1).strip() channel_name = match.group(2).strip() if current_category not in channels: channels[current_category] = [] elif line and not line.startswith("#"): if current_category and channel_name: channels[current_category].append((channel_name, line)) channel_name = "" else: for line in lines: line = line.strip() if "#genre#" in line: current_category = line.split(",")[0].strip() channels[current_category] = [] elif current_category and line: parts = line.split(",", 1) if len(parts) == 2: name, url = parts channels[current_category].append((name.strip(), url.strip())) return channels except Exception as e: print(f"Error fetching {url}: {str(e)}") return OrderedDict() def match_channels(template_channels, all_channels): matched = OrderedDict() for category, names in template_channels.items(): matched[category] = OrderedDict() for name in names: primary_name = name.split("|")[0] for src_category, channels in all_channels.items(): for chan_name, chan_url in channels: if chan_name in name.split("|"): matched[category].setdefault(primary_name, []).append(chan_url) return matched def is_ipv6(url): return re.match(r"^http:\/\/\[[0-9a-fA-F:]+\]", url) is not None def generate_outputs(channels, template_channels): written_urls = set() current_date = datetime.now().strftime("%Y-%m-%d") with open("lib/iptv.m3u", "w", encoding="utf-8") as m3u, \ open("lib/iptv.txt", "w", encoding="utf-8") as txt: # Write channel list total_count = 0 for category in template_channels: if category not in channels: continue txt.write(f"\n{category},#genre#\n") for name in template_channels[category]: primary_name = name.split("|")[0] urls = channels[category].get(primary_name, []) # URL filtering and sorting filtered = [ url for url in urls if url and url not in written_urls # and not any(b in url for b in config.url_blacklist) ] if not filtered: continue # IP version priority sorting # filtered.sort(key=lambda x: is_ipv6(x) != (config.ip_version_priority == "ipv6")) # Format URLs total = len(filtered) for idx, url in enumerate(filtered, 1): base_url = url.split("$")[0] suffix = "$LR•" + ("IPV6" if is_ipv6(url) else "IPV4") if total > 1: suffix += f"•{total}『线路{idx}』" final_url = f"{base_url}{suffix}" m3u.write(f'#EXTINF:-1 tvg-id="{idx}" tvg-name="{primary_name}" ' #f'tvg-logo="https://example.com/logo/{primary_name}.png" ' f'group-title="{category}",{primary_name}\n') m3u.write(f"{final_url}\n") txt.write(f"{primary_name},{final_url}\n") written_urls.add(url) total_count += 1 print(f"频道处理完成,总计有效频道数:{total_count}") def filter_sources(template_file): template = parse_template(template_file) all_channels = OrderedDict() with ThreadPoolExecutor(max_workers=5) as executor: futures = {executor.submit(fetch_channels, url): url for url in tv_urls} for future in as_completed(futures): url = futures[future] try: result = future.result() for cat, chans in result.items(): all_channels.setdefault(cat, []).extend(chans) except Exception as e: print(f"处理源 {url} 时出错: {str(e)}") return match_channels(template, all_channels), template if __name__ == "__main__": matched_channels, template = filter_sources("py/config/iptv.txt") generate_outputs(matched_channels, template)