generate website

This commit is contained in:
2025-08-24 22:32:24 +02:00
parent 10df613dd7
commit ddef1be220
11 changed files with 937 additions and 260 deletions

296
main.py
View File

@@ -1,271 +1,47 @@
from dataclasses import dataclass, field
from src.api import Timetable, Stop
from flask import Flask
import fire
import cv2
from PIL import ImageFont, ImageDraw, Image
import numpy as np
import time
from htpy import body, h1, head, html, title, table, tr, td, div, script
badge_colors = {
"S1": {"fill": "#18bae7", "text": "#C6D3D7"},
"S2": {"fill": "#74b72b", "text": "#C6D3D7"},
"S3": {"fill": "#941d81", "text": "#C6D3D7"},
"S4": {"fill": "#e20b1c", "text": "#C6D3D7"},
"S5": {"fill": "#005280", "text": "#C6D3D7"},
"S6": {"fill": "#008c59", "text": "#C6D3D7"},
"S7": {"fill": "#892f24", "text": "#C6D3D7"},
"S8": {"fill": "#0d0d11", "text": "#eeaa00"},
"S20": {"fill": "#e8526d", "text": "#C6D3D7"},
}
app = Flask(__name__)
@dataclass
class ImageSetup:
width: int = 1920
height: int = 1080
bg_color: str = "#282a35"
fonts: dict = field(
default_factory=lambda: {
"title": ("./fonts/Rubik-Bold.ttf", 80),
"badge": ("./fonts/Rubik-SemiBold.ttf", 70),
"column_title": ("./fonts/Rubik-Regular.ttf", 50),
"default": ("./fonts/Rubik-Regular.ttf", 60),
}
def run_server(debug=False):
app.run(debug=debug)
@app.route("/")
def main_page():
return build_page(None)
def build_page(tt, debug=True):
_ = tt
from datetime import datetime
lines = (
("S1", "somewhere", datetime.now().strftime(r"%H:%M:%S"), 4, "3 min"),
("S2", "else", datetime.now().strftime(r"%H:%M:%S"), 4, "3 min"),
)
text_colors: dict = field(
default_factory=lambda: {
"title": "#899194",
"column_title": "#555A5C",
"default": "#899194"
}
)
margins: tuple = (60, 60, 60, 60) # t, b, l, r
retval = html[
head[title["today's menu"]],
body[
div(style="clear: both")[
h1(style="float: left")["Gröbenzell"],
h1(style="float: right", id="clock")[datetime.now().strftime(r"%H:%M:%S")],
],
table[
(tr[
(td[element] for element in line)
] for line in lines)
],
]
]
retval = str(retval)
def denormalize(coords, setup: ImageSetup, use_margins=True):
(x, y) = coords
margins = setup.margins if use_margins else (0, 0, 0, 0)
return (
int(x * (setup.width - margins[2] - margins[3]) + margins[2]),
int(y * (setup.height - margins[0] - margins[1]) + margins[0]),
)
def create_image(tt: Timetable, setup: ImageSetup | None = None):
if setup is None:
setup = ImageSetup()
# img = np.zeros((setup.height, setup.width, 3), dtype=np.uint8)
img = Image.new("RGBA", (setup.width, setup.height), setup.bg_color)
# # fill with background color
# for i, c in enumerate(hex2color(setup.bg_color)):
# img[:, :, i] = int(c * 255)
### setup done ###
draw = ImageDraw.Draw(img)
title_font = ImageFont.truetype(*setup.fonts["title"])
ctitle_font = ImageFont.truetype(*setup.fonts["column_title"])
badge_font = ImageFont.truetype(*setup.fonts["badge"])
default_font = ImageFont.truetype(*setup.fonts["default"])
draw.text(
xy=denormalize((0, 0), setup),
text=tt.station,
font=title_font,
anchor="la",
fill=setup.text_colors["title"]
)
draw.text(
xy=denormalize((0.815, 0), setup),
text=tt.timestamp.strftime(r"%H:%M:%S"),
font=title_font,
anchor="la",
fill=setup.text_colors["title"]
)
line_badge_size = (2*setup.fonts["badge"][1], setup.fonts["badge"][1])
line_height = setup.fonts["badge"][1]*1.75
xy_start = denormalize((0,0.15), setup)
dest_x = denormalize((0.12, 0), setup)[0]
dep_x = denormalize((0.5, 0), setup)[0]
delay_x = denormalize((0.6, 0), setup)[0]
max_dest_len = dep_x - dest_x - 60
until_x = denormalize((0.5+0.5-0.12, 0), setup)[0]
# col_title_y = denormalize((0, 0.15), setup)[1]
# draw.text(
# xy=(dest_x, col_title_y),
# text="Richtung",
# font=ctitle_font,
# anchor="lm",
# fill=setup.text_colors["column_title"],
# )
# draw.text(
# xy=(dep_x, col_title_y),
# text="Zeit",
# font=ctitle_font,
# anchor="lm",
# fill=setup.text_colors["column_title"],
# )
# draw.text(
# xy=(until_x, col_title_y),
# text="fährt in",
# font=ctitle_font,
# anchor="lm",
# fill=setup.text_colors["column_title"],
# )
current_xys_line_badge = [*xy_start, xy_start[0]+line_badge_size[0], xy_start[1]+line_badge_size[1]]
for stop in tt.stops[:min(tt.min_stop_count, len(tt.stops), 7)]:
stop: Stop = stop
draw.rounded_rectangle(
current_xys_line_badge,
radius=min(current_xys_line_badge[2]-current_xys_line_badge[0], current_xys_line_badge[3]-current_xys_line_badge[1])//2,
fill=badge_colors[stop.line]["fill"],
outline=badge_colors[stop.line]["fill"],
width=10
)
center_xy = ((current_xys_line_badge[0]+current_xys_line_badge[2])//2, (current_xys_line_badge[1]+current_xys_line_badge[3])//2)
draw.text(
xy=center_xy,
text=stop.line,
font=badge_font,
anchor="mm",
fill=badge_colors[stop.line]["text"],
)
dest_text :str = stop.destination
dest_text = dest_text.replace("München-", "")
dest_text = dest_text.replace("München ", "")
paren_index = dest_text.find("(")
if paren_index != -1:
dest_text = dest_text[:paren_index]
cutoff = 0
while default_font.getlength(dest_text) > max_dest_len:
dest_text = (dest_text[:len(dest_text)-cutoff+1]+ ("." if cutoff > 0 else "")).replace(" .", "")
cutoff += 1
dep_text = stop.departure_time.strftime(r"%H:%M")
time_until = round((stop.departure_time - tt.timestamp).total_seconds()/60)
until_text = f"{time_until:d} min"
until_color = setup.text_colors["default"]
delay_color = setup.text_colors["column_title"]
dep_color = setup.text_colors["default"]
dest_color = setup.text_colors["default"]
delay_text = ""
if stop.departure_delay is not None:
delay_text = f"{"+" if stop.departure_delay >= 0 else "-"} {abs(stop.departure_delay):d}"
if stop.departure_delay > 5:
delay_color = "#c8683f"
elif stop.departure_delay < 0:
delay_color = "#48c144"
if time_until <= 6:
until_color = "#c8683f"
elif 6 < time_until <= 10:
until_color = "#b79b46"
if stop.departure_canceled:
dep_color = "#c83f4e"
dest_color = "#c83f4e"
dep_text = "Fällt aus :("
until_text = "----"
delay_text = ""
draw.text(
xy=(dest_x, center_xy[1]),
text=dest_text,
font=default_font,
anchor="lm",
fill=dest_color,
)
draw.text(
xy=(dep_x, center_xy[1]),
text=dep_text,
font=default_font,
anchor="lm",
fill=dep_color,
)
draw.text(
xy=(delay_x, center_xy[1]),
text=delay_text,
font=ctitle_font,
anchor="lm",
fill=delay_color,
)
draw.text(
xy=(until_x, center_xy[1]),
text=until_text,
font=default_font,
anchor="lm",
fill=until_color,
)
current_xys_line_badge[1] += line_height
current_xys_line_badge[3] += line_height
# if gif is not None:
# gif = Image.open(gif)
# gif.seek(gif_frame % gif.n_frames)
# frame = gif.convert("RGBA")
# frame_size = frame.size
# frame_anchor = denormalize((1, 1), setup, use_margins=False)
# img.paste(frame, (frame_anchor[0]-frame_size[0], frame_anchor[1]-frame_size[1]), frame)
# last step, convert from rgb to bgr
img = img.convert("RGB")
img = np.array(img)
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
return img
def main(eva=None):
eva = 8002377 if eva is None else eva
# eva = 8098263
# eva = 8004158
cv2.namedWindow("fs", cv2.WND_PROP_FULLSCREEN)
cv2.setWindowProperty("fs", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
# cv2.moveWindow("fs", -1920, 0)
tt = Timetable(eva=eva)
while True:
start = time.time()
tt.get_stops()
img = create_image(tt)
cv2.imshow("fs", img)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
elapsed = time.time() - start
time.sleep(max(0, 1.0 - elapsed))
cv2.destroyAllWindows()
if debug:
print(retval)
return retval
if __name__ == "__main__":
fire.Fire(main)
fire.Fire(run_server)