From 30f1e9f11bb3db298e74379801b958d44a141ee6 Mon Sep 17 00:00:00 2001 From: stoney420 Date: Sun, 29 Jun 2025 02:35:11 +0200 Subject: [PATCH] Update scripts/generate_playlist.py --- scripts/generate_playlist.py | 277 +++++++++++++++++++---------------- 1 file changed, 150 insertions(+), 127 deletions(-) diff --git a/scripts/generate_playlist.py b/scripts/generate_playlist.py index 1712669..a1cecb5 100644 --- a/scripts/generate_playlist.py +++ b/scripts/generate_playlist.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 """ -IPTV Playlist Generator - Enhanced Country Detection -FIXED: Properly handles working directory for Forgejo +IPTV Country + Platform Organizer +Groups channels by country first, then platform within country +Example: ๐Ÿ‡บ๐Ÿ‡ธ USA, ๐Ÿ‡บ๐Ÿ‡ธ USA - Plex, ๐Ÿ‡บ๐Ÿ‡ธ USA - Pluto, ๐Ÿ‡จ๐Ÿ‡ฆ Canada, ๐Ÿ‡จ๐Ÿ‡ฆ Canada - Plex """ import os @@ -13,19 +14,28 @@ from pathlib import Path # FIXED: Ensure we're in the right directory script_dir = Path(__file__).parent root_dir = script_dir.parent - -# Change to root directory where channels.txt should be os.chdir(root_dir) -def setup_directories(): - """Create required directories.""" - os.makedirs('reports/daily', exist_ok=True) - os.makedirs('backups', exist_ok=True) - os.makedirs('logs', exist_ok=True) +def detect_country_and_platform(channel_name, epg_id="", logo_url="", stream_url=""): + """Enhanced country + platform detection.""" + all_text = f"{channel_name.lower().strip()} {epg_id.lower().strip()} {logo_url.lower().strip()} {stream_url.lower().strip()}" + channel_lower = channel_name.lower() + + # STEP 1: Detect the country first + country = detect_base_country(channel_name, epg_id, logo_url, stream_url) + + # STEP 2: Detect platform + platform = detect_platform(all_text) + + # STEP 3: Combine country + platform + if platform: + return f"{country} - {platform}" + else: + return country -def detect_country_enhanced(channel_name, epg_id="", logo_url=""): - """Enhanced country detection with all the fixes.""" - all_text = f"{channel_name.lower().strip()} {epg_id.lower().strip()} {logo_url.lower().strip()}" +def detect_base_country(channel_name, epg_id="", logo_url="", stream_url=""): + """Detect the base country of the channel.""" + all_text = f"{channel_name.lower().strip()} {epg_id.lower().strip()} {logo_url.lower().strip()} {stream_url.lower().strip()}" channel_lower = channel_name.lower() # PRIORITY 1: EPG ID suffix detection (most reliable) @@ -41,99 +51,129 @@ def detect_country_enhanced(channel_name, epg_id="", logo_url=""): return "๐Ÿ‡ฆ๐Ÿ‡บ Australia" elif ".jp" in epg_id.lower(): return "๐Ÿ‡ฏ๐Ÿ‡ต Japan" + elif ".my" in epg_id.lower(): + return "๐Ÿ‡ฒ๐Ÿ‡พ Malaysia" + elif ".de" in epg_id.lower(): + return "๐Ÿ‡ฉ๐Ÿ‡ช Germany" + elif ".fr" in epg_id.lower(): + return "๐Ÿ‡ซ๐Ÿ‡ท France" # PRIORITY 2: Specific channel fixes for misclassified channels - # Canadian sports channels (TSN series) + + # Canadian channels 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" - - # CBC News Toronto (Canadian) - if "cbc news toronto" in channel_lower: + if any(x in channel_lower for x in ["cbc news", "cbc news toronto", "cbc news british columbia"]): + return "๐Ÿ‡จ๐Ÿ‡ฆ Canada" + if "w network" in channel_lower and "canada" in all_text: return "๐Ÿ‡จ๐Ÿ‡ฆ Canada" # 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" + if any(x in channel_lower for x in ["cbs", "nbc", "abc", "fox news", "cnn", "espn", "discovery channel", "cartoon network"]): + return "๐Ÿ‡บ๐Ÿ‡ธ United States" - # UK shows/channels + # UK channels if "come dine with me" in channel_lower: return "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom" + if any(x in channel_lower for x in ["bbc", "itv", "sky news", "channel 4", "channel 5"]): + return "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom" - # Philippines news channels + # Philippines channels if any(x in channel_lower for x in ["anc global", "anc ph"]): return "๐Ÿ‡ต๐Ÿ‡ญ Philippines" - # Japan anime channels + # Malaysia channels + if "malaysia" in channel_lower or "bein sports 1 malaysia" in channel_lower: + return "๐Ÿ‡ฒ๐Ÿ‡พ Malaysia" + + # Japan channels if "animax" in channel_lower: return "๐Ÿ‡ฏ๐Ÿ‡ต Japan" - # PRIORITY 3: Platform-based detection - # Pluto TV special handling + # PRIORITY 3: Special platform handling with country detection + + # Pluto TV regional detection if "pluto.tv" in all_text or "images.pluto.tv" in all_text or "jmp2.uk/plu-" in all_text: - pluto_overrides = { + pluto_countries = { "cbc news toronto": "๐Ÿ‡จ๐Ÿ‡ฆ Canada", + "cbc news british columbia": "๐Ÿ‡จ๐Ÿ‡ฆ Canada", "come dine with me": "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom" } - for channel_pattern, country in pluto_overrides.items(): + for channel_pattern, country in pluto_countries.items(): if channel_pattern in channel_lower: return country - return "๐Ÿ‡บ๐Ÿ‡ธ United States" # Default Pluto TV to US + # Default Pluto TV to US + return "๐Ÿ‡บ๐Ÿ‡ธ United States" - # Plex TV handling (mostly US) + # Samsung TV Plus regional detection + if "samsung" in all_text or "sam-" in stream_url: + if "cabc" in epg_id.lower(): # Canadian Samsung channels + return "๐Ÿ‡จ๐Ÿ‡ฆ Canada" + return "๐Ÿ‡บ๐Ÿ‡ธ United States" # Default Samsung to US + + # Plex TV (mostly US unless specifically regional) if "plex.tv" in all_text or "provider-static.plex.tv" in all_text: return "๐Ÿ‡บ๐Ÿ‡ธ United States" - # PRIORITY 4: Pattern matching - patterns = { - "๐Ÿ‡บ๐Ÿ‡ธ United States": ["usa", "us ", "america", "cbs", "nbc", "abc", "fox", "espn", "cnn", "amc", "mtv", "comedy central", "nickelodeon", "disney", "hgtv", "syfy", "bravo", "tlc", "lifetime", "paramount", "weather channel", "tmz", "wgn"], - "๐Ÿ‡จ๐Ÿ‡ฆ Canada": ["canada", "canadian", "cbc", "ctv", "global", "tsn", "sportsnet", "w network", "much", "teletoon"], - "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom": ["uk", "british", "bbc", "itv", "sky", "channel 4", "channel 5", "dave", "quest", "bt sport", "premier league"], - "๐Ÿ‡ต๐Ÿ‡ญ Philippines": ["philippines", "filipino", "abs-cbn", "gma", "anc", "cnn philippines"], - "๐Ÿ‡ฆ๐Ÿ‡บ Australia": ["australia", "australian", "abc australia", "nine network", "seven network", "ten network"], - "๐Ÿ‡ฏ๐Ÿ‡ต Japan": ["japan", "japanese", "nhk", "fuji tv", "animax"], - "๐Ÿ‡ฎ๐Ÿ‡ณ India": ["india", "indian", "hindi", "zee", "star", "sony", "colors"], - "๐Ÿ‡ฉ๐Ÿ‡ช Germany": ["germany", "german", "ard", "zdf", "rtl", "sat.1", "pro7"], - "๐Ÿ‡ซ๐Ÿ‡ท France": ["france", "french", "tf1", "france 2", "m6", "canal+"], - "๐Ÿ‡ช๐Ÿ‡ธ Spain": ["spain", "spanish", "antena 3", "telecinco", "tve"], - "๐Ÿ‡ฎ๐Ÿ‡น Italy": ["italy", "italian", "rai", "mediaset", "canale 5"], - "๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands": ["netherlands", "dutch", "npo", "rtl 4"], - "๐Ÿ‡ง๐Ÿ‡ท Brazil": ["brazil", "brazilian", "globo", "sbt", "record"], - "๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico": ["mexico", "mexican", "televisa", "tv azteca"], - "๐Ÿ‡ท๐Ÿ‡บ Russia": ["russia", "russian", "ะฟะตั€ะฒั‹ะน", "ั€ะพััะธั", "ะฝั‚ะฒ"] + # PRIORITY 4: Pattern matching by keywords + country_patterns = { + "๐Ÿ‡บ๐Ÿ‡ธ United States": ["usa", "america", "united states", "c-span", "newsmax", "newsnation"], + "๐Ÿ‡จ๐Ÿ‡ฆ Canada": ["canada", "canadian", "ctv", "global", "sportsnet"], + "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom": ["uk", "british", "britain", "england"], + "๐Ÿ‡ต๐Ÿ‡ญ Philippines": ["philippines", "filipino"], + "๐Ÿ‡ฆ๐Ÿ‡บ Australia": ["australia", "australian"], + "๐Ÿ‡ฏ๐Ÿ‡ต Japan": ["japan", "japanese", "nhk"], + "๐Ÿ‡ฒ๐Ÿ‡พ Malaysia": ["malaysia", "malaysian"], + "๐Ÿ‡ฉ๐Ÿ‡ช Germany": ["germany", "german", "deutschland"], + "๐Ÿ‡ซ๐Ÿ‡ท France": ["france", "french"], + "๐Ÿ‡ช๐Ÿ‡ธ Spain": ["spain", "spanish"], + "๐Ÿ‡ฎ๐Ÿ‡น Italy": ["italy", "italian"], + "๐Ÿ‡ง๐Ÿ‡ท Brazil": ["brazil", "brazilian"], + "๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico": ["mexico", "mexican"], + "๐Ÿ‡ท๐Ÿ‡บ Russia": ["russia", "russian"] } - for country, keywords in patterns.items(): + for country, keywords in country_patterns.items(): if any(keyword in all_text for keyword in keywords): return country return "๐ŸŒ International" -def debug_current_directory(): - """Debug what files are available in current directory.""" - current_dir = os.getcwd() - print(f"๐Ÿ—‚๏ธ Current working directory: {current_dir}") +def detect_platform(all_text): + """Detect streaming platform.""" - files = os.listdir('.') - print(f"๐Ÿ“ Files in directory: {len(files)} items") + # Platform detection patterns + if "pluto.tv" in all_text or "images.pluto.tv" in all_text or "jmp2.uk/plu-" in all_text: + return "Pluto TV" + elif "plex.tv" in all_text or "provider-static.plex.tv" in all_text: + return "Plex TV" + elif "samsung" in all_text or "sam-" in all_text: + return "Samsung TV+" + elif "tubi" in all_text: + return "Tubi" + elif "xumo" in all_text: + return "Xumo" + elif "crackle" in all_text: + return "Crackle" + elif "roku" in all_text: + return "Roku Channel" + elif "youtube" in all_text: + return "YouTube" + elif "peacock" in all_text: + return "Peacock" + elif "paramount+" in all_text or "paramount plus" in all_text: + return "Paramount+" - # Check for our key files - key_files = ['channels.txt', 'playlist.m3u', 'bulk_import.m3u'] - for file in key_files: - if os.path.exists(file): - size = os.path.getsize(file) - print(f"โœ… Found {file} ({size} bytes)") - else: - print(f"โŒ Missing {file}") + return None # No platform detected = traditional broadcaster def load_channels(): """Load existing channels from channels.txt.""" channels = [] - # Debug first - debug_current_directory() - if not os.path.exists('channels.txt'): print("โŒ No existing channels.txt found") return channels @@ -168,26 +208,39 @@ def load_channels(): return channels -def update_channel_countries(channels): - """Update all channels with enhanced country detection.""" - print("๐ŸŒ Updating channel countries with enhanced detection...") +def reorganize_channels_by_country_platform(channels): + """Reorganize channels by country, then platform within country.""" + print("๐ŸŒ Reorganizing channels by country + platform...") changes = 0 + group_stats = {} for channel in channels: old_group = channel.get('Group', 'Uncategorized') stream_name = channel.get('Stream name', '') epg_id = channel.get('EPG id', '') logo = channel.get('Logo', '') + stream_url = channel.get('Stream URL', '') - new_group = detect_country_enhanced(stream_name, epg_id, logo) + # Apply country + platform detection + new_group = detect_country_and_platform(stream_name, epg_id, logo, stream_url) if old_group != new_group: - print(f"๐Ÿ”„ Fix: '{stream_name}' {old_group} โ†’ {new_group}") + print(f"๐Ÿ”„ Reorg: '{stream_name}' {old_group} โ†’ {new_group}") channel['Group'] = new_group changes += 1 + + # Count groups + group_stats[new_group] = group_stats.get(new_group, 0) + 1 + + print(f"โœ… Reorganized {changes} channel classifications") + + # Show organization results + print(f"\n๐Ÿ—‚๏ธ NEW ORGANIZATION:") + sorted_groups = sorted(group_stats.items(), key=lambda x: (x[0].split(' - ')[0], x[0])) + for group, count in sorted_groups: + print(f" {group}: {count} channels") - print(f"โœ… Updated {changes} channel classifications") return channels def save_channels(channels): @@ -217,13 +270,13 @@ def save_channels(channels): return False def generate_m3u(channels): - """Generate M3U playlist.""" + """Generate M3U playlist with country + platform organization.""" try: with open('playlist.m3u', 'w', encoding='utf-8') as f: f.write('#EXTM3U\n') valid_channels = 0 - country_stats = {} + group_stats = {} for channel in channels: stream_name = channel.get('Stream name', '') @@ -242,16 +295,15 @@ def generate_m3u(channels): f.write(f'{url}\n') valid_channels += 1 - country_stats[group] = country_stats.get(group, 0) + 1 + group_stats[group] = group_stats.get(group, 0) + 1 print(f"๐Ÿ“บ Generated playlist.m3u with {valid_channels} channels") - # Show top countries - sorted_countries = sorted(country_stats.items(), key=lambda x: x[1], reverse=True) - print("๐ŸŒ Top Countries:") - for country, count in sorted_countries[:10]: - percentage = (count / valid_channels * 100) if valid_channels > 0 else 0 - print(f" {country}: {count} ({percentage:.1f}%)") + # Show organized groups + print("๐ŸŒ Organized Groups:") + sorted_groups = sorted(group_stats.items(), key=lambda x: (x[0].split(' - ')[0], x[0])) + for group, count in sorted_groups[:15]: # Show top 15 + print(f" {group}: {count} channels") return True @@ -259,45 +311,13 @@ def generate_m3u(channels): print(f"โŒ Error generating playlist: {e}") return False -def create_report(channels): - """Create a simple report.""" - try: - timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') - report_file = f"reports/daily/report_{timestamp}.md" - - with open(report_file, 'w', encoding='utf-8') as f: - f.write("# ๐ŸŒ Enhanced Country Detection Report\n") - f.write(f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n") - f.write(f"## ๐Ÿ“Š Statistics\n") - f.write(f"- **Total Channels:** {len(channels)}\n\n") - - # Count by country - country_stats = {} - for channel in channels: - group = channel.get('Group', 'Uncategorized') - country_stats[group] = country_stats.get(group, 0) + 1 - - f.write("## ๐ŸŒ Country Distribution\n") - sorted_countries = sorted(country_stats.items(), key=lambda x: x[1], reverse=True) - for country, count in sorted_countries: - percentage = (count / len(channels) * 100) if len(channels) > 0 else 0 - f.write(f"- **{country}:** {count} channels ({percentage:.1f}%)\n") - - f.write("\n---\n") - f.write("*Enhanced country detection with 99%+ accuracy*\n") - - print(f"๐Ÿ“Š Report created: {report_file}") - - except Exception as e: - print(f"โš ๏ธ Could not create report: {e}") - def main(): """Main execution function.""" - print("๐Ÿš€ IPTV Playlist Generator - Enhanced Country Detection") + print("๐ŸŒ IPTV Country + Platform Organizer") + print("=" * 60) + print("Organizing channels: Country first, then platform within country") + print("Example: ๐Ÿ‡บ๐Ÿ‡ธ USA, ๐Ÿ‡บ๐Ÿ‡ธ USA - Plex, ๐Ÿ‡จ๐Ÿ‡ฆ Canada, ๐Ÿ‡จ๐Ÿ‡ฆ Canada - Plex") print("=" * 60) - - # Setup - setup_directories() # Load existing channels channels = load_channels() @@ -306,23 +326,25 @@ def main(): print("โŒ No channels found to process") return False - # Update countries with enhanced detection - updated_channels = update_channel_countries(channels) + # Reorganize by country + platform + reorganized_channels = reorganize_channels_by_country_platform(channels) - # Sort channels - updated_channels.sort(key=lambda x: (x.get('Group', ''), x.get('Stream name', ''))) + # Sort channels: Country first, then platform within country, then channel name + print("๐Ÿ“ Sorting channels by country + platform...") + reorganized_channels.sort(key=lambda x: ( + x.get('Group', '').split(' - ')[0], # Country first + x.get('Group', ''), # Then platform within country + x.get('Stream name', '') # Then channel name + )) - # Save updated channels - if not save_channels(updated_channels): + # Save reorganized channels + if not save_channels(reorganized_channels): return False # Generate playlist - if not generate_m3u(updated_channels): + if not generate_m3u(reorganized_channels): return False - # Create report - create_report(updated_channels) - # Clear import file try: with open('bulk_import.m3u', 'w', encoding='utf-8') as f: @@ -331,12 +353,13 @@ def main(): except: pass - print("\n๐ŸŽ‰ ENHANCED COUNTRY DETECTION COMPLETED!") - print("โœ… All TSN channels should now be in Canada") - print("โœ… TV Land, We TV should now be in USA") - print("โœ… ANC channels should now be in Philippines") - print("โœ… Come Dine with Me should now be in UK") - print("โœ… Animax should now be in Japan") + print("\n๐ŸŽ‰ COUNTRY + PLATFORM ORGANIZATION COMPLETED!") + print("โœ… Channels organized by country first, then platform") + print("โœ… TSN channels โ†’ Canada") + print("โœ… CBC News โ†’ Canada") + print("โœ… TV Land โ†’ USA") + print("โœ… Plex/Pluto channels organized within their countries") + print("โœ… Clean country-based organization achieved!") return True