quick / app.py
shvchenko's picture
Update app.py
df66e9c verified
import gradio as gr
import random
import string
import re
import json
from difflib import get_close_matches
# Load channel mapping JSON (unchanged)
with open("channels_fixed.json", "r", encoding="utf-8") as f:
channel_map = json.load(f)
# Random 3-character filename generator (unchanged)
def random_filename(uppercase=True):
letters = string.ascii_uppercase if uppercase else string.ascii_lowercase
return "".join(random.choice(letters) for _ in range(3)) + ".m3u"
# Insert tvg-id immediately after #EXTINF:-1 (unchanged)
def insert_tvg_id(extinf_line, tvg_id):
line = re.sub(r'tvg-id="[^"]*"', '', extinf_line) # remove existing tvg-id if present
line = line.replace('#EXTINF:-1', f'#EXTINF:-1 tvg-id="{tvg_id}"', 1)
line = re.sub(r'\s+', ' ', line).strip()
return line
# Apply special event rules (unchanged)
def apply_event_special_cases(extinf_line, channel_name):
line = extinf_line
if "MLB LEAGUE PASS" in channel_name:
return insert_tvg_id(line, "MLB.Baseball.Dummy.us")
if "NHL GAMECENTER" in channel_name:
return insert_tvg_id(line, "NHL.Hockey.Dummy.us")
if "ATP" in channel_name or "WTA" in channel_name:
return insert_tvg_id(line, "Tennis.Channel.us")
if 'tvg-id="test"' in line:
return insert_tvg_id(line, "Live.Event.us")
return line
# Normalize helper: remove non-alphanum, lowercase
def normalize_name(s: str) -> str:
if not s:
return ""
s = s.upper()
# Remove punctuation but keep letters and digits
s = re.sub(r'[^A-Z0-9]', '', s)
return s
# Main processor (Project 1 Base with minimal fix)
def process_m3u(m3u_text):
lines = m3u_text.splitlines()
out_247_blocks = []
out_events_blocks = []
log = []
# Hardcoded abbreviation + forced fixes (now includes your new entries)
hardcoded_fixes = {
"BTN": "Big.Ten.Network.HD.us2",
"SNY": "SNY.SportsNet.New.York.HD.us2",
"MASN": "MASN.-.Mid.Atlantic.Sports.Network.us2",
"YES": "Yes.Network.us2",
"MSG": "MSG.National.us2",
"REELZ USA": "ReelzChannel.HD.us2",
"ABCNY USA": "ABC.(WABC).New.York,.NY.us",
"ABC NY USA": "ABC.(WABC).New.York,.NY.us", # handle space variant
"OWN USA": "Oprah.Winfrey.Network.HD.us2",
"DISCOVERY USA": "Discovery.Channel.ca2",
"OUTDOOR CHANNEL": "Outdoor.Channel.HD.us2",
"OUTDOOR CHANNEL USA": "Outdoor.Channel.HD.us2",
"LOITV": "Soccer.Dummy.us",
"NBCNY USA" : "WNBC-DT.us_locals1",
"NBC USA" : "WNBC-DT.us_locals1",
}
# Precompute normalized hardcoded map for fast comparisons
normalized_hardcoded = {normalize_name(k): v for k, v in hardcoded_fixes.items()}
for i, line in enumerate(lines):
if line.startswith("#EXTINF"):
url = lines[i+1] if i+1 < len(lines) else ""
extinf = line
# Extract tvg-id and channel name (same as base)
tvg_match = re.search(r'tvg-id="([^"]*)"', extinf)
tvg_id = tvg_match.group(1) if tvg_match else None
name_match = re.search(r",(.*)", extinf)
channel_name = name_match.group(1).strip() if name_match else ""
# Extract group
group_match = re.search(r'group-title="([^"]*)"', extinf)
group_title = group_match.group(1).upper().strip() if group_match else ""
# --- 24/7 CHANNELS ---
if group_title and "24/7" in group_title:
if tvg_id == "test" or not tvg_id:
new_ext = insert_tvg_id(extinf, "Info.Guide.Dummy.us")
log.append(f"ℹ️ 24/7 | {channel_name} β†’ Info.Guide.Dummy.us")
else:
new_ext = extinf
out_247_blocks.append((new_ext, url))
# --- EVENTS ---
elif group_title and "EVENTS" in group_title:
# candidate extraction: prefer bracketed network name if present
bracket_match = re.search(r'\[([^\]]+)\]', channel_name)
if bracket_match:
candidate_raw = bracket_match.group(1).strip()
else:
# fallback to entire display string
candidate_raw = channel_name
candidate_norm = normalize_name(candidate_raw)
# 1) Check hardcoded fixes FIRST using normalized keys
matched_fix = False
if candidate_norm:
# exact normalized match
if candidate_norm in normalized_hardcoded:
fixed_id = normalized_hardcoded[candidate_norm]
new_ext = insert_tvg_id(extinf, fixed_id)
new_ext = apply_event_special_cases(new_ext, candidate_raw)
out_events_blocks.append((new_ext, url))
log.append(f"βœ… EVENTS | {candidate_raw} β†’ {fixed_id} (hardcoded exact)")
matched_fix = True
else:
# sometimes bracket may be abbreviation (e.g. BTN), so check startswith any key
for key_norm, fixed_id in normalized_hardcoded.items():
# if candidate begins with the key (normalized), or key appears within candidate_norm
if candidate_norm.startswith(key_norm) or key_norm in candidate_norm:
new_ext = insert_tvg_id(extinf, fixed_id)
new_ext = apply_event_special_cases(new_ext, candidate_raw)
out_events_blocks.append((new_ext, url))
log.append(f"βœ… EVENTS | {candidate_raw} β†’ {fixed_id} (hardcoded partial)")
matched_fix = True
break
if matched_fix:
continue
# 2) Try matching against JSON (same behavior as base)
match = None
for key, data in channel_map.items():
if data.get("tvg_id") and key.lower() in candidate_raw.lower():
match = data["tvg_id"]
break
if not match:
keys = list(channel_map.keys())
guesses = get_close_matches(candidate_raw, keys, n=1, cutoff=0.6)
if guesses:
match = channel_map[guesses[0]].get("tvg_id")
if match:
new_ext = insert_tvg_id(extinf, match)
log.append(f"βœ… EVENTS | {candidate_raw} β†’ {match}")
else:
new_ext = insert_tvg_id(extinf, "Live.Event.us")
log.append(f"⚠️ EVENTS | {candidate_raw} β†’ Live.Event.us (fallback)")
new_ext = apply_event_special_cases(new_ext, candidate_raw)
out_events_blocks.append((new_ext, url))
# Build output playlists (unchanged formatting)
out_247 = "\n".join([f"{ext}\n{url}" for ext, url in out_247_blocks])
out_events = "\n".join([f"{ext}\n{url}" for ext, url in out_events_blocks])
return out_247, out_events, "\n".join(log)
# Gradio UI with requested sizing (small input, larger log)
def run_app(m3u_text):
out_247, out_events, log = process_m3u(m3u_text)
file_247 = random_filename(uppercase=True)
file_events = random_filename(uppercase=False)
with open(file_247, "w", encoding="utf-8") as f:
f.write(out_247)
with open(file_events, "w", encoding="utf-8") as f:
f.write(out_events)
return file_247, file_events, log
iface = gr.Interface(
fn=run_app,
inputs=gr.Textbox(lines=5, placeholder="Paste your M3U playlist here..."), # smaller input
outputs=[
gr.File(label="24/7 Playlist"),
gr.File(label="Events Playlist"),
gr.Textbox(label="Log", lines=30), # larger log
],
title="Project 1 Playlist Processor",
description="Splits 24/7 and Events playlists, applies JSON tvg-id mappings, adds hardcoded + special rules, and outputs two clean M3Us."
)
if __name__ == "__main__":
iface.launch()