diff --git a/scripts/generate_playlist.py b/scripts/generate_playlist.py index c3dda98..6ba43d5 100644 --- a/scripts/generate_playlist.py +++ b/scripts/generate_playlist.py @@ -3,173 +3,16 @@ import os import json from datetime import datetime -# --- Configuration --- +# --- Simple Configuration --- CHANNELS_FILE = 'channels.txt' PLAYLIST_FILE = 'playlist.m3u' IMPORT_FILE = 'bulk_import.m3u' LOG_FILE = 'playlist_update.log' + +# Config files (optional) SETTINGS_FILE = 'config/settings.json' GROUP_OVERRIDES_FILE = 'config/group_overrides.json' -# Country detection patterns -COUNTRY_PATTERNS = { - # United Kingdom - "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom": [ - "uk", "united kingdom", "britain", "british", "england", "scotland", "wales", - "bbc", "itv", "sky", "channel 4", "channel5", "dave", "really", "yesterday", - "drama", "pick", "alibi", "eden", "gold", "w+1", "more4", "e4", "film4", - "quest", "discovery uk", "eurosport uk", "bt sport" - ], - - # United States - "๐Ÿ‡บ๐Ÿ‡ธ United States": [ - "usa", "us", "united states", "america", "american", - "cnn", "fox news", "msnbc", "abc", "nbc", "cbs", "espn", "fox sports", - "disney", "nickelodeon", "cartoon network", "tnt", "tbs", "usa network", - "fx", "amc", "discovery", "history", "nat geo", "hgtv", "food network" - ], - - # Canada - "๐Ÿ‡จ๐Ÿ‡ฆ Canada": [ - "canada", "canadian", "cbc", "ctv", "global", "city tv", "tvo", "ici", - "sportsnet", "tsn", "rds", "aptn", "ztele", "canal d", "tele quebec" - ], - - # Australia - "๐Ÿ‡ฆ๐Ÿ‡บ Australia": [ - "australia", "australian", "aussie", "abc au", "sbs", "nine", "ten", - "seven", "foxtel", "optus sport", "kayo" - ], - - # Germany - "๐Ÿ‡ฉ๐Ÿ‡ช Germany": [ - "germany", "german", "deutschland", "ard", "zdf", "rtl", "sat.1", "pro7", - "vox", "kabel", "sport1", "eurosport de", "sky de" - ], - - # France - "๐Ÿ‡ซ๐Ÿ‡ท France": [ - "france", "french", "tf1", "france 2", "france 3", "france 5", "m6", - "canal+", "bfm", "cnews", "rmc", "eurosport fr" - ], - - # Spain - "๐Ÿ‡ช๐Ÿ‡ธ Spain": [ - "spain", "spanish", "espaรฑa", "tve", "antena 3", "cuatro", "telecinco", - "la sexta", "canal sur", "telemadrid", "movistar" - ], - - # Italy - "๐Ÿ‡ฎ๐Ÿ‡น Italy": [ - "italy", "italian", "italia", "rai", "mediaset", "canale 5", "italia 1", - "rete 4", "la7", "sky italia" - ], - - # Netherlands - "๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands": [ - "netherlands", "dutch", "nederland", "npo", "rtl nl", "sbs nl", "veronica", - "net5", "rtl 4", "rtl 5", "rtl 7" - ], - - # Belgium - "๐Ÿ‡ง๐Ÿ‡ช Belgium": [ - "belgium", "belgian", "vtm", "een", "canvas", "ketnet", "rtbf", "la une" - ], - - # Portugal - "๐Ÿ‡ต๐Ÿ‡น Portugal": [ - "portugal", "portuguese", "rtp", "sic", "tvi", "porto canal", "benfica tv" - ], - - # India - "๐Ÿ‡ฎ๐Ÿ‡ณ India": [ - "india", "indian", "hindi", "bollywood", "zee", "star plus", "colors", - "sony", "dd national", "aaj tak", "ndtv", "times now" - ], - - # Brazil - "๐Ÿ‡ง๐Ÿ‡ท Brazil": [ - "brazil", "brazilian", "brasil", "globo", "sbt", "record", "band", - "rede tv", "cultura", "sportv" - ], - - # Mexico - "๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico": [ - "mexico", "mexican", "televisa", "tv azteca", "canal 5", "las estrellas", - "canal once", "imagen" - ], - - # Arabic/Middle East - "๐Ÿ‡ธ๐Ÿ‡ฆ Arabic": [ - "arabic", "arab", "al jazeera", "mbc", "dubai", "abu dhabi", "qatar", - "saudi", "kuwait", "lebanon", "syria", "iraq", "jordan" - ], - - # Turkey - "๐Ÿ‡น๐Ÿ‡ท Turkey": [ - "turkey", "turkish", "trt", "atv", "kanal d", "star tv", "fox tr", - "show tv", "ntv" - ], - - # Russia - "๐Ÿ‡ท๐Ÿ‡บ Russia": [ - "russia", "russian", "rt", "channel one", "ั€ะพััะธั", "ะฝั‚ะฒ", "ั‚ะฝั‚" - ], - - # Poland - "๐Ÿ‡ต๐Ÿ‡ฑ Poland": [ - "poland", "polish", "tvp", "polsat", "tvn", "tv4", "canal+ pl" - ], - - # Sweden - "๐Ÿ‡ธ๐Ÿ‡ช Sweden": [ - "sweden", "swedish", "svt", "tv4", "kanal 5", "tv6", "tv8" - ], - - # Norway - "๐Ÿ‡ณ๐Ÿ‡ด Norway": [ - "norway", "norwegian", "nrk", "tv2", "tvnorge", "max" - ], - - # Denmark - "๐Ÿ‡ฉ๐Ÿ‡ฐ Denmark": [ - "denmark", "danish", "dr", "tv2 dk", "kanal 5 dk", "6eren" - ], - - # Finland - "๐Ÿ‡ซ๐Ÿ‡ฎ Finland": [ - "finland", "finnish", "yle", "mtv3", "nelonen", "sub" - ], - - # Greece - "๐Ÿ‡ฌ๐Ÿ‡ท Greece": [ - "greece", "greek", "ert", "mega", "ant1", "alpha", "skai" - ], - - # China - "๐Ÿ‡จ๐Ÿ‡ณ China": [ - "china", "chinese", "cctv", "cgtn", "phoenix", "tvb", "ไธญๅ›ฝ", "ไธญๅคฎ" - ], - - # Japan - "๐Ÿ‡ฏ๐Ÿ‡ต Japan": [ - "japan", "japanese", "nhk", "fuji tv", "tbs", "tv asahi", "nippon tv" - ], - - # South Korea - "๐Ÿ‡ฐ๐Ÿ‡ท South Korea": [ - "korea", "korean", "kbs", "mbc", "sbs", "jtbc", "tvn" - ], - - # International/Global - "๐ŸŒ International": [ - "international", "global", "world", "euro", "euronews", "dw", - "france 24", "cnn international", "bbc world", "sky news", - "bloomberg", "cnbc", "discovery", "national geographic", - "animal planet", "history", "travel", "mtv", "vh1", "nickelodeon" - ] -} - def log_message(message, level="INFO"): """Logs messages to file and prints them.""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -190,8 +33,7 @@ def load_settings(): "sort_channels": True, "backup_before_import": True, "auto_cleanup_import": True, - "auto_detect_country": True, - "normalize_country_names": True + "auto_detect_country": True } if os.path.exists(SETTINGS_FILE): @@ -204,62 +46,8 @@ def load_settings(): return default_settings -def detect_country_from_text(text): - """Detect country from channel name, group, or other text.""" - text_lower = text.lower() - - # Score each country based on keyword matches - country_scores = {} - - for country, keywords in COUNTRY_PATTERNS.items(): - score = 0 - for keyword in keywords: - if keyword in text_lower: - # Give higher score for exact matches and longer keywords - score += len(keyword) * (2 if keyword == text_lower else 1) - - if score > 0: - country_scores[country] = score - - # Return country with highest score - if country_scores: - best_country = max(country_scores, key=country_scores.get) - return best_country, country_scores[best_country] - - return None, 0 - -def smart_country_detection(channel): - """Smart country detection using multiple sources.""" - # Sources to check (in order of priority) - sources = [ - ("Stream name", channel.get('Stream name', '')), - ("Group", channel.get('Group', '')), - ("EPG id", channel.get('EPG id', '')), - ("Logo", channel.get('Logo', '')) - ] - - best_country = None - best_score = 0 - detection_source = None - - for source_name, text in sources: - if text: - country, score = detect_country_from_text(text) - if country and score > best_score: - best_country = country - best_score = score - detection_source = source_name - - # Log detection for debugging - if best_country: - log_message(f"Country detection: '{channel.get('Stream name', 'Unknown')}' โ†’ {best_country} (from {detection_source}, score: {best_score})", "DEBUG") - else: - log_message(f"Country detection: Could not detect country for '{channel.get('Stream name', 'Unknown')}'", "DEBUG") - - return best_country or "๐ŸŒ International" - def load_group_overrides(): - """Load manual group overrides.""" + """Load group overrides.""" if os.path.exists(GROUP_OVERRIDES_FILE): try: with open(GROUP_OVERRIDES_FILE, 'r', encoding='utf-8') as f: @@ -269,37 +57,211 @@ def load_group_overrides(): return {} -def apply_country_detection(channel, settings): - """Apply country detection and overrides.""" - original_group = channel.get('Group', 'Uncategorized') +def detect_country_from_channel(channel_name, epg_id="", logo_url=""): + """ + Auto-detect country from channel information using cascading approach. + Returns the detected country group or 'Uncategorized' if no match found. + """ - # Check manual overrides first - group_overrides = load_group_overrides() - stream_name = channel.get('Stream name', '').lower() + # Normalize inputs for better matching + channel_lower = channel_name.lower().strip() + epg_lower = epg_id.lower().strip() + logo_lower = logo_url.lower().strip() + # 1. BROADCASTER NAMES (Most reliable) + broadcaster_country = { + # US Major Networks + "cbs": "๐Ÿ‡บ๐Ÿ‡ธ United States", "nbc": "๐Ÿ‡บ๐Ÿ‡ธ United States", + "abc": "๐Ÿ‡บ๐Ÿ‡ธ United States", "fox": "๐Ÿ‡บ๐Ÿ‡ธ United States", + "espn": "๐Ÿ‡บ๐Ÿ‡ธ United States", "cnn": "๐Ÿ‡บ๐Ÿ‡ธ United States", + "hbo": "๐Ÿ‡บ๐Ÿ‡ธ United States", "mtv": "๐Ÿ‡บ๐Ÿ‡ธ United States", + "discovery": "๐Ÿ‡บ๐Ÿ‡ธ United States", "cartoon network": "๐Ÿ‡บ๐Ÿ‡ธ United States", + "showtime": "๐Ÿ‡บ๐Ÿ‡ธ United States", "starz": "๐Ÿ‡บ๐Ÿ‡ธ United States", + "tnt": "๐Ÿ‡บ๐Ÿ‡ธ United States", "tbs": "๐Ÿ‡บ๐Ÿ‡ธ United States", + + # UK Networks + "bbc": "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", "itv": "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", + "channel 4": "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", "sky": "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", + "e4": "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", "film4": "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", + "more4": "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", "dave": "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", + + # Canadian Networks + "cbc": "๐Ÿ‡จ๐Ÿ‡ฆ Canada", "ctv": "๐Ÿ‡จ๐Ÿ‡ฆ Canada", "global": "๐Ÿ‡จ๐Ÿ‡ฆ Canada", + "tvo": "๐Ÿ‡จ๐Ÿ‡ฆ Canada", "aptn": "๐Ÿ‡จ๐Ÿ‡ฆ Canada", + + # German Networks + "ard": "๐Ÿ‡ฉ๐Ÿ‡ช Germany", "zdf": "๐Ÿ‡ฉ๐Ÿ‡ช Germany", "rtl": "๐Ÿ‡ฉ๐Ÿ‡ช Germany", + "sat.1": "๐Ÿ‡ฉ๐Ÿ‡ช Germany", "pro7": "๐Ÿ‡ฉ๐Ÿ‡ช Germany", "vox": "๐Ÿ‡ฉ๐Ÿ‡ช Germany", + "kabel": "๐Ÿ‡ฉ๐Ÿ‡ช Germany", "n24": "๐Ÿ‡ฉ๐Ÿ‡ช Germany", + + # French Networks + "tf1": "๐Ÿ‡ซ๐Ÿ‡ท France", "france 2": "๐Ÿ‡ซ๐Ÿ‡ท France", "m6": "๐Ÿ‡ซ๐Ÿ‡ท France", + "canal+": "๐Ÿ‡ซ๐Ÿ‡ท France", "arte": "๐Ÿ‡ซ๐Ÿ‡ท France", + + # Spanish Networks + "tve": "๐Ÿ‡ช๐Ÿ‡ธ Spain", "antena 3": "๐Ÿ‡ช๐Ÿ‡ธ Spain", "telecinco": "๐Ÿ‡ช๐Ÿ‡ธ Spain", + "cuatro": "๐Ÿ‡ช๐Ÿ‡ธ Spain", "la sexta": "๐Ÿ‡ช๐Ÿ‡ธ Spain", + + # Italian Networks + "rai": "๐Ÿ‡ฎ๐Ÿ‡น Italy", "mediaset": "๐Ÿ‡ฎ๐Ÿ‡น Italy", "canale 5": "๐Ÿ‡ฎ๐Ÿ‡น Italy", + "italia 1": "๐Ÿ‡ฎ๐Ÿ‡น Italy", "rete 4": "๐Ÿ‡ฎ๐Ÿ‡น Italy", + + # Other Countries + "globo": "๐Ÿ‡ง๐Ÿ‡ท Brazil", "band": "๐Ÿ‡ง๐Ÿ‡ท Brazil", "sbt": "๐Ÿ‡ง๐Ÿ‡ท Brazil", + "televisa": "๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico", "tv azteca": "๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico", + "al jazeera": "๐Ÿ‡ธ๐Ÿ‡ฆ Arabic", "mbc": "๐Ÿ‡ธ๐Ÿ‡ฆ Arabic", "lbc": "๐Ÿ‡ธ๐Ÿ‡ฆ Arabic", + "rt": "๐Ÿ‡ท๐Ÿ‡บ Russia", "channel one": "๐Ÿ‡ท๐Ÿ‡บ Russia", + "cctv": "๐Ÿ‡จ๐Ÿ‡ณ China", "phoenix": "๐Ÿ‡จ๐Ÿ‡ณ China", + "nhk": "๐Ÿ‡ฏ๐Ÿ‡ต Japan", "fuji": "๐Ÿ‡ฏ๐Ÿ‡ต Japan", + "kbs": "๐Ÿ‡ฐ๐Ÿ‡ท South Korea", "sbs": "๐Ÿ‡ฐ๐Ÿ‡ท South Korea", "mbc": "๐Ÿ‡ฐ๐Ÿ‡ท South Korea", + "abc au": "๐Ÿ‡ฆ๐Ÿ‡บ Australia", "seven": "๐Ÿ‡ฆ๐Ÿ‡บ Australia", "nine": "๐Ÿ‡ฆ๐Ÿ‡บ Australia", + "npo": "๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands", "rtl nl": "๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands" + } + + # Check for exact broadcaster matches + for broadcaster, country in broadcaster_country.items(): + if broadcaster in channel_lower: + return country + + # 2. COUNTRY NAME PATTERNS (Very reliable) + country_patterns = { + "๐Ÿ‡บ๐Ÿ‡ธ United States": [ + "usa", "united states", "america", "american", " us ", "us:", "(us)", "us hd" + ], + "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom": [ + " uk ", "uk:", "(uk)", "united kingdom", "britain", "british", "england", "english" + ], + "๐Ÿ‡จ๐Ÿ‡ฆ Canada": [ + "canada", "canadian", " ca ", "ca:", "(ca)" + ], + "๐Ÿ‡ฉ๐Ÿ‡ช Germany": [ + "germany", "german", "deutschland", "deutsch", " de ", "de:", "(de)" + ], + "๐Ÿ‡ซ๐Ÿ‡ท France": [ + "france", "french", "franรงais", " fr ", "fr:", "(fr)" + ], + "๐Ÿ‡ช๐Ÿ‡ธ Spain": [ + "spain", "spanish", "espaรฑa", "espaรฑol", " es ", "es:", "(es)" + ], + "๐Ÿ‡ฎ๐Ÿ‡น Italy": [ + "italy", "italian", "italia", "italiano", " it ", "it:", "(it)" + ], + "๐Ÿ‡ง๐Ÿ‡ท Brazil": [ + "brazil", "brazilian", "brasil", "portuguรชs", " br ", "br:", "(br)" + ], + "๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico": [ + "mexico", "mexican", "mรฉxico", " mx ", "mx:", "(mx)" + ], + "๐Ÿ‡ฆ๐Ÿ‡บ Australia": [ + "australia", "australian", "aussie", " au ", "au:", "(au)" + ], + "๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands": [ + "netherlands", "dutch", "holland", "nederland", " nl ", "nl:", "(nl)" + ], + "๐Ÿ‡ท๐Ÿ‡บ Russia": [ + "russia", "russian", "ั€ะพััะธั", " ru ", "ru:", "(ru)" + ], + "๐Ÿ‡จ๐Ÿ‡ณ China": [ + "china", "chinese", "ไธญๅ›ฝ", " cn ", "cn:", "(cn)" + ], + "๐Ÿ‡ฏ๐Ÿ‡ต Japan": [ + "japan", "japanese", "ๆ—ฅๆœฌ", " jp ", "jp:", "(jp)" + ], + "๐Ÿ‡ฐ๐Ÿ‡ท South Korea": [ + "korea", "korean", "ํ•œ๊ตญ", " kr ", "kr:", "(kr)", "south korea" + ], + "๐Ÿ‡ธ๐Ÿ‡ฆ Arabic": [ + "arabic", "arab", "middle east", "ุงู„ุนุฑุจูŠุฉ", "al ", "aljazeera" + ], + "๐Ÿ‡ฎ๐Ÿ‡ณ India": [ + "india", "indian", "hindi", "bollywood", "zee", "star plus" + ], + "๐Ÿ‡ต๐Ÿ‡น Portugal": [ + "portugal", "portuguese", " pt ", "pt:", "(pt)" + ], + "๐Ÿ‡น๐Ÿ‡ท Turkey": [ + "turkey", "turkish", "tรผrkiye", " tr ", "tr:", "(tr)" + ] + } + + # Check channel name for country patterns + for country, patterns in country_patterns.items(): + for pattern in patterns: + if pattern in channel_lower: + return country + + # 3. EPG ID ANALYSIS (Good for country codes) + epg_country_map = { + "๐Ÿ‡บ๐Ÿ‡ธ United States": [".us", "usa.", ".com"], + "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom": [".uk", ".gb", "british"], + "๐Ÿ‡จ๐Ÿ‡ฆ Canada": [".ca", "canada."], + "๐Ÿ‡ฉ๐Ÿ‡ช Germany": [".de", "german."], + "๐Ÿ‡ซ๐Ÿ‡ท France": [".fr", "france."], + "๐Ÿ‡ช๐Ÿ‡ธ Spain": [".es", "spain."], + "๐Ÿ‡ฎ๐Ÿ‡น Italy": [".it", "italy."], + "๐Ÿ‡ง๐Ÿ‡ท Brazil": [".br", "brazil."], + "๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico": [".mx", "mexico."], + "๐Ÿ‡ฆ๐Ÿ‡บ Australia": [".au", "australia."], + "๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands": [".nl", "netherlands."], + "๐Ÿ‡ท๐Ÿ‡บ Russia": [".ru", "russia."], + "๐Ÿ‡จ๐Ÿ‡ณ China": [".cn", "china."], + "๐Ÿ‡ฏ๐Ÿ‡ต Japan": [".jp", "japan."], + "๐Ÿ‡ฐ๐Ÿ‡ท South Korea": [".kr", "korea."], + "๐Ÿ‡ฎ๐Ÿ‡ณ India": [".in", "india."], + "๐Ÿ‡ต๐Ÿ‡น Portugal": [".pt", "portugal."], + "๐Ÿ‡น๐Ÿ‡ท Turkey": [".tr", "turkey."] + } + + # Check EPG ID for country indicators + if epg_id: + for country, patterns in epg_country_map.items(): + for pattern in patterns: + if pattern in epg_lower: + return country + + # 4. LOGO URL ANALYSIS (Sometimes helpful) + if logo_url: + for country, patterns in epg_country_map.items(): + for pattern in patterns: + if pattern in logo_lower: + return country + + # If no match found, return Uncategorized + return "Uncategorized" + +def apply_auto_country_detection(channel, group_overrides, settings): + """ + Enhanced version of apply_group_overrides that includes auto-detection. + """ + stream_name = channel.get('Stream name', '') + epg_id = channel.get('EPG id', '') + logo_url = channel.get('Logo', '') + current_group = channel.get('Group', 'Uncategorized') + + # First try manual overrides (highest priority) + stream_name_lower = stream_name.lower() for key, new_group in group_overrides.items(): - if key.lower() in stream_name: + if key.lower() in stream_name_lower: channel['Group'] = new_group - log_message(f"Manual override: '{channel.get('Stream name')}' โ†’ {new_group}", "DEBUG") + log_message(f"Manual override: '{stream_name}' โ†’ {new_group}", "DEBUG") return channel - # Auto-detect country if enabled + # If auto-detection is enabled, try it if settings.get('auto_detect_country', True): - detected_country = smart_country_detection(channel) + detected_country = detect_country_from_channel(stream_name, epg_id, logo_url) - # Normalize existing country names if enabled - if settings.get('normalize_country_names', True): + # Only override if we detected something specific (not "Uncategorized") + if detected_country != "Uncategorized": channel['Group'] = detected_country + log_message(f"Auto-detected: '{stream_name}' โ†’ {detected_country}", "INFO") else: - # Only change if current group is not already a country - current_group_lower = original_group.lower() - is_already_country = any( - any(keyword in current_group_lower for keyword in keywords) - for keywords in COUNTRY_PATTERNS.values() - ) - - if not is_already_country: - channel['Group'] = detected_country + # Keep existing group or set to Uncategorized + if current_group in ['', 'Unknown', 'Other']: + channel['Group'] = "Uncategorized" + else: + # Auto-detection disabled, use manual overrides only + if current_group in ['', 'Unknown', 'Other']: + channel['Group'] = "Uncategorized" return channel @@ -378,7 +340,6 @@ def remove_duplicates(channels, settings): unique_channels.append(channel) else: duplicate_count += 1 - log_message(f"Duplicate removed: {channel.get('Stream name', 'Unknown')}", "DEBUG") if duplicate_count > 0: log_message(f"Removed {duplicate_count} duplicate channels", "INFO") @@ -390,6 +351,7 @@ def remove_duplicates(channels, settings): def process_import(): """Process bulk import file.""" settings = load_settings() + group_overrides = load_group_overrides() if not os.path.exists(IMPORT_FILE): log_message(f"No {IMPORT_FILE} found, skipping import", "INFO") @@ -418,7 +380,7 @@ def process_import(): continue channel_data = parse_m3u_entry(extinf_line, url_line) - channel_data = apply_country_detection(channel_data, settings) + channel_data = apply_auto_country_detection(channel_data, group_overrides, settings) if channel_data.get('Stream name') and channel_data.get('Stream URL'): imported_channels.append(channel_data) @@ -494,9 +456,10 @@ def generate_playlist(): if os.path.exists(LOG_FILE): open(LOG_FILE, 'w').close() - log_message("Starting playlist generation with smart country detection...", "INFO") + log_message("Starting playlist generation...", "INFO") settings = load_settings() + group_overrides = load_group_overrides() # Process import imported_channels = process_import() @@ -518,8 +481,7 @@ def generate_playlist(): if block.strip(): channel = parse_channel_block(block) if channel: - # Apply country detection to existing channels too - channel = apply_country_detection(channel, settings) + channel = apply_auto_country_detection(channel, group_overrides, settings) parsed_channels.append(channel) log_message(f"Parsed {len(parsed_channels)} channels", "INFO") @@ -527,25 +489,18 @@ def generate_playlist(): # Remove duplicates parsed_channels = remove_duplicates(parsed_channels, settings) - # Sort channels by country then name + # Sort channels if settings.get('sort_channels', True): parsed_channels.sort(key=lambda x: (x.get('Group', '').lower(), x.get('Stream name', '').lower())) log_message("Channels sorted by country and name", "INFO") - # Log country distribution - country_counts = {} - for channel in parsed_channels: - country = channel.get('Group', 'Unknown') - country_counts[country] = country_counts.get(country, 0) + 1 - - log_message("Country distribution:", "INFO") - for country, count in sorted(country_counts.items()): - log_message(f" {country}: {count} channels", "INFO") - # Build M3U m3u_lines = ["#EXTM3U"] valid_channels = 0 + # Count channels by country for stats + country_stats = {} + for channel in parsed_channels: stream_name = channel.get('Stream name', '') group_name = channel.get('Group', 'Uncategorized') @@ -567,6 +522,9 @@ def generate_playlist(): m3u_lines.append(extinf_line) m3u_lines.append(stream_url) valid_channels += 1 + + # Count by country + country_stats[group_name] = country_stats.get(group_name, 0) + 1 # Write M3U try: @@ -574,23 +532,13 @@ def generate_playlist(): for line in m3u_lines: f.write(line + '\n') log_message(f"Generated {PLAYLIST_FILE} with {valid_channels} channels", "INFO") + + # Log country statistics + log_message(f"Channels by country: {dict(sorted(country_stats.items(), key=lambda x: x[1], reverse=True))}", "INFO") + except Exception as e: log_message(f"Error writing playlist: {e}", "ERROR") - # Update channels.txt with new country assignments - if settings.get('normalize_country_names', True): - try: - with open(CHANNELS_FILE, 'w', encoding='utf-8') as f: - for i, channel in enumerate(parsed_channels): - if i > 0: - f.write("\n\n") - - block_content = convert_to_channels_txt_block(channel) - f.write(block_content) - log_message("Updated channels.txt with normalized country names", "INFO") - except Exception as e: - log_message(f"Error updating channels.txt: {e}", "ERROR") - log_message("Playlist generation complete", "INFO") if __name__ == "__main__":