Update app.py
Browse files
app.py
CHANGED
|
@@ -1,143 +1,146 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
-
import re
|
| 3 |
import random
|
|
|
|
|
|
|
| 4 |
import json
|
|
|
|
| 5 |
|
| 6 |
-
# Load
|
| 7 |
with open("channels_fixed.json", "r", encoding="utf-8") as f:
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
#
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
"
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
"""
|
| 40 |
-
# Step 1: Hardcoded
|
| 41 |
-
for key, val in HARDCODED_FIXES.items():
|
| 42 |
-
if key.lower() in channel_name.lower():
|
| 43 |
-
return val
|
| 44 |
-
|
| 45 |
-
# Step 2: JSON lookup
|
| 46 |
-
for json_channel, info in json_data.items():
|
| 47 |
-
if channel_name.lower() in json_channel.lower():
|
| 48 |
-
return info.get("tvg_id", "")
|
| 49 |
-
|
| 50 |
-
# Step 3: Event fallback rules
|
| 51 |
-
if "MLB" in channel_name.upper():
|
| 52 |
-
return "MLB.Baseball.Dummy.us"
|
| 53 |
-
if "NHL" in channel_name.upper():
|
| 54 |
-
return "NHL.Hockey.Dummy.us"
|
| 55 |
-
if "ATP" in channel_name.upper() or "WTA" in channel_name.upper():
|
| 56 |
-
return "Tennis.Channel.us"
|
| 57 |
-
|
| 58 |
-
# Step 4: Default
|
| 59 |
-
return "Live.Event.us"
|
| 60 |
-
|
| 61 |
-
# Process the M3U
|
| 62 |
def process_m3u(m3u_text):
|
| 63 |
-
lines = m3u_text.
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
if line.startswith("#EXTINF"):
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
#
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
def run_app(m3u_text):
|
| 118 |
-
out_247, out_events,
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
with
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
process_btn.click(
|
| 137 |
-
fn=run_app,
|
| 138 |
-
inputs=[m3u_input],
|
| 139 |
-
outputs=[out_247, out_events, dl_247, dl_events]
|
| 140 |
-
)
|
| 141 |
|
| 142 |
if __name__ == "__main__":
|
| 143 |
-
|
|
|
|
| 1 |
import gradio as gr
|
|
|
|
| 2 |
import random
|
| 3 |
+
import string
|
| 4 |
+
import re
|
| 5 |
import json
|
| 6 |
+
from difflib import get_close_matches
|
| 7 |
|
| 8 |
+
# Load channel mapping JSON
|
| 9 |
with open("channels_fixed.json", "r", encoding="utf-8") as f:
|
| 10 |
+
channel_map = json.load(f)
|
| 11 |
+
|
| 12 |
+
# Random 3-character filename generator
|
| 13 |
+
def random_filename(uppercase=True):
|
| 14 |
+
letters = string.ascii_uppercase if uppercase else string.ascii_lowercase
|
| 15 |
+
return "".join(random.choice(letters) for _ in range(3)) + ".m3u"
|
| 16 |
+
|
| 17 |
+
# Insert tvg-id immediately after #EXTINF:-1
|
| 18 |
+
def insert_tvg_id(extinf_line, tvg_id):
|
| 19 |
+
# Remove existing tvg-id if present
|
| 20 |
+
line = re.sub(r'tvg-id="[^"]*"', '', extinf_line)
|
| 21 |
+
# Insert tvg-id immediately after #EXTINF:-1
|
| 22 |
+
line = line.replace('#EXTINF:-1', f'#EXTINF:-1 tvg-id="{tvg_id}"', 1)
|
| 23 |
+
# Clean multiple spaces
|
| 24 |
+
line = re.sub(r'\s+', ' ', line).strip()
|
| 25 |
+
return line
|
| 26 |
+
|
| 27 |
+
# Apply special event rules
|
| 28 |
+
def apply_event_special_cases(extinf_line, channel_name):
|
| 29 |
+
line = extinf_line
|
| 30 |
+
if "MLB LEAGUE PASS" in channel_name:
|
| 31 |
+
return insert_tvg_id(line, "MLB.Baseball.Dummy.us")
|
| 32 |
+
if "NHL GAMECENTER" in channel_name:
|
| 33 |
+
return insert_tvg_id(line, "NHL.Hockey.Dummy.us")
|
| 34 |
+
if "ATP" in channel_name or "WTA" in channel_name:
|
| 35 |
+
return insert_tvg_id(line, "Tennis.Channel.us")
|
| 36 |
+
if 'tvg-id="test"' in line:
|
| 37 |
+
return insert_tvg_id(line, "Live.Event.us")
|
| 38 |
+
return line
|
| 39 |
+
|
| 40 |
+
# Main processor
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
def process_m3u(m3u_text):
|
| 42 |
+
lines = m3u_text.splitlines()
|
| 43 |
+
out_247_blocks = []
|
| 44 |
+
out_events_blocks = []
|
| 45 |
+
log = []
|
| 46 |
+
|
| 47 |
+
# Hardcoded abbreviation exceptions for Events
|
| 48 |
+
exceptions = {
|
| 49 |
+
"BTN": "Big.Ten.Network.HD.us2",
|
| 50 |
+
"SNY": "SNY.SportsNet.New.York.HD.us2",
|
| 51 |
+
"MASN": "MASN.-.Mid.Atlantic.Sports.Network.us2",
|
| 52 |
+
"YES": "Yes.Network.us2",
|
| 53 |
+
"MSG": "MSG.National.us2",
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
for i, line in enumerate(lines):
|
| 57 |
if line.startswith("#EXTINF"):
|
| 58 |
+
url = lines[i+1] if i+1 < len(lines) else ""
|
| 59 |
+
extinf = line
|
| 60 |
+
|
| 61 |
+
# Extract tvg-id and channel name
|
| 62 |
+
tvg_match = re.search(r'tvg-id="([^"]*)"', extinf)
|
| 63 |
+
tvg_id = tvg_match.group(1) if tvg_match else None
|
| 64 |
+
name_match = re.search(r",(.*)", extinf)
|
| 65 |
+
channel_name = name_match.group(1).strip() if name_match else ""
|
| 66 |
+
|
| 67 |
+
# Extract group
|
| 68 |
+
group_match = re.search(r'group-title="([^"]*)"', extinf)
|
| 69 |
+
group_title = group_match.group(1).upper().strip() if group_match else ""
|
| 70 |
+
|
| 71 |
+
# --- 24/7 CHANNELS ---
|
| 72 |
+
if group_title and "24/7" in group_title:
|
| 73 |
+
if tvg_id == "test" or not tvg_id:
|
| 74 |
+
new_ext = insert_tvg_id(extinf, "Info.Guide.Dummy.us")
|
| 75 |
+
log.append(f"ℹ️ 24/7 | {channel_name} → Info.Guide.Dummy.us")
|
| 76 |
+
else:
|
| 77 |
+
new_ext = extinf
|
| 78 |
+
out_247_blocks.append((new_ext, url))
|
| 79 |
+
|
| 80 |
+
# --- EVENTS ---
|
| 81 |
+
elif group_title and "EVENTS" in group_title:
|
| 82 |
+
candidate = channel_name
|
| 83 |
+
|
| 84 |
+
# Check hardcoded abbreviation exceptions
|
| 85 |
+
matched_exception = False
|
| 86 |
+
for abbr, fixed_id in exceptions.items():
|
| 87 |
+
if candidate.upper().startswith(abbr):
|
| 88 |
+
new_ext = insert_tvg_id(extinf, fixed_id)
|
| 89 |
+
new_ext = apply_event_special_cases(new_ext, candidate)
|
| 90 |
+
out_events_blocks.append((new_ext, url))
|
| 91 |
+
log.append(f"✅ EVENTS | {candidate} → {fixed_id} (hardcoded {abbr} exception)")
|
| 92 |
+
matched_exception = True
|
| 93 |
+
break
|
| 94 |
+
if matched_exception:
|
| 95 |
+
continue
|
| 96 |
+
|
| 97 |
+
# Try matching against JSON
|
| 98 |
+
match = None
|
| 99 |
+
for key, data in channel_map.items():
|
| 100 |
+
if data.get("tvg_id") and key.lower() in candidate.lower():
|
| 101 |
+
match = data["tvg_id"]
|
| 102 |
+
break
|
| 103 |
+
if not match:
|
| 104 |
+
keys = list(channel_map.keys())
|
| 105 |
+
guesses = get_close_matches(candidate, keys, n=1, cutoff=0.6)
|
| 106 |
+
if guesses:
|
| 107 |
+
match = channel_map[guesses[0]].get("tvg_id")
|
| 108 |
+
|
| 109 |
+
if match:
|
| 110 |
+
new_ext = insert_tvg_id(extinf, match)
|
| 111 |
+
log.append(f"✅ EVENTS | {candidate} → {match}")
|
| 112 |
+
else:
|
| 113 |
+
new_ext = insert_tvg_id(extinf, "Live.Event.us")
|
| 114 |
+
log.append(f"⚠️ EVENTS | {candidate} → Live.Event.us (fallback)")
|
| 115 |
+
|
| 116 |
+
new_ext = apply_event_special_cases(new_ext, candidate)
|
| 117 |
+
out_events_blocks.append((new_ext, url))
|
| 118 |
+
|
| 119 |
+
# Build output playlists
|
| 120 |
+
out_247 = "\n".join([f"{ext}\n{url}" for ext, url in out_247_blocks])
|
| 121 |
+
out_events = "\n".join([f"{ext}\n{url}" for ext, url in out_events_blocks])
|
| 122 |
+
|
| 123 |
+
return out_247, out_events, "\n".join(log)
|
| 124 |
+
|
| 125 |
+
# Gradio UI with file downloads
|
| 126 |
def run_app(m3u_text):
|
| 127 |
+
out_247, out_events, log = process_m3u(m3u_text)
|
| 128 |
+
file_247 = random_filename(uppercase=True)
|
| 129 |
+
file_events = random_filename(uppercase=False)
|
| 130 |
+
with open(file_247, "w", encoding="utf-8") as f:
|
| 131 |
+
f.write(out_247)
|
| 132 |
+
with open(file_events, "w", encoding="utf-8") as f:
|
| 133 |
+
f.write(out_events)
|
| 134 |
+
|
| 135 |
+
return file_247, file_events, log
|
| 136 |
+
|
| 137 |
+
iface = gr.Interface(
|
| 138 |
+
fn=run_app,
|
| 139 |
+
inputs=gr.Textbox(lines=15, placeholder="Paste your M3U playlist here..."),
|
| 140 |
+
outputs=[gr.File(label="24/7 Playlist"), gr.File(label="Events Playlist"), gr.Textbox(label="Log")],
|
| 141 |
+
title="Project 1 Playlist Processor",
|
| 142 |
+
description="Splits 24/7 and Events playlists, applies JSON tvg-id mappings, adds special sports rules, and outputs two clean M3Us."
|
| 143 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
|
| 145 |
if __name__ == "__main__":
|
| 146 |
+
iface.launch()
|