From f5ff82d7293b50ec412231e65b4480d6bf206fcc Mon Sep 17 00:00:00 2001 From: "user@node5.net" Date: Tue, 23 Jun 2026 00:39:08 +0200 Subject: nixify repo, 2026 headline design, debug environment variables --- src/app.py | 69 ----------------- src/get_program_for_pixel_flut.py | 53 +++++++++++++ src/logger.py | 36 +++++++++ src/program_parser.py | 157 ++++++++++++++++++-------------------- src/static/title_headline.png | Bin 12481 -> 2302 bytes src/text_tv_bornhack.py | 47 ++++++++++++ 6 files changed, 210 insertions(+), 152 deletions(-) delete mode 100644 src/app.py create mode 100644 src/get_program_for_pixel_flut.py create mode 100644 src/logger.py create mode 100644 src/text_tv_bornhack.py (limited to 'src') diff --git a/src/app.py b/src/app.py deleted file mode 100644 index 977288b..0000000 --- a/src/app.py +++ /dev/null @@ -1,69 +0,0 @@ -import logging -import os -import datetime - -import flask -import yaml - -import program_parser - -#import db_handler - -class CustomFormatter(logging.Formatter): - grey = "\x1b[90;20m" - blue = "\x1b[34;20m" - yellow = "\x1b[33;20m" - red = "\x1b[31;20m" - bold_red = "\x1b[31;1m" - reset = "\x1b[0m" - format = "%(asctime)s,%(msecs)03d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s" - - FORMATS = { - logging.DEBUG: grey + format + reset, - logging.INFO: blue + format + reset, - logging.WARNING: yellow + format + reset, - logging.ERROR: red + format + reset, - logging.CRITICAL: bold_red + format + reset - } - - def format(self, record): - log_fmt = self.FORMATS.get(record.levelno) - formatter = logging.Formatter(log_fmt) - return formatter.format(record) - -logger = logging.getLogger(__name__) -if os.environ.get('FLASK_DEBUG') == '1': # Local development - logger.root.setLevel(logging.DEBUG) -else: # Production - logger.root.setLevel(logging.INFO) - -stream_handler = logging.StreamHandler() -stream_handler.setFormatter(CustomFormatter()) -logger.root.addHandler(stream_handler) - -app = flask.Flask(__name__, template_folder='templates', static_folder='static', static_url_path='') - -@app.route('/top') -def top(): - return f'BornHack S105 {datetime.datetime.now().strftime("%a %d %b %R:%S")} ' - -@app.route('/program.json') -def program(): - events_to_show: list[Event] = program_parser.get_events_to_show(program_parser.get_program(), programs_to_show_count = 1000) - #{event.title} {day} {start_time}-{end_time} {icon} - json = [{'title': a.title, 'day': a.day, 'time': a.time, 'progress': a.progress, 'icon': a.icon} for a in events_to_show] - return json -''' -@app.route('/news') -def news(): - with open(news_file_path, "r") as news_file: - return news_file.read().replace('\n', '
') -''' -@app.route('/text') -def text() -> dict[str, str]: - return {part: eval(part)() for part in ['top', 'program']} - -@app.route('/') -def index(): - return flask.render_template('index.html', parts=text()) - diff --git a/src/get_program_for_pixel_flut.py b/src/get_program_for_pixel_flut.py new file mode 100644 index 0000000..5557184 --- /dev/null +++ b/src/get_program_for_pixel_flut.py @@ -0,0 +1,53 @@ +import logging +import os + +import program_parser +from program_parser import Event + +class CustomFormatter(logging.Formatter): + grey = "\x1b[90;20m" + blue = "\x1b[34;20m" + yellow = "\x1b[33;20m" + red = "\x1b[31;20m" + bold_red = "\x1b[31;1m" + reset = "\x1b[0m" + format = "%(asctime)s,%(msecs)03d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s" + + FORMATS = { + logging.DEBUG: grey + format + reset, + logging.INFO: blue + format + reset, + logging.WARNING: yellow + format + reset, + logging.ERROR: red + format + reset, + logging.CRITICAL: bold_red + format + reset + } + + def format(self, record): + log_fmt = self.FORMATS.get(record.levelno) + formatter = logging.Formatter(log_fmt) + return formatter.format(record) + +logger = logging.getLogger(__name__) +if os.environ.get('FLASK_DEBUG') == '1': # Local development + logger.root.setLevel(logging.DEBUG) +else: # Production + logger.root.setLevel(logging.INFO) + +stream_handler = logging.StreamHandler() +stream_handler.setFormatter(CustomFormatter()) +logger.root.addHandler(stream_handler) + +events_to_show: list[Event] = program_parser.get_events_to_show(program_parser.get_program(), programs_to_show_count = 1000) +#{event.title} {day} {start_time}-{end_time} {icon} +programs_formatted_list = [] +last_event = None +for event in events_to_show: + programs_formatted_list.append(f"{event.title} {event.time} {event.icon} {event.progress if event.progress > 0 else ''}") + if last_event and last_event.day != event.day: + # Day change + programs_formatted_list.append(f'\n\n{event.day}\n') + last_event = event + +program_formatted = '\n'.join([f"" for a in events_to_show ]) +program_formatted = '\n'.join(programs_formatted_list) +print(program_formatted) + diff --git a/src/logger.py b/src/logger.py new file mode 100644 index 0000000..aaa1441 --- /dev/null +++ b/src/logger.py @@ -0,0 +1,36 @@ +import logging +import os + + +class CustomFormatter(logging.Formatter): + grey = "\x1b[90;20m" + blue = "\x1b[34;20m" + yellow = "\x1b[33;20m" + red = "\x1b[31;20m" + bold_red = "\x1b[31;1m" + reset = "\x1b[0m" + format = "%(asctime)s,%(msecs)03d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s" + + FORMATS = { + logging.DEBUG: grey + format + reset, + logging.INFO: blue + format + reset, + logging.WARNING: yellow + format + reset, + logging.ERROR: red + format + reset, + logging.CRITICAL: bold_red + format + reset + } + + def format(self, record): + log_fmt = self.FORMATS.get(record.levelno) + formatter = logging.Formatter(log_fmt) + return formatter.format(record) + +logger = logging.getLogger(__name__) +if os.environ.get('FLASK_DEBUG') == '1': # Local development + logger.root.setLevel(logging.DEBUG) +else: # Production + logger.root.setLevel(logging.INFO) + +stream_handler = logging.StreamHandler() +stream_handler.setFormatter(CustomFormatter()) +logger.root.addHandler(stream_handler) + diff --git a/src/program_parser.py b/src/program_parser.py index 67103b1..66c0858 100755 --- a/src/program_parser.py +++ b/src/program_parser.py @@ -1,13 +1,10 @@ -#!/run/current-system/sw/bin/python import os import json import datetime -import copy import dataclasses +import logging -input_file_path = './program.json' - -title_max_length = 34 +logger = logging.getLogger(__name__) # Set the logger name, to the name of the module @dataclasses.dataclass class Event: @@ -22,89 +19,83 @@ class Event: pretty: str = None progress: int = None -icons = { - 'Startent at Info Desk' : "⭐", - 'Speakers Tent': "🏕", - 'Workshop Room': "🛠️", - 'Bar Meetup Area': "🍺", - 'Bar Area': "🍺", - 'External': "🚗", - 'Pyjam.as Village in Party Area': "🐍", - 'Workshop Room (possibly outside of it)': "🛠️(🏞️)", - 'Workshop Room (sofa area)': "🛠️(🛋️)", - 'NWWC Village in Party Area': "🔊", - 'Wellness area': "🛀", - 'Online': "🌐", -} +class ProgramParser: + def __init__(self, program_path:os.PathLike, title_max_length:int=34, override_date:datetime.datetime|None=None): + self.program_path = program_path + self.title_max_length = title_max_length + self.override_date = override_date + self.icons = { + 'Startent at Info Desk' : "⭐", + 'Speakers Tent': "🏕", + 'Workshop Room': "🛠️", + 'Bar Meetup Area': "🍺", + 'Bar Area': "🍺", + 'External': "🚗", + 'Pyjam.as Village in Party Area': "🐍", + 'Workshop Room (possibly outside of it)': "🛠️(🏞️)", + 'Workshop Room (sofa area)': "🛠️(🛋️)", + 'NWWC Village in Party Area': "🔊", + 'Wellness area': "🛀", + 'Online': "🌐", + } -def get_program() -> list[Event]: - program: list[Event] = [] - # Parse program into data format (interpret datetime, and such) - with open(input_file_path, "r") as json_file: - program_raw = json.load(json_file) - for event_raw in program_raw: - event = Event() - #print(event_raw["title"]) - for d in ["title", "room"]: # 1 to 1 mapping - setattr(event, d, event_raw[d]) - event.start_date = datetime.datetime.strptime(event_raw["date"], "%Y-%m-%dT%H:%M:%S+00:00") - event.start_date += datetime.timedelta(hours=2) # Hvad er vinter tid? - end_bogus = datetime.datetime.strptime(event_raw["duration"],"%H:%M:%S") - event.duration = datetime.timedelta(hours=end_bogus.hour, minutes=end_bogus.minute, seconds=end_bogus.second) - event.end_date = event.start_date + event.duration - #print('Start date: ', event["start_date"]) - #print("End date: ", event["end_date"]) - #print() - program.append(event) + def get_program(self) -> list[Event]: + program: list[Event] = [] + # Parse program into data format (interpret datetime, and such) + with open(self.program_path, "r") as json_file: + program_raw = json.load(json_file) + for event_raw in program_raw: + event = Event() + #print(event_raw["title"]) + for d in ["title", "room"]: # 1 to 1 mapping + setattr(event, d, event_raw[d]) + event.start_date = datetime.datetime.strptime(event_raw["date"], "%Y-%m-%dT%H:%M:%S+00:00") + event.start_date += datetime.timedelta(hours=2) # Hvad er vinter tid? + end_bogus = datetime.datetime.strptime(event_raw["duration"],"%H:%M:%S") + event.duration = datetime.timedelta( + hours=end_bogus.hour, minutes=end_bogus.minute, seconds=end_bogus.second) + event.end_date = event.start_date + event.duration + #print('Start date: ', event["start_date"]) + #print("End date: ", event["end_date"]) + #print() + program.append(event) - program = sorted(program, key=lambda x: x.start_date) - return program + program = sorted(program, key=lambda x: x.start_date) + return program -def get_events_to_show(program: list[Event], programs_to_show_count=4) -> list[Event]: - current_datetime = datetime.datetime.now() - #current_datetime = datetime.datetime(year=2025, month=7, day=18, hour=15, minute=30) # DEBUG - # Get events to show - events_to_show = [] - for event in program: - if len(events_to_show) >= programs_to_show_count: - break - if event.end_date > current_datetime: - events_to_show.append(event) + def get_events_to_show(self, program: list[Event], programs_to_show_count=4) -> list[Event]: + current_datetime = datetime.datetime.now() if not self.override_date else self.override_date + #current_datetime = datetime.datetime(year=2025, month=7, day=18, hour=15, minute=30) # DEBUG + # Get events to show + events_to_show = [] + for event in program: + if len(events_to_show) >= programs_to_show_count: + break + if event.end_date > current_datetime: + events_to_show.append(event) - def format_program(event: Event, cli=False) -> str: - if len(event.title) > title_max_length: - event.title = f'{event.title[:title_max_length - 3]}...' # Truncate title by deleting was 1-3 chars and putting ... - else: - event.title += " " * (title_max_length - len(event.title)) # Pad title - event.day = event.start_date.strftime('%a') - start_time = event.start_date.strftime('%H:%M') - end_time = event.end_date.strftime('%H:%M') - event.icon = icons.get(event.room) - if event.icon == None: - event.icon = '?' - print(f"NEW ICON NEEDED!!! for {event.room}") - event.time = f"{start_time}-{end_time}" - return f"{event.title} {event.day} {start_time}-{end_time} {event.icon}" - - for event_to_show in events_to_show: - event_to_show.pretty = format_program(event_to_show) - duration_sec = (event_to_show.end_date - event_to_show.start_date).total_seconds() - progress_sec = (current_datetime - event_to_show.end_date).total_seconds() - event_to_show.progress = 100 + (progress_sec / duration_sec) * 100 + def format_program(event: Event, cli=False) -> str: + if len(event.title) > self.title_max_length: + # Truncate title by deleting was 1-3 chars and putting ... + event.title = f'{event.title[:self.title_max_length - 3]}...' + else: + event.title += " " * (self.title_max_length - len(event.title)) # Pad title + event.day = event.start_date.strftime('%a') + start_time = event.start_date.strftime('%H:%M') + end_time = event.end_date.strftime('%H:%M') + event.icon = self.icons.get(event.room) + if event.icon == None: + event.icon = '?' + logger.warning(f"NEW ICON NEEDED!!! for {event.room}") + event.time = f"{start_time}-{end_time}" + return f"{event.title} {event.day} {start_time}-{end_time} {event.icon}" - return events_to_show + for event_to_show in events_to_show: + event_to_show.pretty = format_program(event_to_show) + duration_sec = (event_to_show.end_date - event_to_show.start_date).total_seconds() + progress_sec = (current_datetime - event_to_show.end_date).total_seconds() + event_to_show.progress = 100 + (progress_sec / duration_sec) * 100 -program: list[Event] = get_program() -events_to_show: list[Event] = get_events_to_show(program) + return events_to_show -''' -# Convert to the output string -events_to_show_string = '\n'.join([a.pretty for a in events_to_show]) -print(f"{events_to_show_string}\n") -# Write to output file -with open(tmp_file_path, 'w') as tmp_file: - tmp_file.write(events_to_show_string) -os.rename(tmp_file_path, output_file_path) -print(f'Written to {output_file_path} length: {len(events_to_show_string)}') -''' diff --git a/src/static/title_headline.png b/src/static/title_headline.png index e53e0cf..a6aada9 100644 Binary files a/src/static/title_headline.png and b/src/static/title_headline.png differ diff --git a/src/text_tv_bornhack.py b/src/text_tv_bornhack.py new file mode 100644 index 0000000..9514da2 --- /dev/null +++ b/src/text_tv_bornhack.py @@ -0,0 +1,47 @@ +import datetime +import os + +import flask + +from program_parser import ProgramParser + + +app = flask.Flask(__name__, template_folder='templates', static_folder='static', static_url_path='') + +program_path = os.environ["PROGRAM_PATH"] if "PROGRAM_PATH" in os.environ else './programs/current.json' +override_date = datetime.datetime.fromisoformat(os.environ["OVERRIDE_DATE"]) if "OVERRIDE_DATE" in os.environ else None +program_parser = ProgramParser(program_path=program_path, override_date=override_date) +debug = 'PROD' not in os.environ + + +@app.route('/top') +def top(): + now = datetime.datetime.now() if not override_date else override_date + return f'BornHack S105 {now.strftime("%a %d %b %R:%S")} ' + + +@app.route('/program.json') +def program(): + events_to_show: list[Event] = program_parser.get_events_to_show(program_parser.get_program(), programs_to_show_count = 1000) + #{event.title} {day} {start_time}-{end_time} {icon} + json = [{'title': a.title, 'day': a.day, 'time': a.time, 'progress': a.progress, 'icon': a.icon} for a in events_to_show] + return json + + +@app.route('/text') +def text() -> dict[str, str]: + return {part: eval(part)() for part in ['top', 'program']} + + +@app.route('/') +def index(): + return flask.render_template('index.html', parts=text()) + + +def main(): + global app + app.run(debug=debug) + + +if __name__ == '__main__': + main() -- cgit v1.3.1