From c66268d4b5bd47c31eff98852d351debc30e160c Mon Sep 17 00:00:00 2001 From: txrpe Date: Wed, 25 Feb 2026 11:24:50 +0100 Subject: [PATCH] [script] Add generation of english posters --- .gitignore | 5 +- image_poster.py | 186 ++++++++++++++++++++++++++++++------------------ 2 files changed, 119 insertions(+), 72 deletions(-) diff --git a/.gitignore b/.gitignore index 1fb0d73..4adadc2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,10 @@ poster.html poster.pdf poster_light.png poster_dark.png +poster_light_en.png +poster_dark_en.png poster.txt +poster_en.txt http.access.log http.error.log events.ical @@ -16,4 +19,4 @@ events.html events_archive.html -!site/lif* \ No newline at end of file +!site/lif* diff --git a/image_poster.py b/image_poster.py index a3ad76d..269d2d0 100755 --- a/image_poster.py +++ b/image_poster.py @@ -1,7 +1,6 @@ #! /usr/bin/env python3 import argparse -import freetype import io from PIL import Image, ImageDraw, ImageFont import csv @@ -12,17 +11,39 @@ from cairosvg import svg2png CURRENT_TIME = dt.date.today() NEXT_MONTH = CURRENT_TIME + relativedelta.relativedelta(months=1, day=1) DAYS_OF_WEEK_SR = ("PON", "UTO", "SRE", "ČET", "PET", "SUB", "NED") -MONTHS_SR = ("Januar", "Februar", "Mart", "April", "Maj", "Jun", "Jul", "Avgust",\ - "Septembar", "Oktobar", "Novembar", "Decembar") +DAYS_OF_WEEK_EN = ("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUn") + +MONTHS_SR = ("Januar", "Februar", "Mart", "April", "Maj", "Jun", "Jul", + "Avgust", "Septembar", "Oktobar", "Novembar", "Decembar") +MONTHS_EN = ("January", "February", "March", "April", "May", "June", "July", + "August", "September", "October", "November", "December") + +HEADER_SR = "Plan za {}" + +SUBHEADER_SR = """ +Ponedeljak u 19h u XECUT Jovana Ćirilova 15 +Utorak u 18h u JAG3 Vatroslava Jagića 5 +Svi dogadjaji su uvek besplatni +""" +HEADER_EN = "Plan for {}" + +SUBHEADER_EN = """ +Mondays at 19h at XECUT Jovana Ćirilova 15 +Tuesdays at 18h at JAG3 Vatroslava Jagića 5 +All events are always free +""" + def parseArgs(parser): """ Parse all arguments and return the list of argument values """ - parser.add_argument("month", metavar = "MM", help = "two digit number representing the month for which to generate poster", default = "empty", nargs = "?") + parser.add_argument("month", metavar="MM", + help="two digit number representing the month for which to generate poster", default="empty", nargs="?") return parser.parse_args() -def load_events(csv_path:str, month:int) -> list[dict]: + +def load_events(csv_path: str, month: int) -> list[dict]: monthafter = month + relativedelta.relativedelta(months=1, day=1) events = [] with open(csv_path) as csv_file: @@ -30,59 +51,107 @@ def load_events(csv_path:str, month:int) -> list[dict]: next(csv_reader, None) for event in csv_reader: event_date = event[0] - event_date_parsed = dt.datetime.strptime(event_date, "%d-%m-%Y").date() + event_date_parsed = dt.datetime.strptime( + event_date, "%d-%m-%Y").date() event_time = event[1] event_title = event[3] - current_event = {"date":event_date_parsed, - "time":event_time, - "title":event_title.strip()} + event_title_en = event[3] + if len(event) > 6: + event_title_en = event[6] + + current_event = {"date": event_date_parsed, + "time": event_time, + "title": event_title.strip(), + "title_en": event_title_en.strip()} if event_date_parsed >= month and event_date_parsed < monthafter: events.append(current_event) return events -def drawPoster(events, bg, fg, month:int): + +def drawMesh(draw, img, fg, bg, font, W, H): + def drawCircle(x, y): + r = 50 + draw.ellipse((x - r, y - r, x + r, y+r), + fill=fg, outline=(0, 0, 0), width=0) + + LCX = 415 # logo center x + LCY = 4350 # logo center y + d = 190 # delta + drawCircle(LCX - d, LCY) + drawCircle(LCX, LCY) + drawCircle(LCX, LCY - d) + drawCircle(LCX, LCY + d) + drawCircle(LCX + d, LCY) + + draw.line([(LCX - d, LCY), (LCX + d, LCY)], fill=fg, width=20, joint=None) + draw.line([(LCX, LCY), (LCX, LCY + d), (LCX + d, LCY), + (LCX, LCY - d)], fill=fg, width=20, joint=None) + draw.text((LCX - 1.7*d, LCY + 1.5*d), "dmz.rs", font=font, fill=fg) + + mesh_svg = svg2png(url='site/img/mesh-light.svg') + mesh_svg_bytes = io.BytesIO(mesh_svg) + mesh_img = Image.open(mesh_svg_bytes) + if bg == (0, 0, 0): + pixdata = mesh_img.load() + for y in range(mesh_img.size[1]): + for x in range(mesh_img.size[0]): + if pixdata[x, y] != (0, 0, 0, 0): + pixdata[x, y] = (0, 100, 0, 255) + + mesh_img = mesh_img.resize((W, H)) + mesh_img.thumbnail((W, H), Image.Resampling.LANCZOS) + + mesh_w, mesh_h = mesh_img.size + mesh_position = (W - mesh_w, H - mesh_h) + img.paste(mesh_img, mesh_position, mesh_img) + + +def drawPoster(events, bg, fg, month: int, en: bool): fontFacade = ImageFont.truetype('./site/font/Facade-Sud.woff', size=365) - fontIosevka = ImageFont.truetype('./site/font/iosevka-regular.woff', size=200) - fontIosevkaSmall = ImageFont.truetype('./site/font/iosevka-regular.woff', size=150) + fontIosevka = ImageFont.truetype( + './site/font/iosevka-regular.woff', size=200) + fontIosevkaSmall = ImageFont.truetype( + './site/font/iosevka-regular.woff', size=150) W = 3508 - H = 4960 + H = 4960 img = Image.new('RGB', (W, H), bg) draw = ImageDraw.Draw(img) - header = "DECENTRALA" - _, _, w, _ = draw.textbbox((0, 0), header, font=fontFacade) - draw.text(((W-w)/2, 165), header, font=fontFacade, fill=fg) + drawMesh(draw, img, fg, bg, fontIosevka, W, H) - subheader = f"Plan za {MONTHS_SR[month.month - 1]}" - _, _, w, _ = draw.textbbox((0, 0), subheader, font=fontIosevka) - draw.text(((W-w)/2, 560), subheader, font=fontIosevka, fill=fg) + title = "DECENTRALA" + _, _, w, _ = draw.textbbox((0, 0), title, font=fontFacade) + draw.text(((W-w)/2, 165), title, font=fontFacade, fill=fg) - height = 990 + header = HEADER_EN if en else HEADER_SR + months = MONTHS_EN if en else MONTHS_SR + header = header.format(months[month.month - 1]) - draw.text((165, height), "Pondeljkom u 19h i utorkom u 18h ", font=fontIosevkaSmall, fill=fg) - height += 200 + _, _, w, _ = draw.textbbox((0, 0), header, font=fontIosevka) + draw.text(((W-w)/2, 560), header, font=fontIosevka, fill=fg) - # draw.text((165, height), "Sredom u 18h na MATF-u", font=fontIosevkaSmall, fill=fg) - # height += 200 + height = 890 - draw.text((165, height), "Svi dogadjaji su uvek besplatni", font=fontIosevkaSmall, fill=fg) - height += 300 + sub_header = SUBHEADER_EN if en else SUBHEADER_SR + draw.text((165, height), sub_header, + font=fontIosevkaSmall, fill=fg) + height += 800 # Write list of events to sperate text file as well - textfile=open("poster.txt","w") + textfile = open(f"poster{"_en" if en else ""}.txt", "w") - textfile.write(f"Plan za {MONTHS_SR[month.month - 1]}\n\n") - textfile.write("Radionice pocinju u 19h u DC Krovu\n") - textfile.write("Svi dogadjaji su uvek besplatni\n\n") + sub_header = SUBHEADER_EN if en else SUBHEADER_SR + textfile.write(sub_header.format(months[month.month - 1])) + days_of_week = DAYS_OF_WEEK_EN if en else DAYS_OF_WEEK_SR # Loop to write events both to poster image and text file for event in events: # Add event to image poster - date = DAYS_OF_WEEK_SR[event["date"].weekday()] + date = days_of_week[event["date"].weekday()] day = event["date"].day - title = event["title"] + title = event["title_en"] if en else event["title"] pad = " " if event["date"].day < 10 else "" eventText = f"{date} {day}. {pad}{title}" draw.text((165, height), eventText, font=fontIosevkaSmall, fill=fg) @@ -93,62 +162,37 @@ def drawPoster(events, bg, fg, month:int): textfile.close() - def drawCircle(x, y): - r = 50 - draw.ellipse((x - r, y - r, x + r, y+r), fill=fg, outline=(0, 0, 0), width=0) - - LCX = 415 # logo center x - LCY = 4350 # logo center y - d = 190 # delta - drawCircle(LCX - d, LCY) - drawCircle(LCX, LCY) - drawCircle(LCX, LCY - d) - drawCircle(LCX, LCY + d) - drawCircle(LCX + d, LCY) - - draw.line([(LCX - d, LCY), (LCX + d, LCY)], fill=fg, width=20, joint=None) - draw.line([(LCX, LCY), (LCX, LCY + d), (LCX + d, LCY), (LCX, LCY - d)], fill=fg, width=20, joint=None) - draw.text((LCX - 1.7*d, LCY + 1.5*d), "dmz.rs", font=fontIosevka, fill=fg) - - mesh_svg = svg2png(url='site/img/mesh-light.svg') - mesh_svg_bytes = io.BytesIO(mesh_svg) - mesh_img = Image.open(mesh_svg_bytes) - if bg == (0,0,0): - pixdata = mesh_img.load() - for y in range(mesh_img.size[1]): - for x in range(mesh_img.size[0]): - if pixdata[x,y] != (0,0,0,0): - pixdata[x, y] = (0, 100, 0, 255) - - mesh_img = mesh_img.resize((W,H)) - mesh_img.thumbnail((W,H), Image.Resampling.LANCZOS) - - mesh_w, mesh_h = mesh_img.size - mesh_position = (W - mesh_w, H - mesh_h) - img.paste(mesh_img, mesh_position, mesh_img) - return img + def main(): # Parse arguments - parser = argparse.ArgumentParser(description="Generate images of the poster") + parser = argparse.ArgumentParser( + description="Generate images of the poster") args = parseArgs(parser) # Set month based on user input month = NEXT_MONTH if args.month.isdigit(): - month = dt.date(CURRENT_TIME.year, int(args.month), 1) + month = dt.date(CURRENT_TIME.year, int(args.month), 1) elif args.month != "empty": - print("Month has to be specified as a number. I will use next month as the default") + print("Month has to be specified as a number. I will use next month as the default") # Load events and draw a poseter events = load_events("dogadjaji.csv", month) - img = drawPoster(events, (0, 0, 0), (20, 250, 50), month) + img = drawPoster(events, (0, 0, 0), (20, 250, 50), month, False) img.save('poster_dark.png') - img = drawPoster(events, (255, 255, 255), (0, 0, 0), month) + img = drawPoster(events, (255, 255, 255), (0, 0, 0), month, False) img.save('poster_light.png') + img = drawPoster(events, (0, 0, 0), (20, 250, 50), month, True) + img.save('poster_dark_en.png') + + img = drawPoster(events, (255, 255, 255), (0, 0, 0), month, True) + img.save('poster_light_en.png') + + if __name__ == "__main__": main()