All checks were successful
Build TV app / build (push) Successful in 2m53s
The club chooser was a flat list of 56 names in a generic picker dialog, which is unreadable from a couch. It now has its own dialog with a crest per row, the short label on the right, and section headers per division. Enabled clubs in the settings card show their crest too. A search field would have been the obvious alternative, but text entry on a remote is exactly what the Ten-Foot rule rules out, so grouping plus crests carries the recognition instead. Also adds tests/demo, a generator for neutral playlist and EPG data so screenshots never carry real channel names. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
108 lines
4.6 KiB
Python
Executable File
108 lines
4.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Neutral demo line-up for screenshots and manual testing.
|
|
|
|
Invented channels and programmes only — no real provider data ever ends up
|
|
in a screenshot. One club match is placed a few minutes ahead so the club
|
|
menu shows its "kick-off imminent" state.
|
|
"""
|
|
import argparse
|
|
import datetime
|
|
import os
|
|
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument("--out", default=os.path.dirname(os.path.abspath(__file__)))
|
|
p.add_argument("--kickoff-in", type=int, default=18,
|
|
help="minutes until the club match starts")
|
|
args = p.parse_args()
|
|
|
|
now = datetime.datetime.now().astimezone()
|
|
|
|
|
|
def fmt(dt):
|
|
# Local offset, so the emulator's clock agrees with ours (CEST vs CET).
|
|
return dt.strftime("%Y%m%d%H%M%S %z")
|
|
|
|
|
|
CHANNELS = [
|
|
("Unterhaltung", "Blau TV HD", "blau"),
|
|
("Unterhaltung", "Nordlicht HD", "nordlicht"),
|
|
("Unterhaltung", "Kanal Sieben", "sieben"),
|
|
("Filme", "Kinohaus HD", "kinohaus"),
|
|
("Filme", "Filmwerk Classic", "filmwerk"),
|
|
("Filme", "Nachtkino HD", "nachtkino"),
|
|
("Serien", "Serienbox HD", "serienbox"),
|
|
("Serien", "Staffel Eins", "staffel"),
|
|
("Sport", "Arena Sport 1 HD", "arena1"),
|
|
("Sport", "Arena Sport 2 HD", "arena2"),
|
|
("Sport", "Liga Live UHD", "ligalive"),
|
|
("Sport", "Stadionkanal HD", "stadion"),
|
|
("Nachrichten", "Tagesblick HD", "tagesblick"),
|
|
("Nachrichten", "Weltbericht", "weltbericht"),
|
|
("Doku & Wissen", "Terra Doku HD", "terra"),
|
|
("Doku & Wissen", "Wissenswelt", "wissen"),
|
|
("Kinder", "Krümelkiste", "kruemel"),
|
|
("Musik", "Tonspur HD", "tonspur"),
|
|
("Regional", "Hafenkanal HD", "hafen"),
|
|
("International", "Globus TV", "globus"),
|
|
]
|
|
|
|
PROGRAMMES = {
|
|
"blau": [("Morgenmagazin", -40, 80), ("Kochduell", 40, 60)],
|
|
"nordlicht": [("Küstenrevier", -20, 70), ("Nordlicht Talk", 50, 45)],
|
|
"sieben": [("Quiz um Sieben", -10, 60), ("Sieben Reportage", 50, 45)],
|
|
"kinohaus": [("Der weite Weg", -55, 125), ("Sturmhöhe", 70, 110)],
|
|
"filmwerk": [("Schwarzweiß", -30, 95), ("Die Erbschaft", 65, 100)],
|
|
"nachtkino": [("Mitternachtszug", -15, 105), ("Letzte Runde", 90, 95)],
|
|
"serienbox": [("Praxis Ostwind (3/12)", -25, 45), ("Praxis Ostwind (4/12)", 20, 45)],
|
|
"staffel": [("Hafenwache (7/10)", -35, 50), ("Hafenwache (8/10)", 15, 50)],
|
|
"arena1": [("Handball: Pokalrunde", -45, 105), ("Sport am Abend", 60, 60)],
|
|
"arena2": [("Radsport: Etappe 4", -50, 130), ("Motorsport kompakt", 80, 55)],
|
|
"ligalive": [("Sport aktuell", -25, 43), ("__CLUB__", None, 120)],
|
|
"stadion": [("Stadionmagazin", -20, 60), ("FC St. Pauli: Der Rückblick", 40, 45)],
|
|
"tagesblick": [("Tagesblick am Mittag", -12, 30), ("Wetter und Verkehr", 18, 15)],
|
|
"weltbericht": [("Weltbericht Spezial", -35, 75), ("Auslandsjournal", 40, 45)],
|
|
"terra": [("Wale der Arktis", -40, 90), ("Vulkane Islands", 50, 45)],
|
|
"wissen": [("Wie Brücken halten", -20, 45), ("Chemie des Alltags", 25, 45)],
|
|
"kruemel": [("Bärenbande", -15, 25), ("Malstunde", 10, 25)],
|
|
"tonspur": [("Charts der Woche", -60, 120), ("Akustik-Session", 60, 60)],
|
|
"hafen": [("Hafenrundfahrt", -30, 60), ("Regional um sechs", 30, 30)],
|
|
"globus": [("Globus Reportage", -45, 90), ("Fernweh", 45, 60)],
|
|
}
|
|
|
|
CLUB_TITLE = "Pokal: Nordstadt - FC St. Pauli, 1. Runde"
|
|
|
|
os.makedirs(args.out, exist_ok=True)
|
|
m3u = os.path.join(args.out, "playlist.m3u")
|
|
xml = os.path.join(args.out, "epg.xml")
|
|
|
|
with open(m3u, "w", encoding="utf-8") as f:
|
|
f.write("#EXTM3U\n")
|
|
for group, name, cid in CHANNELS:
|
|
f.write(f'#EXTINF:-1 tvg-id="{cid}" group-title="{group}",{name}\n')
|
|
f.write(f"http://10.0.2.2:8099/stream/{cid}.ts\n")
|
|
|
|
|
|
def escape(text):
|
|
return text.replace("&", "&").replace("<", "<").replace(">", ">")
|
|
|
|
|
|
with open(xml, "w", encoding="utf-8") as f:
|
|
f.write('<?xml version="1.0" encoding="UTF-8"?>\n<tv>\n')
|
|
for _, name, cid in CHANNELS:
|
|
f.write(f' <channel id="{cid}"><display-name>{escape(name)}</display-name></channel>\n')
|
|
for cid, items in PROGRAMMES.items():
|
|
for title, offset, length in items:
|
|
if title == "__CLUB__":
|
|
title, offset = CLUB_TITLE, args.kickoff_in
|
|
start = now + datetime.timedelta(minutes=offset)
|
|
stop = start + datetime.timedelta(minutes=length)
|
|
f.write(
|
|
f' <programme start="{fmt(start)}" stop="{fmt(stop)}" channel="{cid}">'
|
|
f"<title>{escape(title)}</title></programme>\n"
|
|
)
|
|
f.write("</tv>\n")
|
|
|
|
print(f"{len(CHANNELS)} Sender, {sum(len(v) for v in PROGRAMMES.values())} Sendungen")
|
|
print(f"Anstoß in {args.kickoff_in} Min: {CLUB_TITLE}")
|
|
print(f"-> {m3u}\n-> {xml}")
|