diff --git a/sotmeu14/artwork/Logo.de.svg b/sotmeu14/artwork/Logo.de.svg new file mode 100644 index 0000000..6824b2d --- /dev/null +++ b/sotmeu14/artwork/Logo.de.svg @@ -0,0 +1,488 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + KARLSRUHE 13.-15. JUNI 14 + + diff --git a/sotmeu14/artwork/Logo.en.svg b/sotmeu14/artwork/Logo.en.svg new file mode 100644 index 0000000..c14b1f8 --- /dev/null +++ b/sotmeu14/artwork/Logo.en.svg @@ -0,0 +1,488 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + KARLSRUHE 13-15 JUNE 14 + + diff --git a/sotmeu14/artwork/Logo.fr.svg b/sotmeu14/artwork/Logo.fr.svg new file mode 100644 index 0000000..474cb45 --- /dev/null +++ b/sotmeu14/artwork/Logo.fr.svg @@ -0,0 +1,488 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + KARLSRUHE 13-15 JUIN 14 + + diff --git a/sotmeu14/artwork/Logo.ro.svg b/sotmeu14/artwork/Logo.ro.svg new file mode 100644 index 0000000..98af9ed --- /dev/null +++ b/sotmeu14/artwork/Logo.ro.svg @@ -0,0 +1,488 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + KARLSRUHE 13.-15. IULIE 14 + + diff --git a/sotmeu14/artwork/by-sa.svg b/sotmeu14/artwork/by-sa.svg new file mode 100644 index 0000000..f850297 --- /dev/null +++ b/sotmeu14/artwork/by-sa.svg @@ -0,0 +1,199 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sotmeu14/artwork/img-building.jpg b/sotmeu14/artwork/img-building.jpg new file mode 100644 index 0000000..f59419d Binary files /dev/null and b/sotmeu14/artwork/img-building.jpg differ diff --git a/sotmeu14/artwork/img-mensa.jpg b/sotmeu14/artwork/img-mensa.jpg new file mode 100644 index 0000000..c73f7d6 Binary files /dev/null and b/sotmeu14/artwork/img-mensa.jpg differ diff --git a/sotmeu14/artwork/img-palace.jpg b/sotmeu14/artwork/img-palace.jpg new file mode 100644 index 0000000..bf0eed3 Binary files /dev/null and b/sotmeu14/artwork/img-palace.jpg differ diff --git a/sotmeu14/artwork/img-townhall-soft.jpg b/sotmeu14/artwork/img-townhall-soft.jpg new file mode 100644 index 0000000..e85ec68 Binary files /dev/null and b/sotmeu14/artwork/img-townhall-soft.jpg differ diff --git a/sotmeu14/artwork/img-townhall.jpg b/sotmeu14/artwork/img-townhall.jpg new file mode 100644 index 0000000..b2a1c0d Binary files /dev/null and b/sotmeu14/artwork/img-townhall.jpg differ diff --git a/sotmeu14/artwork/overlay-1024x576.png b/sotmeu14/artwork/overlay-1024x576.png new file mode 100644 index 0000000..2815aa3 Binary files /dev/null and b/sotmeu14/artwork/overlay-1024x576.png differ diff --git a/sotmeu14/artwork/overlay-720x576.png b/sotmeu14/artwork/overlay-720x576.png new file mode 100644 index 0000000..728a5f5 Binary files /dev/null and b/sotmeu14/artwork/overlay-720x576.png differ diff --git a/sotmeu14/artwork/pause.svg b/sotmeu14/artwork/pause.svg new file mode 100644 index 0000000..960e391 --- /dev/null +++ b/sotmeu14/artwork/pause.svg @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + Berlin19. - 21. März 2014 + FOSSGIS Konferenz2014fossgis.de/konferenz/2014/ + + + + Gleich geht's weiter... + + + + diff --git a/sotmeu14/artwork/vorspann.svg b/sotmeu14/artwork/vorspann.svg new file mode 100644 index 0000000..00f4e25 --- /dev/null +++ b/sotmeu14/artwork/vorspann.svg @@ -0,0 +1,706 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + $personnames + $title + + + diff --git a/sotmeu14/make.py b/sotmeu14/make.py new file mode 100755 index 0000000..283db33 --- /dev/null +++ b/sotmeu14/make.py @@ -0,0 +1,466 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import sys +import glob +import os +import re +import math +import shutil +import errno +import unicodedata +import urllib2 +#import xml.etree.ElementTree as etree +from lxml import etree +import cssutils +import logging +import textwrap +import tempfile +import threading +import multiprocessing +from threading import Thread, Lock +from Queue import Queue + +# Frames per second. Increasing this renders more frames, the avconf-statements would still need modifications +fps = 25 + +# using --debug skips the threading, the network fetching of the schedule and +# just renders one type of video +debug = ('--debug' in sys.argv) + +# using --offline only skips the network fetching and use a local schedule.de.xml +offline = ('--offline' in sys.argv) + +# set charset of output-terminal +reload(sys) +sys.setdefaultencoding('utf-8') + +# t: current time, b: begInnIng value, c: change In value, d: duration +# copied from jqueryui +def easeOutCubic(t, b, c, d): + t=float(t)/d-1 + return c*((t)*t*t + 1) + b + +def easeInCubic(t, b, c, d): + t=float(t)/d + return c*(t)*t*t + b; + +# try to create all folders needed and skip, they already exist +def ensurePathExists(path): + try: + os.makedirs(path) + except OSError as exception: + if exception.errno != errno.EEXIST: + raise + +# remove the files matched by the pattern +def ensureFilesRemoved(pattern): + for f in glob.glob(pattern): + os.unlink(f) + +# Normalizes string, converts to lowercase, removes non-alpha characters, + #and converts spaces to hyphens. +def slugify(value): + value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') + value = unicode(re.sub('[^\w\s-]', '', value).strip().lower()) + value = unicode(re.sub('[-\s]+', '-', value)) + return value + +# create a filename from the events' id and a slugified version of the title +def vorspannFilename(id, title): + return u'{0:04d}-{1}.dv'.format(id, slugify(unicode(title))) + +# svg does not have a method for automatic line breaking, that rsvg is capable of +# so we do it in python as good as we can +def vorspannTitle(title): + return ''.join(textwrap.wrap(title, 50)) + +def vorspannUrl(id): + return 'fossgis.de/konferenz/2014/programm/events/'+str(id)+'.de.html' + + +def abspannFrames(): + # 5 Sekunden + + # 2 Sekunden Fadein Text + frames = 2*fps + for i in range(0, frames): + yield ( + ('banderole', 'opacity', "%.4f" % easeOutCubic(i, 0, 1, frames) ), + ('license', 'opacity', 0) + ) + + # 2 Sekunde Fadein Lizenz-Logo + frames = 2*fps + for i in range(0, frames): + yield ( + ('banderole', 'opacity', 1), + ('license', 'opacity', "%.4f" % (float(i)/frames)) + ) + + # 1 Sekunde stehen bleiben + frames = 1*fps + for i in range(0, frames): + yield ( + ('banderole', 'opacity', 1), + ('license', 'opacity', 1) + ) + +def vorspannFrames(): + # 7 Sekunden + + # 0.5 Sekunden stehen bleiben + frames = int(math.ceil(0.5*fps)) + for i in range(0, frames): + yield ( + ('logo', 'opacity', 0), + ('box', 'opacity', 0) + ) + + # 3 Sekunden Fadein Logo + frames = 3*fps + for i in range(0, frames): + yield ( + ('logo', 'opacity', "%.4f" % easeInCubic(i, 0, 1, frames)), + ('box', 'opacity', 0) + ) + + # 3 Sekunde Fadein Box + frames = 3*fps + for i in range(0, frames): + yield ( + ('logo', 'opacity', 1), + ('box', 'opacity', "%.4f" % easeOutCubic(i, 0, 1, frames)) + ) + + # 3 Sekunden stehen bleiben + frames = int(math.ceil(2.5*fps)) + for i in range(0, frames): + yield ( + ('logo', 'opacity', 1), + ('box', 'opacity', 1) + ) + +def pauseFrames(): + # 12 Sekunden + + # 2 Sekunden Text1 stehen + frames = 2*fps + for i in range(0, frames): + yield ( + ('text1', 'opacity', 1), + ('text2', 'opacity', 0) + ) + + # 2 Sekunden Fadeout Text1 + frames = 2*fps + for i in range(0, frames): + yield ( + ('text1', 'opacity', "%.4f" % (1-easeOutCubic(i, 0, 1, frames))), + ('text2', 'opacity', 0) + ) + + # 2 Sekunden Fadein Text2 + frames = 2*fps + for i in range(0, frames): + yield ( + ('text1', 'opacity', 0), + ('text2', 'opacity', "%.4f" % easeOutCubic(i, 0, 1, frames)) + ) + + # 2 Sekunden Text2 stehen + frames = 2*fps + for i in range(0, frames): + yield ( + ('text1', 'opacity', 0), + ('text2', 'opacity', 1) + ) + + # 2 Sekunden Fadeout Text2 + frames = 2*fps + for i in range(0, frames): + yield ( + ('text1', 'opacity', 0), + ('text2', 'opacity', "%.4f" % (1-easeOutCubic(i, 0, 1, frames))) + ) + + # 2 Sekunden Fadein Text1 + frames = 2*fps + for i in range(0, frames): + yield ( + ('text1', 'opacity', "%.4f" % (easeOutCubic(i, 0, 1, frames))), + ('text2', 'opacity', 0) + ) + +cssutils.ser.prefs.lineSeparator = ' ' +cssutils.log.setLevel(logging.ERROR) + +def render(infile, outfile, sequence, parameters={}, workdir='artwork'): + # in debug mode we have no thread-worker which prints its progress + if debug: + print "generating {0} from {1}".format(outfile, infile) + + # make sure a .frames-directory exists in out workdir + ensurePathExists(os.path.join(workdir, '.frames')) + + # open and parse the input file + with open(os.path.join(workdir, infile), 'r') as fp: + svgstr = fp.read() + for key in parameters.keys(): + svgstr = svgstr.replace(key, str(parameters[key])) + + svg = etree.fromstring(svgstr) + + # find all images and force them to absolute file-urls + namespaces = {'xlink': 'http://www.w3.org/1999/xlink', 'svg': 'http://www.w3.org/2000/svg'} + for el in svg.findall(".//svg:image[@xlink:href]", namespaces=namespaces): + el.attrib['{http://www.w3.org/1999/xlink}href'] = 'file:///' + os.path.realpath(workdir) + '/' + el.attrib['{http://www.w3.org/1999/xlink}href'] + + + # frame-number counter + frameNr = 0 + + # iterate through the animation seqence frame by frame + # frame is a ... tbd + for frame in sequence(): + # print a line for each and every frame generated + if debug: + print "frameNr {0:2d} => {1}".format(frameNr, frame) + + # open the output-file (named ".gen.svg" in the workdir) + with open(os.path.join(workdir, '.gen.svg'), 'w') as fp: + # apply the replace-pairs to the input text, by finding the specified xml-elements by thier id and modify thier css-parameter the correct value + for replaceinfo in frame: + (id, key, value) = replaceinfo + + for el in svg.findall(".//*[@id='"+id.replace("'", "\\'")+"']"): + style = cssutils.parseStyle( el.attrib['style'] if 'style' in el.attrib else '' ) + style[key] = unicode(value) + el.attrib['style'] = style.cssText + + # write the generated svg-text into the output-file + fp.write( etree.tostring(svg) ) + + # invoke rsvg to convert the generated svg-file into a png inside the .frames-directory + os.system('cd {0} && rsvg-convert .gen.svg > .frames/{1:04d}.png'.format(workdir, frameNr)) + + # incrwement frame-number + frameNr += 1 + + # remove the dv we are about to (re-)generate + ensureFilesRemoved(os.path.join(workdir, outfile)) + + # invoke avconv aka ffmpeg and renerate a lossles-dv from the frames + # if we're not in debug-mode, suppress all output + os.system('cd {0} && avconv -ar 48000 -ac 2 -f s16le -i /dev/zero -f image2 -i .frames/%04d.png -target pal-dv -aspect 16:9 -shortest "{1}"'.format(workdir, outfile) + ('' if debug else '>/dev/null 2>&1')) + + # as before, in non-debug-mode the thread-worker does all progress messages + if debug: + print "cleanup" + + # remove the .frames-dir with all frames in it + shutil.rmtree(os.path.join(workdir, '.frames')) + + # remove the generated svg + ensureFilesRemoved(os.path.join(workdir, '.gen.svg')) + + + +# Download the Events-Schedule and parse all Events out of it. Yield a tupel for each Event +def events(): + print "downloading pentabarf schedule" + + # use --offline to skip networking + if offline: + # parse the offline-version + schedule = etree.parse('schedule.de.xml').getroot() + + else: + # download the schedule + response = urllib2.urlopen('http://www.fossgis.de/konferenz/2014/programm/schedule.de.xml') + + # read xml-source + xml = response.read() + + # parse into ElementTree + schedule = etree.fromstring(xml) + + # 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 + personnames = [] + for person in event.find('persons').iter('person'): + personnames.append(person.text) + + # yield a tupel with the event-id, event-title and person-names + yield ( int(event.get('id')), event.find('title').text, ', '.join(personnames) ) + + +# debug-mode selected by --debug switch +if debug: + print "!!! DEBUG MODE !!!" + title = 'OpenJUMP - Überblick, Neuigkeiten, Zusammenarbeit/Schnittstellen mit proprietärer Software' + + render( + 'vorspann.svg', + os.path.join('..', str(667)+".dv"), + vorspannFrames, + { + '$id': 667, + '$title': vorspannTitle(title), + '$personnames': 'Matthias Scholz' + } + ) + + # render( + # 'abspann.svg', + # '../outro.dv', + # abspannFrames + # ) + + # render('pause.svg', + # '../pause.dv', + # pauseFrames + # ) + + sys.exit(0) + + + +# threaded task queue +tasks = Queue() + +titlemap = { + 708: "Neue WEB-Anwendungen des LGRB Baden-Württemberg im Überblick" +} + +# iterate over all events extracted from the schedule xml-export +for (id, title, personnames) in events(): + if id in titlemap: + title = titlemap[id] + + # generate a task description and put them into the queue + tasks.put(( + 'vorspann.svg', + str(id)+".dv", + vorspannFrames, + { + '$id': id, + '$title': vorspannTitle(title), + '$personnames': personnames + } + )) + +# place a task for the outro into the queue +tasks.put(( + 'abspann.svg', + 'outro.dv', + abspannFrames +)) + +# place the pause-sequence into the queue +tasks.put(( + 'pause.svg', + 'pause.dv', + pauseFrames +)) + +# one working thread per cpu +num_worker_threads = multiprocessing.cpu_count() +print "{0} tasks in queue, starting {1} worker threads".format(tasks.qsize(), num_worker_threads) + +# put a sentinel for each thread into the queue to signal the end +for _ in range(num_worker_threads): + tasks.put(None) + +# this lock ensures, that only one thread at a time is writing to stdout +# and avoids output from multiple threads intermixing +printLock = Lock() +def tprint(str): + # aquire lock + printLock.acquire() + + # print thread-name and message + print threading.current_thread().name+': '+str + + # release lock + printLock.release() + + +# thread worker +def worker(): + # generate a tempdir for this worker-thread and use the artwork-subdir as temporary folder + tempdir = tempfile.mkdtemp() + workdir = os.path.join(tempdir, 'artwork') + + # save the current working dir as output-dir + outdir = os.getcwd() + + # print a message that we're about to initialize our environment + tprint("initializing worker in {0}, writing result to {1}".format(tempdir, outdir)) + + # copy the artwork-dir into the tempdir + shutil.copytree('artwork', workdir) + + # loop until all tasks are done (when the thread fetches a sentinal from the queue) + while True: + # fetch a task from the queue + task = tasks.get() + + # if it is a stop-sentinal break out of the loop + if task == None: + break + + # print that we're about to render a task + tprint('rendering {0}'.format(task[1])) + + # render options + opts = ( + # argument 0 is the input file. prepend the workdir + os.path.join(workdir, task[0]), + + # argument 1 is the output file. prepend the outdir + os.path.join(outdir, task[1]), + + # argument 2 is the frame generator, nothing to do here + task[2], + + # argument 3 are the extra parameters + task[3] if len(task) > 3 else {}, + + # argument 4 is the workdir path + workdir + ) + + # render with these arguments + render(*opts) + + # print that we're finished + tprint('finished {0}, {1} tasks left'.format(task[1], max(0, tasks.qsize() - num_worker_threads))) + + # mark the task as finished + tasks.task_done() + + # all tasks from the queue done, clean up + tprint("cleaning up worker") + + # remove the tempdir + shutil.rmtree(tempdir) + + # mark the sentinal as done + tasks.task_done() + + +# generate and start the threads +for i in range(num_worker_threads): + t = Thread(target=worker) + t.daemon = True + t.start() + +# wait until they finished doing the work +tasks.join() +print "all worker threads ended"