diff --git a/forumoe19/__init__.py b/forumoe19/__init__.py new file mode 100644 index 0000000..b85bffc --- /dev/null +++ b/forumoe19/__init__.py @@ -0,0 +1,154 @@ +#!/usr/bin/python + +from renderlib import * +from easing import * + +# URL to Schedule-XML +scheduleUrl = 'https://gist.githubusercontent.com/danimo/3cf27a2198da2fbc7c1fb138c13506ce/raw/forumoe19-schedule.xml' + +personmap = { +1: 'Margit Stumpp (Fraktion B. 90/Die Grünen)', +2: 'Marja-Liisa Völlers (SPD-Fraktion)', +3: 'Dr. Jens Brandenburg (FDP-Fraktion)', +} + +taglinemap = { +1: "Bildungspolitische Sprecherin", +2: "Expertin für frühk., schul. und berufl. Bildung", +3: "Sprecher für Studium und Bildung", +4: "mediale.pfade", +} + + +def bounce(i, min, max, frames): + if i == frames - 1: + return 0 + + if i <= frames/2: + return easeInOutQuad(i, min, max, frames/2) + else: + return max - easeInOutQuad(i - frames/2, min, max, frames/2) + +def introFrames(parameters): + # 1 Sekunde Text Fadein + frames = 1*fps + for i in range(0, frames): + yield ( + ('text', 'style', 'opacity', "%.4f" % easeLinear(i, 0, 1, frames)), + ) + + # 4 Sekunden stehen lassen + frames = 4*fps + for i in range(0, frames): + yield () + +def outroFrames(p): + # 5 Sekunden stehen bleiben + frames = 5*fps + for i in range(0, frames): + yield [] + +def bbFrames(parameters): + # 1 Sekunde Text Fadein + frames = 1*fps + for i in range(0, frames): + yield ( + ('bg', 'style', 'opacity', "%.4f" % easeLinear(i, 0, 1, frames)), + ('text', 'style', 'opacity', "%.4f" % easeLinear(i, 0, 1, frames)), + ) + + # 3 Sekunden stehen lassen + frames = 3*fps + for i in range(0, frames): + yield () + + frames = 1*fps + for i in range(0, frames): + yield ( + ('bg', 'style', 'opacity', "%.4f" % easeLinear(i, 1, -1, frames)), + ('text', 'style', 'opacity', "%.4f" % easeLinear(i, 1, -1, frames)), + ) + + + +def debug(): +# render( +# 'intro.svg', +# '../intro.ts', +# introFrames, +# { +# '$ID': 4711, +# '$TITLE': "Long Long Long title is LONG", +# '$SUBTITLE': 'Long Long Long Long subtitle is LONGER', +# '$SPEAKER': 'Long Name of Dr. Dr. Prof. Dr. Long Long' +# } +# ) + + render( + 'insert.svg', + '../insert.mkv', + bbFrames, + { + '$PERSON': "Prof. Bernhard Birnbaum", + '$TAGLINE': "Leiter des rennomierten Birnbaum-Instituts", + } + ) + +# render( +# 'pause.svg', +# '../pause.ts', +# pauseFrames +# ) +# +# render( +# 'outro.svg', +# '../outro.ts', +# outroFrames +# ) + +def tasks(queue, args, idlist, skiplist): + # iterate over all events extracted from the schedule xml-export + for event in events(scheduleUrl): + if event['room'] not in ('ecdf'): + print("skipping room %s (%s [%s])" % (event['room'], event['title'], event['id'])) + 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 + queue.put(Rendertask( + infile = 'intro.svg', + outfile = str(event['id'])+".ts", + sequence = introFrames, + parameters = { + '$ID': event['id'], + '$TITLE': event['title'], + '$SUBTITLE': event['subtitle'], + '$SPEAKER': 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 + )) + + for person in persons(scheduleUrl, personmap, taglinemap): + queue.put(Rendertask( + infile = 'insert.svg', + outfile = "insert_{}.mkv".format(person['person'].replace("/", "_")), + sequence = bbFrames, + parameters = { + '$PERSON': person['person'], + '$TAGLINE': person['tagline'], + } + )) + diff --git a/forumoe19/artwork/5_Intro-Video.png b/forumoe19/artwork/5_Intro-Video.png new file mode 100644 index 0000000..c7e2925 Binary files /dev/null and b/forumoe19/artwork/5_Intro-Video.png differ diff --git a/forumoe19/artwork/CC-Zero-badge.svg b/forumoe19/artwork/CC-Zero-badge.svg new file mode 100644 index 0000000..122529e --- /dev/null +++ b/forumoe19/artwork/CC-Zero-badge.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/forumoe19/artwork/Logo_Bündnis_Freie_Bildung_hochkant.png b/forumoe19/artwork/Logo_Bündnis_Freie_Bildung_hochkant.png new file mode 100644 index 0000000..f5febb2 Binary files /dev/null and b/forumoe19/artwork/Logo_Bündnis_Freie_Bildung_hochkant.png differ diff --git a/forumoe19/artwork/insert.png b/forumoe19/artwork/insert.png new file mode 100644 index 0000000..4dd7c16 Binary files /dev/null and b/forumoe19/artwork/insert.png differ diff --git a/forumoe19/artwork/insert.svg b/forumoe19/artwork/insert.svg new file mode 100644 index 0000000..c2106fb --- /dev/null +++ b/forumoe19/artwork/insert.svg @@ -0,0 +1,97 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + $PERSON$TAGLINE + + + diff --git a/forumoe19/artwork/intro.svg b/forumoe19/artwork/intro.svg new file mode 100644 index 0000000..bab0025 --- /dev/null +++ b/forumoe19/artwork/intro.svg @@ -0,0 +1,588 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $TITLE $SPEAKER + + + + + + diff --git a/forumoe19/artwork/logo-okfn.png b/forumoe19/artwork/logo-okfn.png new file mode 100644 index 0000000..1abf6f4 Binary files /dev/null and b/forumoe19/artwork/logo-okfn.png differ diff --git a/forumoe19/artwork/outro.svg b/forumoe19/artwork/outro.svg new file mode 100644 index 0000000..a4353e3 --- /dev/null +++ b/forumoe19/artwork/outro.svg @@ -0,0 +1,583 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/forumoe19/artwork/pause.svg b/forumoe19/artwork/pause.svg new file mode 100644 index 0000000..e69de29 diff --git a/forumoe19/artwork/wmde.png b/forumoe19/artwork/wmde.png new file mode 100644 index 0000000..5394326 Binary files /dev/null and b/forumoe19/artwork/wmde.png differ diff --git a/renderlib.py b/renderlib.py index 36ff9b4..21b7173 100644 --- a/renderlib.py +++ b/renderlib.py @@ -162,7 +162,7 @@ def rendertask(task): # write the generated svg-text into the output-file fp.write(etree.tostring(svg, encoding='unicode')) - if task.outfile.endswith('.ts') or task.outfile.endswith('.mov'): + if task.outfile.endswith('.ts') or task.outfile.endswith('.mov') or task.outfile.endswith('.mkv'): width = 1920 height = 1080 else: @@ -216,6 +216,8 @@ def rendertask(task): elif task.outfile.endswith('.mov'): cmd = 'cd {0} && '.format(task.workdir) cmd += 'ffmpeg -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -f image2 -i .frames/%04d.png -r 25 -shortest -c:v qtrle -f mov "{0}"'.format(task.outfile) + elif task.outfile.endswith('.mkv'): + cmd = 'cd {0} && ffmpeg -ar 48000 -ac 2 -f s16le -i /dev/zero -f image2 -i .frames/%04d.png -aspect 16:9 -c copy -shortest "{1}"'.format(task.workdir, task.outfile) else: cmd = 'cd {0} && ffmpeg -ar 48000 -ac 2 -f s16le -i /dev/zero -f image2 -i .frames/%04d.png -target pal-dv -aspect 16:9 -shortest "{1}"'.format(task.workdir, task.outfile) @@ -238,7 +240,7 @@ def rendertask(task): # Download the Events-Schedule and parse all Events out of it. Yield a tupel for each Event -def events(scheduleUrl, titlemap={}): +def downloadSchedule(scheduleUrl): print("downloading schedule") # download the schedule @@ -249,8 +251,41 @@ def events(scheduleUrl, titlemap={}): # parse into ElementTree parser = etree.XMLParser(huge_tree=True) - schedule = etree.fromstring(xml, parser) + return etree.fromstring(xml, parser) +def persons(scheduleUrl, personmap={}, taglinemap={}): + schedule = downloadSchedule(scheduleUrl) + # iterate all days + for day in schedule.iter('day'): + # iterate all rooms + for room in day.iter('room'): + # iterate events on that day in this room + for event in room.iter('event'): + # aggregate names of the persons holding this talk + persons_seen = [] + if event.find('persons') is not None: + for person in event.find('persons').iter('person'): + id = int(person.get("id")) + person = re.sub(r'\s+', ' ', person.text).strip() + match = re.search(r'\((.*?)\)', person) + tagline = '' + if not match is None: + tagline = match.group(1) + person = person.split(" (")[0] + if id in taglinemap: + tagline = taglinemap[id] + if id in personmap: + person = personmap[id] + if not id in persons_seen: + persons_seen.append(id) + yield { + 'id': id, + 'person': person, + 'tagline': tagline + } + +def events(scheduleUrl, titlemap={}): + schedule = downloadSchedule(scheduleUrl) # iterate all days for day in schedule.iter('day'): # iterate all rooms @@ -277,7 +312,6 @@ def events(scheduleUrl, titlemap={}): subtitle = re.sub(r'\s+', ' ', event.find('subtitle').text).strip() else: subtitle = '' - # yield a tupel with the event-id, event-title and person-names yield { 'id': id, @@ -287,7 +321,7 @@ def events(scheduleUrl, titlemap={}): 'personnames': ', '.join(personnames), 'room': room.attrib['name'], 'track': event.find('track').text, - 'url': event.find('url').text + #'url': event.find('url').text }