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
"""
IPTV Enhanced Country Detection - Updated Version
Uses 3-point analysis: Channel Name + EPG ID + Logo URL
Then filters to keep only legitimate countries
IPTV Enhanced Country Detection - Simplified No-Hang Version
Uses your existing 3-point analysis but removes network calls that cause hanging
"""
import os
import shutil
import re
from datetime import datetime
from pathlib import Path
@ -15,50 +15,38 @@ script_dir = Path(__file__).parent
root_dir = script_dir.parent
os.chdir(root_dir)
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()}"
# 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"
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)
for service in streaming_services:
if service in all_text:
def detect_traditional_broadcaster_country(channel_name, epg_id="", logo_url="", stream_url=""):
"""Enhanced 3-point country detection for traditional broadcasters."""
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 2: EPG ID detection (most reliable)
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"
# 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 domain, country in epg_patterns.items():
for domain, country in epg_countries.items():
if domain in epg_id.lower():
return country
# STEP 3: Specific channel fixes (from your channel_processor.txt)
channel_lower = channel_name.lower()
# PRIORITY 2: Specific channel fixes from your original code
# 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"]):
@ -84,119 +72,45 @@ def detect_country_from_channel_content(channel_name, epg_id="", logo_url="", st
if "animax" in channel_lower:
return "🇯🇵 Japan"
# STEP 4: Logo URL analysis
# PRIORITY 3: Logo URL detection (your 3-point analysis)
logo_patterns = {
"🇨🇦 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"]
"/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"
}
for country, patterns in logo_patterns.items():
for pattern in patterns:
for pattern, country in logo_patterns.items():
if pattern in logo_url.lower():
return country
# 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"
]
# 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": ["первый", "россия", "нтв", "тнт"]
}
for country, keywords in broadcaster_patterns.items():
for keyword in keywords:
if keyword in all_text:
for country, keywords in broadcasters.items():
if any(keyword in all_text for keyword in keywords):
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."""
if not os.path.exists('channels.txt'):
@ -228,20 +142,12 @@ def load_channels():
print(f"❌ Error loading channels: {e}")
return []
def reorganize_channels(channels):
"""Enhanced reorganization with 3-point analysis."""
print("🔍 Enhanced Country Detection with 3-Point Analysis")
print("📊 Analyzing: Channel Name + EPG ID + Logo URL")
print("-" * 60)
"""Reorganize channels by country (traditional only)."""
print("📺 Organizing traditional broadcasters by country...")
changes = 0
stats = {
'country_detected': 0,
'sent_to_uncategorized': 0,
'kept_existing_country': 0
}
country_counts = {}
stats = {}
for channel in channels:
old_group = channel.get('Group', 'Uncategorized')
@ -250,46 +156,24 @@ def reorganize_channels(channels):
logo = channel.get('Logo', '')
stream_url = channel.get('Stream URL', '')
# Detect country using enhanced 3-point analysis
detected_country = detect_country_from_channel_content(stream_name, epg_id, logo, stream_url)
new_group = detect_traditional_broadcaster_country(stream_name, epg_id, logo, stream_url)
# 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":
if old_group != new_group:
if new_group == "Uncategorized":
print(f"📱 Platform: '{stream_name}' → Uncategorized")
else:
print(f"📺 Fixed: '{stream_name}' {old_group}{new_group}")
channel['Group'] = new_group
changes += 1
channel['Group'] = final_group
country_counts[final_group] = country_counts.get(final_group, 0) + 1
stats[new_group] = stats.get(new_group, 0) + 1
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")
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)}")
return channels
def save_channels(channels):
"""Save channels to file."""
# Backup
@ -315,7 +199,6 @@ def save_channels(channels):
print(f"❌ Save error: {e}")
return False
def generate_m3u(channels):
"""Generate M3U playlist."""
try:
@ -343,23 +226,38 @@ 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 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)
print("📺 Enhanced Country Detection - No Network Calls")
print("=" * 60)
# Create logs directory
create_logs()
channels = load_channels()
if not channels:
return False
print(" No channels to process")
return True
# Enhanced reorganization
# Reorganize
channels = reorganize_channels(channels)
# Sort: Countries first (alphabetically), then Uncategorized last
# Sort: Countries first, then Uncategorized last
channels.sort(key=lambda x: (
"zzz" if x.get('Group') == "Uncategorized" else x.get('Group', ''),
x.get('Stream name', '')
@ -380,15 +278,13 @@ def main():
except:
pass
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")
print("\n🎉 COMPLETED!")
print("✅ Traditional broadcasters organized by country")
print("✅ All streaming platforms → Uncategorized")
print("✅ No network calls - fast execution")
return True
if __name__ == "__main__":
success = main()
exit(0 if success else 1)