#!/usr/bin/python3 from renderlib import * from schedulelib import * from easing import * from xml.sax.saxutils import escape as xmlescape from PIL import ImageFont # URL to Schedule-XML scheduleUrl = "https://cfp.ctbk.de/fsck-2025/schedule/export/schedule.xml" def introFrames(args): frames = 1 * fps for _ in range(0, frames): yield ( ("title", "style", "opacity", 0), ("persons", "style", "opacity", 0), ("glogo", "style", "opacity", 0), ("sparkle", "style", "opacity", 0), ) # fade in logo frames = 3 * fps for i in range(0, frames): # source: matrix(0.66093871,0,0,0.66093871,325.49887,111.96043) # target: matrix(1, 0, 0, 1, 0, 0) size = 0.66 + easeInOutQuad(i, 0.0, 1.0 - 0.66, frames) posx = easeInOutQuad(frames - i, 0.0, 325.49887, frames) posy = easeInOutQuad(frames - i, 0.0, 111.96043, frames) yield ( ( "glogo", "attr", "transform", # the easing function can’t handle easing from larger to smaller, so use this hacky workaround f"translate(0,{easeInQuad(frames - i, 0.0, 200.0, frames)})", ), ("glogo", "style", "opacity", easeOutQuad(i, 0, 1, frames)), # scale in the fsck text banner behind the foreground elements # target size is 5.414656 in both x and y ( "fsck-banner", "attr", "transform", f"matrix({size:.4f},0,0,{size:.4f},{posx:.4f},{posy:.4f})", ), ("title", "style", "opacity", 0), ("persons", "style", "opacity", 0), ("sparkle", "style", "opacity", 0), ) frames = 1 * fps for i in range(0, frames): yield ( ("glogo", "style", "opacity", 1), ("title", "style", "opacity", 0), ("persons", "style", "opacity", 0), ("sparkle", "style", "opacity", 0), ) # fade in title and persons frames = 3 * fps intermediate = (frames * 1) // 3 for i in range(0, intermediate): yield ( ("title", "style", "opacity", easeOutQuad(i, 0, 1, frames)), ("persons", "style", "opacity", 0), ("glogo", "style", "opacity", 1), ( "sparkle", "style", "opacity", easeOutQuad(min(i * 2, intermediate), 0, 1, intermediate), ), ( "sparkle", "attr", "transform", f"translate({-463.66869 + easeLinear(i, 0.0, 378.32308 + 463.66869, frames)}, 0)", ), ) for i in range(intermediate, frames): yield ( ("title", "style", "opacity", easeOutQuad(i, 0, 1, frames)), ( "persons", "style", "opacity", easeOutQuad(i - intermediate, 0, 1, frames - intermediate), ), ("glogo", "style", "opacity", 1), ( "sparkle", "style", "opacity", easeInQuad( i - intermediate, 1, -1, frames - intermediate, ), ), ( "sparkle", "attr", "transform", f"translate({-463.66869 + easeLinear(i, 0.0, 378.32308 + 463.66869, frames)}, 0)", ), ) # show whole image for 5 seconds frames = 5 * fps for i in range(0, frames): yield ( ("title", "style", "opacity", 1), ("persons", "style", "opacity", 1), ("glogo", "style", "opacity", 1), ("sparkle", "style", "opacity", 0), ) # fade out image and text frames = 1 * fps for i in range(0, frames): yield ( ("title", "style", "opacity", easeOutQuad(i, 1, -1, frames)), ("persons", "style", "opacity", easeOutQuad(i, 1, -1, frames)), ("glogo", "style", "opacity", easeOutQuad(i, 1, -1, frames)), ("sparkle", "style", "opacity", 0), ) def outroFrames(args): frames = 3 * fps for i in range(0, frames): yield ( ("cc-text", "style", "opacity", 1), ("logo", "style", "opacity", 1), ) # fadeout outro graphics frames = 3 * fps for i in range(0, frames): yield ( ("cc-text", "style", "opacity", easeOutQuad(i, 1, -1, frames)), ("logo", "style", "opacity", easeOutQuad(i, 1, -1, frames)), ) def debug(): render( ["intro.svg", "intro.flac"], "../intro.ts", introFrames, { "$title": "Long Long Long title is LONG ", "$personnames": "Long Name of Dr. Dr. Prof. Dr. Long Long", }, ) # render('outro.svg', # '../outro.mkv', # outroFrames # ) def fit_text(string: str, max_width: int, font: ImageFont) -> list[str]: """Break text into array of strings which fit certain a width (in pixels) for the specified font.""" split_line = [x.strip() for x in string.split()] lines = [] w = 0 line = [] for word in split_line: new_line = line + [word.rstrip(":")] w = font.getlength(" ".join(new_line)) if w > max_width: lines.append(" ".join(line)) line = [] line.append(word.rstrip(":")) if word.endswith(":"): lines.append(" ".join(line)) line = [] if line: lines.append(" ".join(line)) return lines def tasks(queue, args, idlist, skiplist): font = ImageFont.truetype( "../DIN1451Mittelschrift.ttf", size=74.6667, encoding="unic" ) # iterate over all events extracted from the schedule xml-export for event in events(scheduleUrl): # if event["room"] not in ("Medientheater", "Vortragssaal", "Blauer Salon"): # print("skipping room %s (%s)" % (event["room"], event["title"])) # continue # if event["day"] not in ("0", "1", "2", "3", "4"): # print("skipping day %s" % (event["day"])) # continue if not (idlist == []): if 000000 in idlist: print("skipping id (%s [%s])" % (event["title"], event["id"])) continue if int(event["id"]) not in idlist: print("skipping id (%s [%s])" % (event["title"], event["id"])) continue # generate a task description and put it into the queue title_segments = fit_text(xmlescape(event["title"]), 1920, font) title = "".join( f'{el}' for el in title_segments ) queue.put( Rendertask( infile=["intro.svg", "intro.flac"], outfile=str(event["id"]) + ".ts", sequence=introFrames, parameters={ "$title": title, "$personnames": xmlescape(event["personnames"]), }, ) ) # # place a task for the outro into the queue # if not "out" in skiplist: # queue.put( # Rendertask(infile="outro.svg", outfile="outro.ts", sequence=outroFrames) # ) # if not "pause" in skiplist: # # place the pause-sequence into the queue # queue.put( # Rendertask(infile="pause.svg", outfile="pause.ts", sequence=pauseFrames) # )