diff --git a/scripts/generate_playlist.py b/scripts/generate_playlist.py index 8fe8a82..4ec99e8 100644 --- a/scripts/generate_playlist.py +++ b/scripts/generate_playlist.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 """ -IPTV Enhanced Country Detection - Simplified No-Hang Version -Uses your existing 3-point analysis but removes network calls that cause hanging +IPTV Enhanced Country Detection - Updated Version +Uses 3-point analysis: Channel Name + EPG ID + Logo URL +Then filters to keep only legitimate countries """ import os import shutil -import re from datetime import datetime from pathlib import Path @@ -15,53 +15,114 @@ script_dir = Path(__file__).parent root_dir = script_dir.parent os.chdir(root_dir) -def is_streaming_platform(all_text): - """Check if channel is from streaming platform.""" - platforms = [ - "plex.tv", "pluto.tv", "jmp2.uk/plu-", "sam-", "samsung", - "tubi", "xumo", "roku", "youtube", "daddylive", "drew247tv", - "moveonjoy", "247tv", "livetv" - ] - return any(platform in all_text for platform in platforms) - -def detect_traditional_broadcaster_country(channel_name, epg_id="", logo_url="", stream_url=""): - """Enhanced 3-point country detection for traditional broadcasters.""" +def detect_country_from_channel_content(channel_name, epg_id="", logo_url="", stream_url=""): + """ + Enhanced country detection using 3-point analysis + Priority: EPG ID > Logo URL > Channel Name > Stream URL + """ + + # Combine all text for analysis all_text = f"{channel_name.lower()} {epg_id.lower()} {logo_url.lower()} {stream_url.lower()}" - channel_lower = channel_name.lower() - # Skip streaming platforms entirely - if is_streaming_platform(all_text): - return "Uncategorized" + # STEP 1: Check for streaming services first (these go to Uncategorized) + streaming_services = [ + "plex", "pluto", "tubi", "samsung", "xumo", "stirr", "crackle", "imdb tv", + "daddylive", "drew247", "aixmedia", "moveonjoy", "drewlive24", "udptv", + "a1xs.vip", "zekonew", "forcedtoplay", "cdn1host", "tvpass.org", + "jmp2.uk/plu-", "provider-static.plex.tv", "images.pluto.tv" + ] - # PRIORITY 1: EPG ID detection (most reliable) - epg_countries = { - ".ca": "๐Ÿ‡จ๐Ÿ‡ฆ Canada", ".us": "๐Ÿ‡บ๐Ÿ‡ธ United States", ".uk": "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", - ".ph": "๐Ÿ‡ต๐Ÿ‡ญ Philippines", ".au": "๐Ÿ‡ฆ๐Ÿ‡บ Australia", ".jp": "๐Ÿ‡ฏ๐Ÿ‡ต Japan", - ".my": "๐Ÿ‡ฒ๐Ÿ‡พ Malaysia", ".de": "๐Ÿ‡ฉ๐Ÿ‡ช Germany", ".fr": "๐Ÿ‡ซ๐Ÿ‡ท France", - ".es": "๐Ÿ‡ช๐Ÿ‡ธ Spain", ".it": "๐Ÿ‡ฎ๐Ÿ‡น Italy", ".br": "๐Ÿ‡ง๐Ÿ‡ท Brazil", - ".mx": "๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico", ".ar": "๐Ÿ‡ฆ๐Ÿ‡ท Argentina" + for service in streaming_services: + if service in all_text: + return "Uncategorized" + + # STEP 2: EPG ID detection (most reliable) - Enhanced + epg_patterns = { + ".ca": "๐Ÿ‡จ๐Ÿ‡ฆ Canada", + ".us": "๐Ÿ‡บ๐Ÿ‡ธ United States", + ".uk": "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", + ".ph": "๐Ÿ‡ต๐Ÿ‡ญ Philippines", + ".au": "๐Ÿ‡ฆ๐Ÿ‡บ Australia", + ".jp": "๐Ÿ‡ฏ๐Ÿ‡ต Japan", + ".my": "๐Ÿ‡ฒ๐Ÿ‡พ Malaysia", + ".de": "๐Ÿ‡ฉ๐Ÿ‡ช Germany", + ".fr": "๐Ÿ‡ซ๐Ÿ‡ท France", + ".es": "๐Ÿ‡ช๐Ÿ‡ธ Spain", + ".it": "๐Ÿ‡ฎ๐Ÿ‡น Italy", + ".br": "๐Ÿ‡ง๐Ÿ‡ท Brazil", + ".nl": "๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands" } - for domain, country in epg_countries.items(): + for domain, country in epg_patterns.items(): if domain in epg_id.lower(): return country - # PRIORITY 2: Specific channel fixes from your original code + # Enhanced Canadian EPG detection + canadian_epg_patterns = [ + "cbc.", "ctv.", "global.", "tsn.", "sportsnet.", "citytv.", "aptn.", + ".ab.ca", ".bc.ca", ".mb.ca", ".nb.ca", ".nl.ca", ".ns.ca", ".nt.ca", + ".nu.ca", ".on.ca", ".pe.ca", ".qc.ca", ".sk.ca", ".yt.ca", + "cfcn", "cky", "ctfo", "cjoh", "ckws" + ] - # Canadian sports channels (TSN series) - if any(x in channel_lower for x in ["tsn 1", "tsn 2", "tsn 3", "tsn 4", "tsn 5", "tsn1", "tsn2", "tsn3", "tsn4", "tsn5"]): - return "๐Ÿ‡จ๐Ÿ‡ฆ Canada" + for pattern in canadian_epg_patterns: + if pattern in epg_id.lower() or pattern in all_text: + return "๐Ÿ‡จ๐Ÿ‡ฆ Canada" - # CBC News Toronto (Canadian) - if "cbc news toronto" in channel_lower: - return "๐Ÿ‡จ๐Ÿ‡ฆ Canada" + # STEP 3: Enhanced specific channel fixes + channel_lower = channel_name.lower() + + # Enhanced Canadian channels detection + canadian_indicators = [ + # TSN variations + "tsn 1", "tsn 2", "tsn 3", "tsn 4", "tsn 5", "tsn1", "tsn2", "tsn3", "tsn4", "tsn5", + # CBC variations + "cbc news", "cbc toronto", "cbc calgary", "cbc vancouver", "cbc winnipeg", "cbc montreal", + # CTV variations + "ctv calgary", "ctv vancouver", "ctv toronto", "ctv winnipeg", "ctv ottawa", "ctv montreal", + "ctv atlantic", "ctv edmonton", "ctv saskatoon", "ctv regina", "ctv kitchener", + # Regional station calls + "cfcn", "cky", "ctfo", "cjoh", "ckws", "cfrn", "cfqc", "ckck", "chch", + # Other Canadian broadcasters + "sportsnet", "global tv", "citytv", "aptn", "omni", "tvo", "tรฉlรฉ-quรฉbec" + ] + + for indicator in canadian_indicators: + if indicator in channel_lower: + return "๐Ÿ‡จ๐Ÿ‡ฆ Canada" + + # Enhanced BBC handling (distinguish US vs UK) + if "bbc" in channel_lower: + # BBC America is US + if "bbc america" in channel_lower: + return "๐Ÿ‡บ๐Ÿ‡ธ United States" + # Most other BBC channels are UK + elif any(x in channel_lower for x in ["bbc one", "bbc two", "bbc three", "bbc four", + "bbc news", "bbc iplayer", "bbc scotland", "bbc wales", + "bbc comedy", "bbc drama", "bbc earth"]): + # Check if it's specifically UK version + if not any(x in all_text for x in ["america", ".us", "usa"]): + return "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom" # US channels that were misclassified if any(x in channel_lower for x in ["tv land", "tvland", "we tv", "wetv", "all weddings we tv", "cheaters", "cheers", "christmas 365"]): return "๐Ÿ‡บ๐Ÿ‡ธ United States" - # UK shows/channels - if "come dine with me" in channel_lower: + # Enhanced US network detection + us_networks = [ + "cbs", "nbc", "abc", "fox", "cnn", "espn", "hbo", "showtime", "starz", "cinemax", + "mtv", "vh1", "comedy central", "cartoon network", "nickelodeon", "disney channel", + "discovery", "history", "tlc", "hgtv", "food network", "travel channel", + "lifetime", "hallmark", "e!", "bravo", "oxygen", "syfy", "usa network", + "tnt", "tbs", "fx", "fxx", "amc", "ifc", "tcm", "turner classic" + ] + + for network in us_networks: + if network in channel_lower and not any(x in all_text for x in ["canada", ".ca", "uk", ".uk"]): + return "๐Ÿ‡บ๐Ÿ‡ธ United States" + + # UK channels (but not BBC America) + if "come dine with me" in channel_lower or "itv" in channel_lower: return "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom" # Philippines news channels @@ -72,47 +133,121 @@ def detect_traditional_broadcaster_country(channel_name, epg_id="", logo_url="", if "animax" in channel_lower: return "๐Ÿ‡ฏ๐Ÿ‡ต Japan" - # PRIORITY 3: Logo URL detection (your 3-point analysis) + # STEP 4: Logo URL analysis logo_patterns = { - "/canada/": "๐Ÿ‡จ๐Ÿ‡ฆ Canada", "/ca/": "๐Ÿ‡จ๐Ÿ‡ฆ Canada", "canada": "๐Ÿ‡จ๐Ÿ‡ฆ Canada", - "/usa/": "๐Ÿ‡บ๐Ÿ‡ธ United States", "/us/": "๐Ÿ‡บ๐Ÿ‡ธ United States", "america": "๐Ÿ‡บ๐Ÿ‡ธ United States", - "/uk/": "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", "/britain/": "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", "british": "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", - "/germany/": "๐Ÿ‡ฉ๐Ÿ‡ช Germany", "/de/": "๐Ÿ‡ฉ๐Ÿ‡ช Germany", - "/france/": "๐Ÿ‡ซ๐Ÿ‡ท France", "/fr/": "๐Ÿ‡ซ๐Ÿ‡ท France", - "/spain/": "๐Ÿ‡ช๐Ÿ‡ธ Spain", "/es/": "๐Ÿ‡ช๐Ÿ‡ธ Spain", - "/italy/": "๐Ÿ‡ฎ๐Ÿ‡น Italy", "/it/": "๐Ÿ‡ฎ๐Ÿ‡น Italy" + "๐Ÿ‡จ๐Ÿ‡ฆ Canada": ["/canada/", "/ca/", "canada.", "canadian"], + "๐Ÿ‡บ๐Ÿ‡ธ United States": ["/usa/", "/us/", "united-states", "american"], + "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom": ["/uk/", "/united-kingdom/", "british", "england"], + "๐Ÿ‡ฉ๐Ÿ‡ช Germany": ["/germany/", "/de/", "german", "deutschland"], + "๐Ÿ‡ซ๐Ÿ‡ท France": ["/france/", "/fr/", "french", "franรงais"], + "๐Ÿ‡ฎ๐Ÿ‡น Italy": ["/italy/", "/it/", "italian", "italiano"], + "๐Ÿ‡ช๐Ÿ‡ธ Spain": ["/spain/", "/es/", "spanish", "espaรฑol"], + "๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands": ["/netherlands/", "/nl/", "dutch", "nederland"], + "๐Ÿ‡ฆ๐Ÿ‡บ Australia": ["/australia/", "/au/", "australian", "aussie"], + "๐Ÿ‡ฏ๐Ÿ‡ต Japan": ["/japan/", "/jp/", "japanese", "ๆ—ฅๆœฌ"], + "๐Ÿ‡ฐ๐Ÿ‡ท South Korea": ["/korea/", "/kr/", "korean", "ํ•œ๊ตญ"], + "๐Ÿ‡ฎ๐Ÿ‡ณ India": ["/india/", "/in/", "indian", "เคญเคพเคฐเคค"], + "๐Ÿ‡ง๐Ÿ‡ท Brazil": ["/brazil/", "/br/", "brazilian", "brasil"], + "๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico": ["/mexico/", "/mx/", "mexican", "mรฉxico"], + "๐Ÿ‡ฆ๐Ÿ‡ท Argentina": ["/argentina/", "/ar/", "argentinian", "argentina"], + "๐Ÿ‡ต๐Ÿ‡ญ Philippines": ["/philippines/", "/ph/", "filipino", "pilipinas"] } - for pattern, country in logo_patterns.items(): - if pattern in logo_url.lower(): - return country + for country, patterns in logo_patterns.items(): + for pattern in patterns: + if pattern in logo_url.lower(): + return country - # PRIORITY 4: Essential broadcaster patterns - broadcasters = { - "๐Ÿ‡จ๐Ÿ‡ฆ Canada": ["tsn", "cbc", "ctv", "global", "sportsnet", "w network", "city tv", "aptn"], - "๐Ÿ‡บ๐Ÿ‡ธ United States": ["cbs", "nbc", "abc", "fox", "cnn", "espn", "discovery", "hgtv", "mtv", "c-span", "msnbc", "comedy central"], - "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom": ["bbc", "itv", "sky", "channel 4", "channel 5", "dave", "e4", "film4"], - "๐Ÿ‡ต๐Ÿ‡ญ Philippines": ["abs-cbn", "gma", "anc", "tv5", "pbo", "net 25"], - "๐Ÿ‡ฆ๐Ÿ‡บ Australia": ["abc australia", "nine network", "seven network", "ten network", "sbs"], - "๐Ÿ‡ฏ๐Ÿ‡ต Japan": ["nhk", "fuji tv", "animax", "tbs", "tv asahi"], - "๐Ÿ‡ฒ๐Ÿ‡พ Malaysia": ["tv1", "tv2", "astro", "rtm", "8tv"], - "๐Ÿ‡ฉ๐Ÿ‡ช Germany": ["ard", "zdf", "rtl", "sat.1", "pro7", "vox"], - "๐Ÿ‡ซ๐Ÿ‡ท France": ["tf1", "france 2", "canal+", "m6", "arte"], - "๐Ÿ‡ช๐Ÿ‡ธ Spain": ["antena 3", "telecinco", "tve", "la sexta"], - "๐Ÿ‡ฎ๐Ÿ‡น Italy": ["rai", "mediaset", "canale 5", "la7"], - "๐Ÿ‡ง๐Ÿ‡ท Brazil": ["globo", "sbt", "record", "band"], - "๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico": ["televisa", "tv azteca", "canal 5"], - "๐Ÿ‡ท๐Ÿ‡บ Russia": ["ะฟะตั€ะฒั‹ะน", "ั€ะพััะธั", "ะฝั‚ะฒ", "ั‚ะฝั‚"] + # STEP 5: Enhanced broadcaster patterns + broadcaster_patterns = { + "๐Ÿ‡จ๐Ÿ‡ฆ Canada": [ + "cbc", "tsn", "ctv", "global", "sportsnet", "citytv", "aptn", "teletoon", "ytv", + "discovery canada", "history canada", "slice", "w network", "oln", "hgtv canada", + "food network canada", "showcase", "crave", "super channel", "hollywood suite" + ], + "๐Ÿ‡บ๐Ÿ‡ธ United States": [ + "cbs", "nbc", "abc", "fox", "cnn", "espn", "amc", "mtv", "comedy central", + "discovery usa", "history usa", "tlc usa", "hgtv usa", "food network usa", "paramount", + "nickelodeon usa", "cartoon network usa", "disney usa", "lifetime", "e!", "bravo usa" + ], + "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom": [ + "bbc", "itv", "channel 4", "channel 5", "sky", "dave", "really", "yesterday", + "discovery uk", "history uk", "tlc uk", "living", "alibi", "gold", "drama" + ], + "๐Ÿ‡ฉ๐Ÿ‡ช Germany": [ + "ard", "zdf", "rtl", "pro7", "sat.1", "vox", "kabel eins", "super rtl", "rtl2", + "discovery germany", "history germany", "tlc germany", "dmax", "sixx", "tele 5" + ], + "๐Ÿ‡ซ๐Ÿ‡ท France": [ + "tf1", "france 2", "france 3", "france 5", "m6", "canal+", "arte", "w9", "tmc", + "discovery france", "history france", "tlc france", "planete+", "ushuaia tv" + ], + "๐Ÿ‡ฎ๐Ÿ‡น Italy": [ + "rai", "canale 5", "italia 1", "rete 4", "la7", "tv8", "nove", "20 mediaset", + "discovery italia", "history italia", "dmax italia", "real time", "giallo" + ], + "๐Ÿ‡ช๐Ÿ‡ธ Spain": [ + "tve", "la 1", "la 2", "antena 3", "cuatro", "telecinco", "la sexta", "nova", + "discovery spain", "history spain", "dmax spain", "mega", "neox", "clan" + ], + "๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands": [ + "npo", "rtl 4", "rtl 5", "rtl 7", "sbs6", "veronica", "net5", "rtl z", + "discovery netherlands", "history netherlands", "tlc netherlands" + ], + "๐Ÿ‡ฆ๐Ÿ‡บ Australia": [ + "abc australia", "nine network", "seven network", "ten", "foxtel", + "discovery australia", "history australia", "lifestyle" + ], + "๐Ÿ‡ฏ๐Ÿ‡ต Japan": [ + "nhk", "fuji tv", "tbs", "tv asahi", "tv tokyo", "nippon tv", "animax" + ], + "๐Ÿ‡ฐ๐Ÿ‡ท South Korea": [ + "kbs", "mbc", "sbs", "jtbc", "tvn", "ocn" + ], + "๐Ÿ‡ฎ๐Ÿ‡ณ India": [ + "zee", "star plus", "colors", "sony tv", "& tv", "discovery india" + ], + "๐Ÿ‡ง๐Ÿ‡ท Brazil": [ + "globo", "sbt", "record", "band", "discovery brasil" + ], + "๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico": [ + "televisa", "tv azteca", "once tv", "discovery mexico" + ], + "๐Ÿ‡ฆ๐Ÿ‡ท Argentina": [ + "telefe", "canal 13", "america tv", "discovery argentina" + ], + "๐Ÿ‡ต๐Ÿ‡ญ Philippines": [ + "abs-cbn", "gma", "anc", "tv5", "pba rush" + ] } - for country, keywords in broadcasters.items(): - if any(keyword in all_text for keyword in keywords): - return country + for country, keywords in broadcaster_patterns.items(): + for keyword in keywords: + if keyword in all_text: + return country return "Uncategorized" + +def is_valid_country_group(group_name): + """Check if group name is a valid country (not a streaming service)""" + valid_countries = [ + "๐Ÿ‡บ๐Ÿ‡ธ United States", "๐Ÿ‡จ๐Ÿ‡ฆ Canada", "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", "๐Ÿ‡ฉ๐Ÿ‡ช Germany", + "๐Ÿ‡ซ๐Ÿ‡ท France", "๐Ÿ‡ฎ๐Ÿ‡น Italy", "๐Ÿ‡ช๐Ÿ‡ธ Spain", "๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands", "๐Ÿ‡ง๐Ÿ‡ช Belgium", + "๐Ÿ‡ฆ๐Ÿ‡น Austria", "๐Ÿ‡จ๐Ÿ‡ญ Switzerland", "๐Ÿ‡ธ๐Ÿ‡ช Sweden", "๐Ÿ‡ณ๐Ÿ‡ด Norway", "๐Ÿ‡ฉ๐Ÿ‡ฐ Denmark", + "๐Ÿ‡ซ๐Ÿ‡ฎ Finland", "๐Ÿ‡ต๐Ÿ‡ฑ Poland", "๐Ÿ‡จ๐Ÿ‡ฟ Czech Republic", "๐Ÿ‡ญ๐Ÿ‡บ Hungary", "๐Ÿ‡ต๐Ÿ‡น Portugal", + "๐Ÿ‡ฌ๐Ÿ‡ท Greece", "๐Ÿ‡ท๐Ÿ‡ด Romania", "๐Ÿ‡ง๐Ÿ‡ฌ Bulgaria", "๐Ÿ‡ญ๐Ÿ‡ท Croatia", "๐Ÿ‡ท๐Ÿ‡ธ Serbia", + "๐Ÿ‡ฆ๐Ÿ‡บ Australia", "๐Ÿ‡ฏ๐Ÿ‡ต Japan", "๐Ÿ‡ฐ๐Ÿ‡ท South Korea", "๐Ÿ‡ฎ๐Ÿ‡ณ India", "๐Ÿ‡จ๐Ÿ‡ณ China", + "๐Ÿ‡ง๐Ÿ‡ท Brazil", "๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico", "๐Ÿ‡ฆ๐Ÿ‡ท Argentina", "๐Ÿ‡จ๐Ÿ‡ฑ Chile", "๐Ÿ‡จ๐Ÿ‡ด Colombia", + "๐Ÿ‡ท๐Ÿ‡บ Russia", "๐Ÿ‡น๐Ÿ‡ท Turkey", "๐Ÿ‡ธ๐Ÿ‡ฆ Saudi Arabia", "๐Ÿ‡ฆ๐Ÿ‡ช UAE", "๐Ÿ‡ช๐Ÿ‡ฌ Egypt", + "๐Ÿ‡ฟ๐Ÿ‡ฆ South Africa", "๐Ÿ‡ณ๐Ÿ‡ฌ Nigeria", "๐Ÿ‡ฐ๐Ÿ‡ช Kenya", "๐Ÿ‡ฎ๐Ÿ‡ฑ Israel", "๐Ÿ‡น๐Ÿ‡ญ Thailand", + "๐Ÿ‡ป๐Ÿ‡ณ Vietnam", "๐Ÿ‡ต๐Ÿ‡ญ Philippines", "๐Ÿ‡ฎ๐Ÿ‡ฉ Indonesia", "๐Ÿ‡ฒ๐Ÿ‡พ Malaysia", "๐Ÿ‡ธ๐Ÿ‡ฌ Singapore" + ] + return group_name in valid_countries + + def load_channels(): - """Load channels from channels.txt.""" + """Load channels from channels.txt with enhanced parsing for malformed entries.""" if not os.path.exists('channels.txt'): print("โŒ No channels.txt found") return [] @@ -130,24 +265,44 @@ def load_channels(): for line in block.strip().split('\n'): if '=' in line: key, value = line.split('=', 1) - channel_data[key.strip()] = value.strip() + key = key.strip() + value = value.strip() + + # Clean up malformed values (fix the quote issues we saw) + if key == "Stream name" and value.startswith('"') and value.count('"') > 2: + # Handle malformed entries like: ".AB.ca",.AB.ca" tvg-logo=... + # Extract just the actual channel name + parts = value.split(',') + if len(parts) > 1: + value = parts[-1].strip().strip('"') + + channel_data[key] = value - if channel_data.get('Stream name'): + # Only add channels with valid stream names + if channel_data.get('Stream name') and len(channel_data.get('Stream name', '')) > 1: channels.append(channel_data) - print(f"โœ… Loaded {len(channels)} channels") + print(f"โœ… Loaded {len(channels)} channels (with enhanced parsing)") return channels except Exception as e: print(f"โŒ Error loading channels: {e}") return [] + def reorganize_channels(channels): - """Reorganize channels by country (traditional only).""" - print("๐Ÿ“บ Organizing traditional broadcasters by country...") + """Enhanced reorganization with 3-point analysis.""" + print("๐Ÿ” Enhanced Country Detection with 3-Point Analysis") + print("๐Ÿ“Š Analyzing: Channel Name + EPG ID + Logo URL") + print("-" * 60) changes = 0 - stats = {} + stats = { + 'country_detected': 0, + 'sent_to_uncategorized': 0, + 'kept_existing_country': 0 + } + country_counts = {} for channel in channels: old_group = channel.get('Group', 'Uncategorized') @@ -156,24 +311,46 @@ def reorganize_channels(channels): logo = channel.get('Logo', '') stream_url = channel.get('Stream URL', '') - new_group = detect_traditional_broadcaster_country(stream_name, epg_id, logo, stream_url) + # Detect country using enhanced 3-point analysis + detected_country = detect_country_from_channel_content(stream_name, epg_id, logo, stream_url) - if old_group != new_group: - if new_group == "Uncategorized": + # Decide final group + if is_valid_country_group(old_group) and detected_country != "Uncategorized": + # Keep existing valid country + final_group = old_group + stats['kept_existing_country'] += 1 + elif detected_country != "Uncategorized": + # Use detected country + final_group = detected_country + stats['country_detected'] += 1 + if old_group != detected_country: + print(f"๐Ÿ” Fixed: '{stream_name}' {old_group} โ†’ {detected_country}") + changes += 1 + else: + # Send to Uncategorized + final_group = "Uncategorized" + stats['sent_to_uncategorized'] += 1 + if old_group != "Uncategorized": print(f"๐Ÿ“ฑ Platform: '{stream_name}' โ†’ Uncategorized") - else: - print(f"๐Ÿ“บ Fixed: '{stream_name}' {old_group} โ†’ {new_group}") - channel['Group'] = new_group - changes += 1 + changes += 1 - stats[new_group] = stats.get(new_group, 0) + 1 + channel['Group'] = final_group + country_counts[final_group] = country_counts.get(final_group, 0) + 1 - print(f"\nโœ… Changes made: {changes}") - print(f"๐Ÿ“บ Traditional broadcasters: {sum(v for k, v in stats.items() if k != 'Uncategorized')}") - print(f"๐Ÿ“ฑ Streaming/Unknown: {stats.get('Uncategorized', 0)}") + print(f"\n๐Ÿ“Š PROCESSING RESULTS:") + print(f"โœ… Changes made: {changes}") + print(f"๐Ÿ” Country detected: {stats['country_detected']}") + print(f"โœ… Kept existing countries: {stats['kept_existing_country']}") + print(f"๐Ÿ“ฑ Sent to Uncategorized: {stats['sent_to_uncategorized']}") + + print(f"\n๐ŸŒ FINAL GROUP DISTRIBUTION:") + sorted_countries = sorted(country_counts.items(), key=lambda x: (x[0] == "Uncategorized", -x[1])) + for country, count in sorted_countries: + print(f" {country}: {count} channels") return channels + def save_channels(channels): """Save channels to file.""" # Backup @@ -199,6 +376,7 @@ def save_channels(channels): print(f"โŒ Save error: {e}") return False + def generate_m3u(channels): """Generate M3U playlist.""" try: @@ -226,38 +404,23 @@ def generate_m3u(channels): print(f"โŒ M3U error: {e}") return False -def create_logs(): - """Create logs directory and simple log.""" - try: - os.makedirs('logs', exist_ok=True) - - with open('logs/playlist_update.log', 'w', encoding='utf-8') as f: - f.write(f"Enhanced IPTV Playlist Generator Log\n") - f.write(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") - f.write(f"Status: Completed successfully\n") - - print("โœ… Created logs") - - except Exception as e: - print(f"โš ๏ธ Log creation failed: {e}") def main(): """Main function.""" - print("๐Ÿ“บ Enhanced Country Detection - No Network Calls") - print("=" * 60) - - # Create logs directory - create_logs() + print("๐ŸŽฏ Enhanced IPTV Country Detection - 3-Point Analysis") + print("=" * 70) + print("๐Ÿ” Analyzes: Channel Name + EPG ID + Logo URL") + print("๐ŸŽฏ Filters: Only countries remain, streaming services โ†’ Uncategorized") + print("=" * 70) channels = load_channels() if not channels: - print("โ„น๏ธ No channels to process") - return True + return False - # Reorganize + # Enhanced reorganization channels = reorganize_channels(channels) - # Sort: Countries first, then Uncategorized last + # Sort: Countries first (alphabetically), then Uncategorized last channels.sort(key=lambda x: ( "zzz" if x.get('Group') == "Uncategorized" else x.get('Group', ''), x.get('Stream name', '') @@ -278,13 +441,15 @@ def main(): except: pass - print("\n๐ŸŽ‰ COMPLETED!") - print("โœ… Traditional broadcasters organized by country") - print("โœ… All streaming platforms โ†’ Uncategorized") - print("โœ… No network calls - fast execution") + print("\n๐ŸŽ‰ ENHANCED PROCESSING COMPLETE!") + print("โœ… 3-point analysis applied to all channels") + print("โœ… Countries detected from EPG ID, Logo URL, and Channel Names") + print("โœ… Streaming services filtered to Uncategorized") + print("โœ… Clean country-organized playlist generated") return True + if __name__ == "__main__": success = main() exit(0 if success else 1) \ No newline at end of file