import os
import re
import requests
import xml.etree.ElementTree as ET
from xml.dom import minidom
from datetime import datetime, timezone
import warnings
TARGET_CHANNELS = [
"NBA TV", "NFL Network", "Unbeaten", "MLB Network", "PLL Network",
"PowerSports World", "Boxing TV", "Matchroom Boxing", "PDC Darts",
"Billiard TV", "ACL Cornhole TV"
]
def sanitize_id(name: str) -> str:
base = name.strip().split(".", 1)[0]
base_clean = re.sub(r'[^A-Za-z0-9]', '', base)
return f"{base_clean}.de"
def format_xmltv_time(iso_str: str) -> str:
try:
dt = datetime.fromisoformat(iso_str.replace("Z", "+00:00"))
if dt.tzinfo is not None:
dt = dt.astimezone(timezone.utc)
else:
dt = dt.replace(tzinfo=timezone.utc)
return dt.strftime("%Y%m%d%H%M%S") + " +0000"
except Exception:
return ""
def extract_target_tiles(epg_data):
found = {}
def match_title(title):
return any(title.strip().lower() == ch.lower() for ch in TARGET_CHANNELS)
if isinstance(epg_data, dict):
if "Tiles" in epg_data and isinstance(epg_data["Tiles"], list):
for tile in epg_data["Tiles"]:
title = (tile.get("Title") or tile.get("Name") or "").strip()
if title and match_title(title):
found[title] = tile
for val in epg_data.values():
if isinstance(val, list):
for entry in val:
if not isinstance(entry, dict):
continue
title = (entry.get("Title") or entry.get("Name") or "").strip()
if title and match_title(title):
found[title] = entry
elif isinstance(epg_data, list):
for entry in epg_data:
if not isinstance(entry, dict):
continue
title = (entry.get("Title") or entry.get("Name") or "").strip()
if title and match_title(title):
found[title] = entry
return list(found.values())
f'https://rail-router.discovery.indazn.com/eu/v10/Rail'
f'?platform=web&id=Livetvschedule&country={country}&languageCode={country}'
)
resp = requests.get(url, headers=headers, proxies=proxies, verify=False, timeout=10)
resp.raise_for_status()
return resp.json()
def build_xmltv(epg_data):
tiles = extract_target_tiles(epg_data)
if not tiles:
print("No matching target channels found in EPG JSON.")
return
tv = ET.Element("tv", {"generator-info-name": "dazn_epg_from_json"})
channel_map = {}
for tile in tiles:
title = (tile.get("Title") or tile.get("Name") or "").strip()
if not title:
continue
ch_id = sanitize_id(title)
channel_map[title] = ch_id
ch_elem = ET.SubElement(tv, "channel", {"id": ch_id})
dn = ET.SubElement(ch_elem, "display-name")
dn.text = title
desc = tile.get("Description") or tile.get("LongDescription") or tile.get("ShortDescription")
if desc:
d = ET.SubElement(ch_elem, "desc")
d.text = desc
for tile in tiles:
title = (tile.get("Title") or tile.get("Name") or "").strip()
ch_id = channel_map.get(title)
if not ch_id:
continue
linear = tile.get("LinearSchedule", {}) or {}
programs = []
if linear.get("Now"):
programs.append(linear["Now"])
if linear.get("Next"):
programs.append(linear["Next"])
if isinstance(linear.get("Later"), list):
programs.extend(linear["Later"])
if not programs:
for key in ("Programs", "Programmes", "Items", "Entries"):
alt = tile.get(key)
if isinstance(alt, list):
programs.extend(alt)
for prog in programs:
if not isinstance(prog, dict):
continue
prog_title = (prog.get("Title") or "").strip()
episode = (prog.get("EpisodeTitle") or "").strip()
full_title = prog_title if not episode else f"{prog_title} - {episode}"
desc = (prog.get("Description") or "").strip()
start = prog.get("Start")
end = prog.get("End")
if not start or not end:
continue
start_fmt = format_xmltv_time(start)
end_fmt = format_xmltv_time(end)
if not start_fmt or not end_fmt:
continue
p_attrib = {"start": start_fmt, "stop": end_fmt, "channel": ch_id}
p_elem = ET.SubElement(tv, "programme", p_attrib)
t_elem = ET.SubElement(p_elem, "title")
t_elem.text = full_title or prog_title or "Unknown"
if desc:
d_elem = ET.SubElement(p_elem, "desc")
d_elem.text = desc
genres = prog.get("Genre") or prog.get("Genres") or []
if isinstance(genres, list):
for g in genres:
if isinstance(g, dict):
name = g.get("name") or g.get("Name")
else:
name = str(g)
if name:
c = ET.SubElement(p_elem, "category")
c.text = name
elif isinstance(genres, str) and genres.strip():
c = ET.SubElement(p_elem, "category")
c.text = genres.strip()
year = prog.get("EventYear") or prog.get("Year")
if year:
y = ET.SubElement(p_elem, "year")
y.text = str(year)
rough = ET.tostring(tv, encoding="utf-8")
reparsed = minidom.parseString(rough)
pretty = reparsed.toprettyxml(indent=" ")
try:
base_dir = os.path.dirname(os.path.abspath(__file__))
target_dir = os.path.abspath(os.path.join(base_dir, "..", "manager", "temp"))
os.makedirs(target_dir, exist_ok=True)
target_path = os.path.join(target_dir, "daznepg.xml")
with open(target_path, "w", encoding="utf-8") as f:
f.write(pretty)
try:
import pwd, grp
uid = pwd.getpwnam("http").pw_uid
gid = grp.getgrnam("http").gr_gid
os.chown(target_path, uid, gid)
except Exception as e:
print(f"Warning: could not chown to http:http: {e}")
try:
os.chmod(target_path, 0o755)
except Exception as e:
print(f"Warning: could not chmod 755: {e}")
print(f"Written XMLTV EPG to {target_path} (owner http:http, mode 755 attempted)")
except Exception as e:
print(f"Failed writing XMLTV: {e}")
def main():
if __name__ == "__main__":
main()