Update app.py
Browse files
app.py
CHANGED
|
@@ -1,154 +1,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
|
| 9 |
with open("channels_fixed.json", "r", encoding="utf-8") as f:
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
def process_m3u(m3u_text):
|
| 42 |
-
lines = m3u_text.splitlines()
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 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 |
-
"NESN": "New.England.Sports.Network.HD.us2",
|
| 55 |
-
"REELZ USA": "ReelzChannel.HD.us2",
|
| 56 |
-
"ABCNY USA": "ABC.(WABC).New.York,.NY.us",
|
| 57 |
-
"OWN USA": "Oprah.Winfrey.Network.HD.us2",
|
| 58 |
-
"DISCOVERY USA": "Discovery.Channel.ca2",
|
| 59 |
-
"OUTDOOR CHANNEL": "Outdoor.Channel.HD.us2",
|
| 60 |
-
"OUTDOOR CHANNEL USA": "Outdoor.Channel.HD.us2",
|
| 61 |
-
"LOITV": "Soccer.Dummy.us",
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
-
for i, line in enumerate(lines):
|
| 65 |
if line.startswith("#EXTINF"):
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 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 |
-
keys = list(channel_map.keys())
|
| 113 |
-
guesses = get_close_matches(candidate, keys, n=1, cutoff=0.6)
|
| 114 |
-
if guesses:
|
| 115 |
-
match = channel_map[guesses[0]].get("tvg_id")
|
| 116 |
-
|
| 117 |
-
if match:
|
| 118 |
-
new_ext = insert_tvg_id(extinf, match)
|
| 119 |
-
log.append(f"✅ EVENTS | {candidate} → {match}")
|
| 120 |
-
else:
|
| 121 |
-
new_ext = insert_tvg_id(extinf, "Live.Event.us")
|
| 122 |
-
log.append(f"⚠️ EVENTS | {candidate} → Live.Event.us (fallback)")
|
| 123 |
-
|
| 124 |
-
new_ext = apply_event_special_cases(new_ext, candidate)
|
| 125 |
-
out_events_blocks.append((new_ext, url))
|
| 126 |
-
|
| 127 |
-
# Build output playlists
|
| 128 |
-
out_247 = "\n".join([f"{ext}\n{url}" for ext, url in out_247_blocks])
|
| 129 |
-
out_events = "\n".join([f"{ext}\n{url}" for ext, url in out_events_blocks])
|
| 130 |
-
|
| 131 |
-
return out_247, out_events, "\n".join(log)
|
| 132 |
-
|
| 133 |
-
# Gradio UI with file downloads
|
| 134 |
def run_app(m3u_text):
|
| 135 |
-
out_247, out_events,
|
| 136 |
-
file_247
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
if __name__ == "__main__":
|
| 154 |
-
|
|
|
|
| 1 |
import gradio as gr
|
|
|
|
|
|
|
| 2 |
import re
|
| 3 |
+
import random
|
| 4 |
import json
|
|
|
|
| 5 |
|
| 6 |
+
# Load JSON file at startup
|
| 7 |
with open("channels_fixed.json", "r", encoding="utf-8") as f:
|
| 8 |
+
CHANNELS_JSON = json.load(f)
|
| 9 |
+
|
| 10 |
+
# Hardcoded corrections and forced mappings
|
| 11 |
+
HARDCODED_FIXES = {
|
| 12 |
+
"BTN USA": "Big.Ten.Network.HD.us2",
|
| 13 |
+
"SNY": "SNY.SportsNet.New.York.HD.us2",
|
| 14 |
+
"MASN": "MASN.-.Mid.Atlantic.Sports.Network.us2",
|
| 15 |
+
"YES": "Yes.Network.us2",
|
| 16 |
+
"MSG": "MSG.National.us2",
|
| 17 |
+
"REELZ USA": "ReelzChannel.HD.us2",
|
| 18 |
+
"ABCNY USA": "ABC.(WABC).New.York,.NY.us",
|
| 19 |
+
"OWN USA": "Oprah.Winfrey.Network.HD.us2",
|
| 20 |
+
"DISCOVERY USA": "Discovery.Channel.ca2",
|
| 21 |
+
"OUTDOOR CHANNEL": "Outdoor.Channel.HD.us2",
|
| 22 |
+
"OUTDOOR CHANNEL USA": "Outdoor.Channel.HD.us2",
|
| 23 |
+
"LOITV": "Soccer.Dummy.us",
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
# Function to generate random 3-letter filename
|
| 27 |
+
def random_filename(is_upper=False):
|
| 28 |
+
letters = ''.join(random.choices("ABCDEFGHIJKLMNOPQRSTUVWXYZ", k=3))
|
| 29 |
+
return letters + ".m3u" if is_upper else letters.lower() + ".m3u"
|
| 30 |
+
|
| 31 |
+
# Resolve tvg-id using priority
|
| 32 |
+
def get_tvg_id(channel_name, json_data):
|
| 33 |
+
"""
|
| 34 |
+
Priority:
|
| 35 |
+
1. Hardcoded fixes
|
| 36 |
+
2. JSON matches
|
| 37 |
+
3. Event special fallbacks
|
| 38 |
+
4. Default Live.Event.us
|
| 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.strip().splitlines()
|
| 64 |
+
|
| 65 |
+
out_247, out_events = ["#EXTM3U"], ["#EXTM3U"]
|
| 66 |
+
|
| 67 |
+
for i in range(len(lines)):
|
| 68 |
+
line = lines[i]
|
| 69 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
if line.startswith("#EXTINF"):
|
| 71 |
+
# Extract channel name from EXTINF line
|
| 72 |
+
match = re.search(r",(.*)", line)
|
| 73 |
+
if not match:
|
| 74 |
+
continue
|
| 75 |
+
channel_name = match.group(1)
|
| 76 |
+
|
| 77 |
+
# Decide if it's 24/7 or Events
|
| 78 |
+
is_event = "EVENTS" in line.upper()
|
| 79 |
+
|
| 80 |
+
# Resolve tvg-id
|
| 81 |
+
tvg_id = get_tvg_id(channel_name, CHANNELS_JSON)
|
| 82 |
+
|
| 83 |
+
# Ensure "test" → Live.Event.us in Events
|
| 84 |
+
if tvg_id == "test" and is_event:
|
| 85 |
+
tvg_id = "Live.Event.us"
|
| 86 |
+
if tvg_id == "test" and not is_event:
|
| 87 |
+
tvg_id = "Info.Guide.Dummy.us"
|
| 88 |
+
|
| 89 |
+
# Replace or insert tvg-id
|
| 90 |
+
if 'tvg-id="' in line:
|
| 91 |
+
line = re.sub(r'tvg-id="[^"]*"', f'tvg-id="{tvg_id}"', line)
|
| 92 |
+
else:
|
| 93 |
+
line = line.replace("#EXTINF:-1", f'#EXTINF:-1 tvg-id="{tvg_id}"')
|
| 94 |
+
|
| 95 |
+
# Append to correct list
|
| 96 |
+
if is_event:
|
| 97 |
+
out_events.append(line)
|
| 98 |
+
if i + 1 < len(lines):
|
| 99 |
+
out_events.append(lines[i+1])
|
| 100 |
+
else:
|
| 101 |
+
out_247.append(line)
|
| 102 |
+
if i + 1 < len(lines):
|
| 103 |
+
out_247.append(lines[i+1])
|
| 104 |
+
|
| 105 |
+
# Generate random filenames
|
| 106 |
+
filename_247 = random_filename(is_upper=True)
|
| 107 |
+
filename_events = random_filename(is_upper=False)
|
| 108 |
+
|
| 109 |
+
return (
|
| 110 |
+
"\n".join(out_247),
|
| 111 |
+
"\n".join(out_events),
|
| 112 |
+
filename_247,
|
| 113 |
+
filename_events
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
# Gradio UI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
def run_app(m3u_text):
|
| 118 |
+
out_247, out_events, file_247, file_events = process_m3u(m3u_text)
|
| 119 |
+
return out_247, out_events, (file_247, out_247), (file_events, out_events)
|
| 120 |
+
|
| 121 |
+
with gr.Blocks() as demo:
|
| 122 |
+
gr.Markdown("## 📺 M3U Playlist Processor (Project 1)")
|
| 123 |
+
|
| 124 |
+
m3u_input = gr.Textbox(label="Paste M3U Playlist", lines=15, placeholder="Paste your .m3u playlist here...")
|
| 125 |
+
|
| 126 |
+
with gr.Row():
|
| 127 |
+
out_247 = gr.Textbox(label="24/7 Channels Output", lines=20)
|
| 128 |
+
out_events = gr.Textbox(label="Events Channels Output", lines=20)
|
| 129 |
+
|
| 130 |
+
with gr.Row():
|
| 131 |
+
dl_247 = gr.File(label="Download 24/7 M3U", type="binary")
|
| 132 |
+
dl_events = gr.File(label="Download Events M3U", type="binary")
|
| 133 |
+
|
| 134 |
+
process_btn = gr.Button("Process Playlist")
|
| 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 |
+
demo.launch()
|