All checks were successful
Build TV app / build (push) Successful in 43s
Defects. "Kopplung zurücksetzen" now asks first, naming how many phones would have to scan again — one stray OK used to wipe a working pairing, and the settings put the focus right on it: entering the screen landed on the gear, one press down jumped into the right column onto that very action, and every further press down did nothing. Focus now starts on the first row of the left column. 1. FC Köln was labelled "EFC". An unreachable source used to look like an evening without programmes — cached list, empty EPG, club menus counting down to zero — so both repositories now record when they last came through, and the top bar says "Server nicht erreichbar · Stand 14:46". The field labels on "Erweitert" sat on the outline with the border running through them; they sit above their field now. Polish. The app opens on the channel list — on the channel last watched, which survives a restart — instead of the settings gear. Rows without EPG fill their empty half with the channel's group. The club chip says "ab 15:06" like the row next to it, the club menu carries the club's name instead of "FCSP", and a fixture title is shortened to the fixture, so "Pokal: Nordstadt - FC St. P…" became "Nordstadt - FC St. Pauli". The settings are three columns and no longer scroll (CONTEXT.md's ten-foot rule). Features. Favourites work for a plain M3U source too, stored on the device and mirrored to the phone; they used to exist only with a Dispatcharr login. The channel keys jump the list by initial letter, left and right jump a division in the club picker, and right on a channel opens its day plan — the EPG beyond now/next, which was parsed all along and never shown. When a club match starts elsewhere the player offers to switch. The remote asked for a code "unten auf dem TV-Bildschirm" where none was; the welcome screen shows it now and the wording points at where it is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
116 lines
5.0 KiB
Python
Executable File
116 lines
5.0 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")
|
|
p.add_argument("--now", default=None,
|
|
help="base time as YYYY-mm-ddTHH:MM:SS; defaults to this "
|
|
"machine's clock. An emulator restored from a snapshot "
|
|
"runs on the clock it was saved with, so pass its time "
|
|
"here or every programme lands in the wrong hour")
|
|
args = p.parse_args()
|
|
|
|
if args.now:
|
|
now = datetime.datetime.strptime(args.now, "%Y-%m-%dT%H:%M:%S").astimezone()
|
|
else:
|
|
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}")
|