shvchenko commited on
Commit
da8bea4
·
verified ·
1 Parent(s): 6fd3e73

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -104
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
- # URLs
11
- CHANNELS_JSON_URL = "https://raw.githubusercontent.com/pigzillaaa/daddylive/main/channels.json"
12
- EPG_US_URL = "https://epgshare01.online/epgshare01/epg_ripper_US2.xml.gz"
13
- EPG_CA_URL = "https://epgshare01.online/epgshare01/epg_ripper_CA2.xml.gz"
14
-
15
- # ---------------- Fetch + Parse Helpers ----------------
16
- def fetch_json(url):
17
- r = requests.get(url, timeout=15)
18
- r.raise_for_status()
19
- return r.json()
20
-
21
- def fetch_epg(url):
22
- r = requests.get(url, timeout=20)
23
- r.raise_for_status()
24
- buf = io.BytesIO(r.content)
25
- with gzip.open(buf, "rb") as f:
26
- xml_data = f.read()
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 find_best_match(old_id, ch_name, epg_map):
43
- if not old_id and not ch_name:
44
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
- # Build normalized map
47
- epg_norm_map = {normalize(k): v for k, v in epg_map.items() if k}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
- old_norm = normalize(old_id)
50
- name_norm = normalize(ch_name)
51
 
52
- # Try normalized id
53
- if old_norm in epg_norm_map:
54
- return epg_norm_map[old_norm]
 
55
 
56
- # Try normalized name
57
- if name_norm in epg_norm_map:
58
- return epg_norm_map[name_norm]
59
 
60
- # Fuzzy match on normalized values
61
- all_norm_keys = list(epg_norm_map.keys())
62
- for candidate in difflib.get_close_matches(name_norm, all_norm_keys, n=1, cutoff=0.85):
63
- return epg_norm_map[candidate]
 
64
 
65
- return None
 
66
 
67
- # ---------------- Main Processing ----------------
68
- def fix_channels():
69
- # Load sources
70
- channels = fetch_json(CHANNELS_JSON_URL)
71
- us_epg = fetch_epg(EPG_US_URL)
72
- ca_epg = fetch_epg(EPG_CA_URL)
73
 
74
- updated = {}
75
- log_lines = []
 
76
 
77
- for ch_name, ch_data in channels.items():
78
- group = ch_data.get("group_title", "").upper()
79
- old_id = ch_data.get("tvg_id", "").strip()
80
- new_id = None
81
 
82
- if group == "UNITED STATES":
83
- new_id = find_best_match(old_id, ch_name, us_epg)
 
 
 
 
84
 
85
- elif group == "CANADA":
86
- new_id = find_best_match(old_id, ch_name, ca_epg)
87
 
88
- # Apply update if found
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()