from dataclasses import dataclass, field from src.api import Timetable, Stop import fire import cv2 from PIL import ImageFont, ImageDraw, Image import numpy as np import time 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"}, } @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), } ) text_colors: dict = field( default_factory=lambda: { "title": "#899194", "column_title": "#555A5C", "default": "#899194" } ) margins: tuple = (60, 60, 60, 60) # t, b, l, r 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 __name__ == "__main__": fire.Fire(main)