Update scripts/generate_playlist.py
All checks were successful
Generate M3U Playlist with Auto-Organization / build-and-organize (push) Successful in 22s

This commit is contained in:
stoney420 2025-06-29 03:33:41 +02:00
parent f7aa7dc61f
commit 202773f4e5

View file

@ -1,12 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
IPTV Enhanced Country Detection - Updated Version IPTV Enhanced Country Detection - Simplified No-Hang Version
Uses 3-point analysis: Channel Name + EPG ID + Logo URL Uses your existing 3-point analysis but removes network calls that cause hanging
Then filters to keep only legitimate countries
""" """
import os import os
import shutil import shutil
import re
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
@ -15,50 +15,38 @@ script_dir = Path(__file__).parent
root_dir = script_dir.parent root_dir = script_dir.parent
os.chdir(root_dir) os.chdir(root_dir)
def detect_country_from_channel_content(channel_name, epg_id="", logo_url="", stream_url=""): def is_streaming_platform(all_text):
""" """Check if channel is from streaming platform."""
Enhanced country detection using 3-point analysis platforms = [
Priority: EPG ID > Logo URL > Channel Name > Stream URL "plex.tv", "pluto.tv", "jmp2.uk/plu-", "sam-", "samsung",
""" "tubi", "xumo", "roku", "youtube", "daddylive", "drew247tv",
"moveonjoy", "247tv", "livetv"
# Combine all text for analysis
all_text = f"{channel_name.lower()} {epg_id.lower()} {logo_url.lower()} {stream_url.lower()}"
# 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"
] ]
return any(platform in all_text for platform in platforms)
for service in streaming_services: def detect_traditional_broadcaster_country(channel_name, epg_id="", logo_url="", stream_url=""):
if service in all_text: """Enhanced 3-point country detection for traditional broadcasters."""
return "Uncategorized" all_text = f"{channel_name.lower()} {epg_id.lower()} {logo_url.lower()} {stream_url.lower()}"
channel_lower = channel_name.lower()
# STEP 2: EPG ID detection (most reliable) # Skip streaming platforms entirely
epg_patterns = { if is_streaming_platform(all_text):
".ca": "🇨🇦 Canada", return "Uncategorized"
".us": "🇺🇸 United States",
".uk": "🇬🇧 United Kingdom", # PRIORITY 1: EPG ID detection (most reliable)
".ph": "🇵🇭 Philippines", epg_countries = {
".au": "🇦🇺 Australia", ".ca": "🇨🇦 Canada", ".us": "🇺🇸 United States", ".uk": "🇬🇧 United Kingdom",
".jp": "🇯🇵 Japan", ".ph": "🇵🇭 Philippines", ".au": "🇦🇺 Australia", ".jp": "🇯🇵 Japan",
".my": "🇲🇾 Malaysia", ".my": "🇲🇾 Malaysia", ".de": "🇩🇪 Germany", ".fr": "🇫🇷 France",
".de": "🇩🇪 Germany", ".es": "🇪🇸 Spain", ".it": "🇮🇹 Italy", ".br": "🇧🇷 Brazil",
".fr": "🇫🇷 France", ".mx": "🇲🇽 Mexico", ".ar": "🇦🇷 Argentina"
".es": "🇪🇸 Spain",
".it": "🇮🇹 Italy",
".br": "🇧🇷 Brazil",
".nl": "🇳🇱 Netherlands"
} }
for domain, country in epg_patterns.items(): for domain, country in epg_countries.items():
if domain in epg_id.lower(): if domain in epg_id.lower():
return country return country
# STEP 3: Specific channel fixes (from your channel_processor.txt) # PRIORITY 2: Specific channel fixes from your original code
channel_lower = channel_name.lower()
# Canadian sports channels (TSN series) # 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"]): if any(x in channel_lower for x in ["tsn 1", "tsn 2", "tsn 3", "tsn 4", "tsn 5", "tsn1", "tsn2", "tsn3", "tsn4", "tsn5"]):
@ -84,119 +72,45 @@ def detect_country_from_channel_content(channel_name, epg_id="", logo_url="", st
if "animax" in channel_lower: if "animax" in channel_lower:
return "🇯🇵 Japan" return "🇯🇵 Japan"
# STEP 4: Logo URL analysis # PRIORITY 3: Logo URL detection (your 3-point analysis)
logo_patterns = { logo_patterns = {
"🇨🇦 Canada": ["/canada/", "/ca/", "canada.", "canadian"], "/canada/": "🇨🇦 Canada", "/ca/": "🇨🇦 Canada", "canada": "🇨🇦 Canada",
"🇺🇸 United States": ["/usa/", "/us/", "united-states", "american"], "/usa/": "🇺🇸 United States", "/us/": "🇺🇸 United States", "america": "🇺🇸 United States",
"🇬🇧 United Kingdom": ["/uk/", "/united-kingdom/", "british", "england"], "/uk/": "🇬🇧 United Kingdom", "/britain/": "🇬🇧 United Kingdom", "british": "🇬🇧 United Kingdom",
"🇩🇪 Germany": ["/germany/", "/de/", "german", "deutschland"], "/germany/": "🇩🇪 Germany", "/de/": "🇩🇪 Germany",
"🇫🇷 France": ["/france/", "/fr/", "french", "français"], "/france/": "🇫🇷 France", "/fr/": "🇫🇷 France",
"🇮🇹 Italy": ["/italy/", "/it/", "italian", "italiano"], "/spain/": "🇪🇸 Spain", "/es/": "🇪🇸 Spain",
"🇪🇸 Spain": ["/spain/", "/es/", "spanish", "español"], "/italy/": "🇮🇹 Italy", "/it/": "🇮🇹 Italy"
"🇳🇱 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 country, patterns in logo_patterns.items(): for pattern, country in logo_patterns.items():
for pattern in patterns: if pattern in logo_url.lower():
if pattern in logo_url.lower(): return country
return country
# STEP 5: Enhanced broadcaster patterns # PRIORITY 4: Essential broadcaster patterns
broadcaster_patterns = { broadcasters = {
"🇨🇦 Canada": [ "🇨🇦 Canada": ["tsn", "cbc", "ctv", "global", "sportsnet", "w network", "city tv", "aptn"],
"cbc", "tsn", "ctv", "global", "sportsnet", "citytv", "aptn", "teletoon", "ytv", "🇺🇸 United States": ["cbs", "nbc", "abc", "fox", "cnn", "espn", "discovery", "hgtv", "mtv", "c-span", "msnbc", "comedy central"],
"discovery canada", "history canada", "slice", "w network", "oln", "hgtv canada", "🇬🇧 United Kingdom": ["bbc", "itv", "sky", "channel 4", "channel 5", "dave", "e4", "film4"],
"food network canada", "showcase", "crave", "super channel", "hollywood suite" "🇵🇭 Philippines": ["abs-cbn", "gma", "anc", "tv5", "pbo", "net 25"],
], "🇦🇺 Australia": ["abc australia", "nine network", "seven network", "ten network", "sbs"],
"🇺🇸 United States": [ "🇯🇵 Japan": ["nhk", "fuji tv", "animax", "tbs", "tv asahi"],
"cbs", "nbc", "abc", "fox", "cnn", "espn", "amc", "mtv", "comedy central", "🇲🇾 Malaysia": ["tv1", "tv2", "astro", "rtm", "8tv"],
"discovery usa", "history usa", "tlc usa", "hgtv usa", "food network usa", "paramount", "🇩🇪 Germany": ["ard", "zdf", "rtl", "sat.1", "pro7", "vox"],
"nickelodeon usa", "cartoon network usa", "disney usa", "lifetime", "e!", "bravo usa" "🇫🇷 France": ["tf1", "france 2", "canal+", "m6", "arte"],
], "🇪🇸 Spain": ["antena 3", "telecinco", "tve", "la sexta"],
"🇬🇧 United Kingdom": [ "🇮🇹 Italy": ["rai", "mediaset", "canale 5", "la7"],
"bbc", "itv", "channel 4", "channel 5", "sky", "dave", "really", "yesterday", "🇧🇷 Brazil": ["globo", "sbt", "record", "band"],
"discovery uk", "history uk", "tlc uk", "living", "alibi", "gold", "drama" "🇲🇽 Mexico": ["televisa", "tv azteca", "canal 5"],
], "🇷🇺 Russia": ["первый", "россия", "нтв", "тнт"]
"🇩🇪 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 broadcaster_patterns.items(): for country, keywords in broadcasters.items():
for keyword in keywords: if any(keyword in all_text for keyword in keywords):
if keyword in all_text: return country
return country
return "Uncategorized" 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(): def load_channels():
"""Load channels from channels.txt.""" """Load channels from channels.txt."""
if not os.path.exists('channels.txt'): if not os.path.exists('channels.txt'):
@ -228,20 +142,12 @@ def load_channels():
print(f"❌ Error loading channels: {e}") print(f"❌ Error loading channels: {e}")
return [] return []
def reorganize_channels(channels): def reorganize_channels(channels):
"""Enhanced reorganization with 3-point analysis.""" """Reorganize channels by country (traditional only)."""
print("🔍 Enhanced Country Detection with 3-Point Analysis") print("📺 Organizing traditional broadcasters by country...")
print("📊 Analyzing: Channel Name + EPG ID + Logo URL")
print("-" * 60)
changes = 0 changes = 0
stats = { stats = {}
'country_detected': 0,
'sent_to_uncategorized': 0,
'kept_existing_country': 0
}
country_counts = {}
for channel in channels: for channel in channels:
old_group = channel.get('Group', 'Uncategorized') old_group = channel.get('Group', 'Uncategorized')
@ -250,46 +156,24 @@ def reorganize_channels(channels):
logo = channel.get('Logo', '') logo = channel.get('Logo', '')
stream_url = channel.get('Stream URL', '') stream_url = channel.get('Stream URL', '')
# Detect country using enhanced 3-point analysis new_group = detect_traditional_broadcaster_country(stream_name, epg_id, logo, stream_url)
detected_country = detect_country_from_channel_content(stream_name, epg_id, logo, stream_url)
# Decide final group if old_group != new_group:
if is_valid_country_group(old_group) and detected_country != "Uncategorized": if new_group == "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") print(f"📱 Platform: '{stream_name}' → Uncategorized")
changes += 1 else:
print(f"📺 Fixed: '{stream_name}' {old_group}{new_group}")
channel['Group'] = new_group
changes += 1
channel['Group'] = final_group stats[new_group] = stats.get(new_group, 0) + 1
country_counts[final_group] = country_counts.get(final_group, 0) + 1
print(f"\n📊 PROCESSING RESULTS:") print(f"\n✅ Changes made: {changes}")
print(f"✅ Changes made: {changes}") print(f"📺 Traditional broadcasters: {sum(v for k, v in stats.items() if k != 'Uncategorized')}")
print(f"🔍 Country detected: {stats['country_detected']}") print(f"📱 Streaming/Unknown: {stats.get('Uncategorized', 0)}")
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 return channels
def save_channels(channels): def save_channels(channels):
"""Save channels to file.""" """Save channels to file."""
# Backup # Backup
@ -315,7 +199,6 @@ def save_channels(channels):
print(f"❌ Save error: {e}") print(f"❌ Save error: {e}")
return False return False
def generate_m3u(channels): def generate_m3u(channels):
"""Generate M3U playlist.""" """Generate M3U playlist."""
try: try:
@ -343,23 +226,38 @@ def generate_m3u(channels):
print(f"❌ M3U error: {e}") print(f"❌ M3U error: {e}")
return False 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(): def main():
"""Main function.""" """Main function."""
print("🎯 Enhanced IPTV Country Detection - 3-Point Analysis") print("📺 Enhanced Country Detection - No Network Calls")
print("=" * 70) print("=" * 60)
print("🔍 Analyzes: Channel Name + EPG ID + Logo URL")
print("🎯 Filters: Only countries remain, streaming services → Uncategorized") # Create logs directory
print("=" * 70) create_logs()
channels = load_channels() channels = load_channels()
if not channels: if not channels:
return False print(" No channels to process")
return True
# Enhanced reorganization # Reorganize
channels = reorganize_channels(channels) channels = reorganize_channels(channels)
# Sort: Countries first (alphabetically), then Uncategorized last # Sort: Countries first, then Uncategorized last
channels.sort(key=lambda x: ( channels.sort(key=lambda x: (
"zzz" if x.get('Group') == "Uncategorized" else x.get('Group', ''), "zzz" if x.get('Group') == "Uncategorized" else x.get('Group', ''),
x.get('Stream name', '') x.get('Stream name', '')
@ -380,15 +278,13 @@ def main():
except: except:
pass pass
print("\n🎉 ENHANCED PROCESSING COMPLETE!") print("\n🎉 COMPLETED!")
print("✅ 3-point analysis applied to all channels") print("✅ Traditional broadcasters organized by country")
print("✅ Countries detected from EPG ID, Logo URL, and Channel Names") print("✅ All streaming platforms → Uncategorized")
print("✅ Streaming services filtered to Uncategorized") print("✅ No network calls - fast execution")
print("✅ Clean country-organized playlist generated")
return True return True
if __name__ == "__main__": if __name__ == "__main__":
success = main() success = main()
exit(0 if success else 1) exit(0 if success else 1)