Update app.py
Browse files
app.py
CHANGED
|
@@ -1,124 +1,109 @@
|
|
| 1 |
import json
|
| 2 |
-
import requests
|
| 3 |
import gzip
|
| 4 |
-
import io
|
| 5 |
-
import xml.etree.ElementTree as ET
|
| 6 |
-
import gradio as gr
|
| 7 |
import re
|
| 8 |
import difflib
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
-
#
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
root = ET.fromstring(xml_data)
|
| 28 |
-
epg_map = {}
|
| 29 |
-
for channel in root.findall("channel"):
|
| 30 |
-
cid = channel.get("id")
|
| 31 |
-
display_name = channel.findtext("display-name")
|
| 32 |
-
if cid:
|
| 33 |
-
epg_map[cid.strip()] = cid.strip()
|
| 34 |
-
if display_name:
|
| 35 |
-
epg_map[display_name.strip()] = cid.strip() if cid else None
|
| 36 |
-
return epg_map
|
| 37 |
-
|
| 38 |
-
# ---------------- Matching Helpers ----------------
|
| 39 |
def normalize(s):
|
| 40 |
return re.sub(r'[^a-z0-9]', '', s.lower())
|
| 41 |
|
| 42 |
-
def
|
| 43 |
-
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
|
| 50 |
-
name_norm = normalize(ch_name)
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
| 55 |
|
| 56 |
-
|
| 57 |
-
if name_norm in epg_norm_map:
|
| 58 |
-
return epg_norm_map[name_norm]
|
| 59 |
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
| 64 |
|
| 65 |
-
|
|
|
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
|
| 74 |
-
updated
|
| 75 |
-
|
|
|
|
| 76 |
|
| 77 |
-
|
| 78 |
-
group = ch_data.get("group_title", "").upper()
|
| 79 |
-
old_id = ch_data.get("tvg_id", "").strip()
|
| 80 |
-
new_id = None
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
-
|
| 86 |
-
new_id = find_best_match(old_id, ch_name, ca_epg)
|
| 87 |
|
| 88 |
-
|
| 89 |
-
if new_id:
|
| 90 |
-
ch_data["tvg_id"] = new_id
|
| 91 |
-
if group in ["UNITED STATES", "CANADA"]:
|
| 92 |
-
log_lines.append(f"✅ {group} | {ch_name} → {new_id}")
|
| 93 |
-
else:
|
| 94 |
-
if group in ["UNITED STATES", "CANADA"]:
|
| 95 |
-
log_lines.append(f"⚠️ {group} | {ch_name} (no match, kept {old_id})")
|
| 96 |
-
|
| 97 |
-
updated[ch_name] = ch_data
|
| 98 |
-
|
| 99 |
-
# Write output
|
| 100 |
-
updated_file = "/tmp/channels_updated.json"
|
| 101 |
-
with open(updated_file, "w", encoding="utf-8") as f:
|
| 102 |
-
json.dump(updated, f, indent=4, ensure_ascii=False)
|
| 103 |
-
|
| 104 |
-
# Sort log: show all ✅ first, ⚠️ unmatched at bottom
|
| 105 |
-
fixed = [l for l in log_lines if l.startswith("✅")]
|
| 106 |
-
unmatched = [l for l in log_lines if l.startswith("⚠️")]
|
| 107 |
-
log_text = "\n".join(fixed + ["", "---- Unmatched ----"] + unmatched if unmatched else fixed)
|
| 108 |
-
|
| 109 |
-
return updated_file, log_text
|
| 110 |
-
|
| 111 |
-
# ---------------- Gradio UI ----------------
|
| 112 |
-
demo = gr.Interface(
|
| 113 |
-
fn=fix_channels,
|
| 114 |
-
inputs=[],
|
| 115 |
-
outputs=[
|
| 116 |
-
gr.File(label="Download Updated channels.json"),
|
| 117 |
-
gr.Textbox(label="Log (US & Canada only)", lines=25, interactive=False)
|
| 118 |
-
],
|
| 119 |
-
title="Project 2: Channels.json Updater",
|
| 120 |
-
description="Updates tvg_id for UNITED STATES and CANADA channels using new EPG XMLs. Uses fuzzy matching to handle naming differences (e.g., TSN1 ⇔ TSN.1). Outputs a single updated file + log."
|
| 121 |
-
)
|
| 122 |
-
|
| 123 |
-
if __name__ == "__main__":
|
| 124 |
-
demo.launch()
|
|
|
|
| 1 |
import json
|
|
|
|
| 2 |
import gzip
|
|
|
|
|
|
|
|
|
|
| 3 |
import re
|
| 4 |
import difflib
|
| 5 |
+
import requests
|
| 6 |
+
import xml.etree.ElementTree as ET
|
| 7 |
+
import gradio as gr
|
| 8 |
|
| 9 |
+
# --- Sources ---
|
| 10 |
+
CHANNELS_URL = "https://raw.githubusercontent.com/pigzillaaa/daddylive/main/channels.json"
|
| 11 |
+
US_EPG_URL = "https://epgshare01.online/epgshare01/epg_ripper_US2.xml.gz"
|
| 12 |
+
US_LOCALS_URL = "https://epgshare01.online/epgshare01/epg_ripper_US_LOCALS2.xml.gz"
|
| 13 |
+
CA_EPG_URL = "https://epgshare01.online/epgshare01/epg_ripper_CA2.xml.gz"
|
| 14 |
+
|
| 15 |
+
# --- Manual Rules (learned fixes) ---
|
| 16 |
+
RULES = {
|
| 17 |
+
"A&E USA": "A.and.E.HD.East.us2",
|
| 18 |
+
"AMC USA": "AMC.HD.us2",
|
| 19 |
+
"Adult Swim": "AdultSwim.com.Cartoon.Network.us2",
|
| 20 |
+
"BBC America (BBCA)": "BBC.America.HD.us2",
|
| 21 |
+
"BBC News Channel HD": "BBC.News.(North.America).HD.us2",
|
| 22 |
+
"BET USA": "BET.HD.us2",
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
# --- Helpers ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
def normalize(s):
|
| 27 |
return re.sub(r'[^a-z0-9]', '', s.lower())
|
| 28 |
|
| 29 |
+
def load_epg_map(url):
|
| 30 |
+
try:
|
| 31 |
+
r = requests.get(url, timeout=20)
|
| 32 |
+
r.raise_for_status()
|
| 33 |
+
with gzip.decompress(r.content) as f:
|
| 34 |
+
pass
|
| 35 |
+
except Exception:
|
| 36 |
+
f = gzip.decompress(r.content)
|
| 37 |
+
root = ET.fromstring(f)
|
| 38 |
+
return {ch.attrib.get("id", ""): ch.attrib.get("id", "") for ch in root.findall("channel")}
|
| 39 |
+
|
| 40 |
+
def build_epg():
|
| 41 |
+
us = load_epg_map(US_EPG_URL)
|
| 42 |
+
us_locals = load_epg_map(US_LOCALS_URL)
|
| 43 |
+
ca = load_epg_map(CA_EPG_URL)
|
| 44 |
+
# Merge maps
|
| 45 |
+
epg = {**us, **us_locals, **ca}
|
| 46 |
+
return epg
|
| 47 |
|
| 48 |
+
def find_best_match(old_id, ch_name, epg_map):
|
| 49 |
+
# Rule-based override first
|
| 50 |
+
if ch_name in RULES:
|
| 51 |
+
return RULES[ch_name]
|
| 52 |
+
|
| 53 |
+
# Normalize input
|
| 54 |
+
old_norm, name_norm = normalize(old_id), normalize(ch_name)
|
| 55 |
+
norm_map = {normalize(k): v for k, v in epg_map.items() if k}
|
| 56 |
+
|
| 57 |
+
# Try old_id
|
| 58 |
+
if old_norm in norm_map:
|
| 59 |
+
return norm_map[old_norm]
|
| 60 |
+
# Try channel name
|
| 61 |
+
if name_norm in norm_map:
|
| 62 |
+
return norm_map[name_norm]
|
| 63 |
+
|
| 64 |
+
# Fuzzy matching
|
| 65 |
+
all_norm_keys = list(norm_map.keys())
|
| 66 |
+
for candidate in difflib.get_close_matches(name_norm, all_norm_keys, n=1, cutoff=0.8):
|
| 67 |
+
return norm_map[candidate]
|
| 68 |
|
| 69 |
+
return None
|
|
|
|
| 70 |
|
| 71 |
+
# --- Main Update ---
|
| 72 |
+
def update_channels():
|
| 73 |
+
r = requests.get(CHANNELS_URL)
|
| 74 |
+
data = json.loads(r.text)
|
| 75 |
|
| 76 |
+
epg_map = build_epg()
|
|
|
|
|
|
|
| 77 |
|
| 78 |
+
log = []
|
| 79 |
+
for ch_name, info in data.items():
|
| 80 |
+
group = info.get("group_title", "")
|
| 81 |
+
if group not in ["UNITED STATES", "CANADA"]:
|
| 82 |
+
continue
|
| 83 |
|
| 84 |
+
old_id = info.get("tvg_id", "")
|
| 85 |
+
new_id = find_best_match(old_id, ch_name, epg_map)
|
| 86 |
|
| 87 |
+
if new_id:
|
| 88 |
+
if new_id != old_id:
|
| 89 |
+
info["tvg_id"] = new_id
|
| 90 |
+
log.append(f"✅ {group} | {ch_name} → {new_id}")
|
| 91 |
+
else:
|
| 92 |
+
log.append(f"⚠️ {group} | {ch_name} (no match, kept {old_id})")
|
| 93 |
|
| 94 |
+
# Save updated JSON
|
| 95 |
+
with open("channels_updated.json", "w") as f:
|
| 96 |
+
json.dump(data, f, indent=4)
|
| 97 |
|
| 98 |
+
return "\n".join(log), "channels_updated.json"
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
+
# --- Gradio UI ---
|
| 101 |
+
with gr.Blocks() as demo:
|
| 102 |
+
gr.Markdown("## Project 2: Update Channels JSON with New EPG IDs")
|
| 103 |
+
run_btn = gr.Button("Run Update")
|
| 104 |
+
log_out = gr.Textbox(label="Update Log", lines=25)
|
| 105 |
+
file_out = gr.File(label="Download Updated JSON")
|
| 106 |
|
| 107 |
+
run_btn.click(fn=update_channels, outputs=[log_out, file_out])
|
|
|
|
| 108 |
|
| 109 |
+
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|