2025-06-27 23:26:06 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""
|
2025-06-29 02:47:11 +02:00
|
|
|
IPTV Traditional Broadcasters Only - Simplified Version
|
|
|
|
Clean, minimal code with no redundancy
|
2025-06-27 23:26:06 +02:00
|
|
|
"""
|
|
|
|
|
2025-06-27 16:34:52 +02:00
|
|
|
import os
|
2025-06-29 02:02:34 +02:00
|
|
|
import shutil
|
2025-06-27 17:36:03 +02:00
|
|
|
from datetime import datetime
|
2025-06-29 02:06:07 +02:00
|
|
|
from pathlib import Path
|
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
# Ensure correct directory
|
2025-06-29 02:06:07 +02:00
|
|
|
script_dir = Path(__file__).parent
|
|
|
|
root_dir = script_dir.parent
|
|
|
|
os.chdir(root_dir)
|
2025-06-27 16:34:52 +02:00
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
def is_streaming_platform(all_text):
|
|
|
|
"""Check if channel is from streaming platform (simplified)."""
|
|
|
|
platforms = ["plex.tv", "pluto.tv", "jmp2.uk/plu-", "sam-", "samsung", "tubi", "xumo", "roku", "youtube"]
|
|
|
|
return any(platform in all_text for platform in platforms)
|
2025-06-28 00:11:19 +02:00
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
def detect_traditional_broadcaster_country(channel_name, epg_id="", logo_url="", stream_url=""):
|
|
|
|
"""Detect country for traditional broadcasters only."""
|
|
|
|
all_text = f"{channel_name.lower()} {epg_id.lower()} {logo_url.lower()} {stream_url.lower()}"
|
2025-06-29 02:02:34 +02:00
|
|
|
channel_lower = channel_name.lower()
|
2025-06-27 23:57:37 +02:00
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
# Skip streaming platforms
|
|
|
|
if is_streaming_platform(all_text):
|
|
|
|
return "Uncategorized"
|
2025-06-29 02:35:11 +02:00
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
# 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"
|
|
|
|
}
|
2025-06-29 02:35:11 +02:00
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
for domain, country in epg_countries.items():
|
|
|
|
if domain in epg_id.lower():
|
|
|
|
return country
|
2025-06-29 02:02:34 +02:00
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
# Essential broadcaster patterns (simplified)
|
|
|
|
broadcasters = {
|
|
|
|
"🇨🇦 Canada": ["tsn", "cbc", "ctv", "global", "sportsnet", "w network"],
|
|
|
|
"🇺🇸 United States": ["cbs", "nbc", "abc", "fox", "cnn", "espn", "discovery", "hgtv", "mtv", "c-span"],
|
|
|
|
"🇬🇧 United Kingdom": ["bbc", "itv", "sky", "channel 4", "channel 5"],
|
|
|
|
"🇵🇭 Philippines": ["abs-cbn", "gma", "anc", "tv5"],
|
|
|
|
"🇦🇺 Australia": ["abc australia", "nine network", "seven network"],
|
|
|
|
"🇯🇵 Japan": ["nhk", "fuji tv", "animax"],
|
|
|
|
"🇲🇾 Malaysia": ["tv1", "tv2", "astro"],
|
|
|
|
"🇩🇪 Germany": ["ard", "zdf", "rtl", "sat.1"],
|
|
|
|
"🇫🇷 France": ["tf1", "france 2", "canal+"],
|
|
|
|
"🇪🇸 Spain": ["antena 3", "telecinco", "tve"],
|
|
|
|
"🇮🇹 Italy": ["rai", "mediaset", "canale 5"],
|
|
|
|
"🇧🇷 Brazil": ["globo", "sbt", "record"],
|
|
|
|
"🇲🇽 Mexico": ["televisa", "tv azteca"],
|
|
|
|
"🇷🇺 Russia": ["первый", "россия", "нтв"]
|
2025-06-29 02:02:34 +02:00
|
|
|
}
|
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
for country, keywords in broadcasters.items():
|
2025-06-29 02:02:34 +02:00
|
|
|
if any(keyword in all_text for keyword in keywords):
|
|
|
|
return country
|
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
return "Uncategorized"
|
2025-06-29 02:06:07 +02:00
|
|
|
|
2025-06-29 02:02:34 +02:00
|
|
|
def load_channels():
|
2025-06-29 02:47:11 +02:00
|
|
|
"""Load channels from channels.txt."""
|
2025-06-29 02:02:34 +02:00
|
|
|
if not os.path.exists('channels.txt'):
|
2025-06-29 02:47:11 +02:00
|
|
|
print("❌ No channels.txt found")
|
|
|
|
return []
|
2025-06-28 23:41:12 +02:00
|
|
|
|
|
|
|
try:
|
2025-06-29 02:02:34 +02:00
|
|
|
with open('channels.txt', 'r', encoding='utf-8') as f:
|
2025-06-28 23:41:12 +02:00
|
|
|
content = f.read()
|
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
channels = []
|
|
|
|
for block in content.split('\n\n'):
|
2025-06-28 23:41:12 +02:00
|
|
|
if not block.strip():
|
|
|
|
continue
|
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
channel_data = {}
|
|
|
|
for line in block.strip().split('\n'):
|
2025-06-28 23:41:12 +02:00
|
|
|
if '=' in line:
|
|
|
|
key, value = line.split('=', 1)
|
|
|
|
channel_data[key.strip()] = value.strip()
|
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
if channel_data.get('Stream name'):
|
2025-06-28 23:41:12 +02:00
|
|
|
channels.append(channel_data)
|
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
print(f"✅ Loaded {len(channels)} channels")
|
|
|
|
return channels
|
2025-06-28 23:41:12 +02:00
|
|
|
|
|
|
|
except Exception as e:
|
2025-06-29 02:02:34 +02:00
|
|
|
print(f"❌ Error loading channels: {e}")
|
2025-06-29 02:47:11 +02:00
|
|
|
return []
|
2025-06-28 23:41:12 +02:00
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
def reorganize_channels(channels):
|
|
|
|
"""Reorganize channels by country (traditional only)."""
|
|
|
|
print("📺 Organizing traditional broadcasters by country...")
|
2025-06-29 02:02:34 +02:00
|
|
|
|
|
|
|
changes = 0
|
2025-06-29 02:47:11 +02:00
|
|
|
stats = {}
|
2025-06-29 02:02:34 +02:00
|
|
|
|
|
|
|
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', '')
|
2025-06-29 02:35:11 +02:00
|
|
|
stream_url = channel.get('Stream URL', '')
|
2025-06-29 02:02:34 +02:00
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
new_group = detect_traditional_broadcaster_country(stream_name, epg_id, logo, stream_url)
|
2025-06-29 02:02:34 +02:00
|
|
|
|
|
|
|
if old_group != new_group:
|
2025-06-29 02:47:11 +02:00
|
|
|
if new_group == "Uncategorized":
|
|
|
|
print(f"📱 Platform: '{stream_name}' → Uncategorized")
|
|
|
|
else:
|
|
|
|
print(f"📺 Fixed: '{stream_name}' {old_group} → {new_group}")
|
2025-06-29 02:02:34 +02:00
|
|
|
channel['Group'] = new_group
|
|
|
|
changes += 1
|
2025-06-29 02:35:11 +02:00
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
stats[new_group] = stats.get(new_group, 0) + 1
|
2025-06-29 02:35:11 +02:00
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
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)}")
|
2025-06-29 02:02:34 +02:00
|
|
|
|
|
|
|
return channels
|
|
|
|
|
|
|
|
def save_channels(channels):
|
2025-06-29 02:47:11 +02:00
|
|
|
"""Save channels to file."""
|
|
|
|
# Backup
|
2025-06-29 02:02:34 +02:00
|
|
|
if os.path.exists('channels.txt'):
|
2025-06-29 02:47:11 +02:00
|
|
|
backup = f"channels_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
|
|
|
|
shutil.copy2('channels.txt', backup)
|
|
|
|
print(f"📋 Backup: {backup}")
|
2025-06-29 02:02:34 +02:00
|
|
|
|
2025-06-28 23:41:12 +02:00
|
|
|
try:
|
2025-06-29 02:02:34 +02:00
|
|
|
with open('channels.txt', 'w', encoding='utf-8') as f:
|
2025-06-28 23:41:12 +02:00
|
|
|
for i, channel in enumerate(channels):
|
|
|
|
if i > 0:
|
|
|
|
f.write("\n\n")
|
|
|
|
f.write(f"Group = {channel.get('Group', 'Uncategorized')}\n")
|
|
|
|
f.write(f"Stream name = {channel.get('Stream name', 'Unknown')}\n")
|
|
|
|
f.write(f"Logo = {channel.get('Logo', '')}\n")
|
|
|
|
f.write(f"EPG id = {channel.get('EPG id', '')}\n")
|
|
|
|
f.write(f"Stream URL = {channel.get('Stream URL', '')}\n")
|
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
print(f"✅ Saved {len(channels)} channels")
|
2025-06-28 23:41:12 +02:00
|
|
|
return True
|
|
|
|
except Exception as e:
|
2025-06-29 02:47:11 +02:00
|
|
|
print(f"❌ Save error: {e}")
|
2025-06-28 23:41:12 +02:00
|
|
|
return False
|
|
|
|
|
2025-06-29 02:02:34 +02:00
|
|
|
def generate_m3u(channels):
|
2025-06-29 02:47:11 +02:00
|
|
|
"""Generate M3U playlist."""
|
2025-06-28 23:41:12 +02:00
|
|
|
try:
|
2025-06-29 02:02:34 +02:00
|
|
|
with open('playlist.m3u', 'w', encoding='utf-8') as f:
|
2025-06-28 23:41:12 +02:00
|
|
|
f.write('#EXTM3U\n')
|
|
|
|
|
|
|
|
for channel in channels:
|
2025-06-29 02:47:11 +02:00
|
|
|
name = channel.get('Stream name', '')
|
2025-06-28 23:41:12 +02:00
|
|
|
group = channel.get('Group', 'Uncategorized')
|
|
|
|
logo = channel.get('Logo', '')
|
|
|
|
epg_id = channel.get('EPG id', '')
|
|
|
|
url = channel.get('Stream URL', '')
|
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
if name and url:
|
2025-06-28 23:41:12 +02:00
|
|
|
f.write(f'#EXTINF:-1 group-title="{group}"')
|
|
|
|
if logo:
|
|
|
|
f.write(f' tvg-logo="{logo}"')
|
|
|
|
if epg_id:
|
|
|
|
f.write(f' tvg-id="{epg_id}"')
|
2025-06-29 02:47:11 +02:00
|
|
|
f.write(f',{name}\n{url}\n')
|
2025-06-27 18:36:13 +02:00
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
print("✅ Generated playlist.m3u")
|
2025-06-29 02:02:34 +02:00
|
|
|
return True
|
|
|
|
except Exception as e:
|
2025-06-29 02:47:11 +02:00
|
|
|
print(f"❌ M3U error: {e}")
|
2025-06-29 02:02:34 +02:00
|
|
|
return False
|
|
|
|
|
|
|
|
def main():
|
2025-06-29 02:47:11 +02:00
|
|
|
"""Main function."""
|
|
|
|
print("📺 Traditional Broadcasters Only - Country Organization")
|
2025-06-29 02:02:34 +02:00
|
|
|
print("=" * 60)
|
|
|
|
|
|
|
|
channels = load_channels()
|
|
|
|
if not channels:
|
2025-06-27 23:26:06 +02:00
|
|
|
return False
|
2025-06-29 02:02:34 +02:00
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
# Reorganize
|
|
|
|
channels = reorganize_channels(channels)
|
2025-06-29 02:02:34 +02:00
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
# 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', '')
|
2025-06-29 02:35:11 +02:00
|
|
|
))
|
2025-06-29 02:02:34 +02:00
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
# Save and generate
|
|
|
|
if not save_channels(channels):
|
2025-06-29 02:02:34 +02:00
|
|
|
return False
|
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
if not generate_m3u(channels):
|
2025-06-29 02:02:34 +02:00
|
|
|
return False
|
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
# Clear import
|
2025-06-29 02:02:34 +02:00
|
|
|
try:
|
|
|
|
with open('bulk_import.m3u', 'w', encoding='utf-8') as f:
|
2025-06-29 02:47:11 +02:00
|
|
|
f.write('#EXTM3U\n')
|
2025-06-29 02:02:34 +02:00
|
|
|
print("🧹 Cleared import file")
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
2025-06-29 02:47:11 +02:00
|
|
|
print("\n🎉 COMPLETED!")
|
|
|
|
print("✅ Traditional broadcasters organized by country")
|
|
|
|
print("✅ All streaming platforms → Uncategorized")
|
2025-06-29 02:02:34 +02:00
|
|
|
|
|
|
|
return True
|
2025-06-27 16:34:52 +02:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2025-06-29 02:02:34 +02:00
|
|
|
success = main()
|
2025-06-27 23:26:06 +02:00
|
|
|
exit(0 if success else 1)
|