From 44101834ae82a6fe271fb5706ca29304bc8663c8 Mon Sep 17 00:00:00 2001 From: Daniel Molkentin Date: Thu, 1 Aug 2019 23:20:32 +0200 Subject: [PATCH 1/6] refactor renderlib to allow for single frame rendering --- forumoe19/__init__.py | 19 +++-- renderlib.py | 180 ++++++++++++++++++++++++------------------ 2 files changed, 117 insertions(+), 82 deletions(-) diff --git a/forumoe19/__init__.py b/forumoe19/__init__.py index b85bffc..906ec4c 100644 --- a/forumoe19/__init__.py +++ b/forumoe19/__init__.py @@ -124,28 +124,35 @@ def tasks(queue, args, idlist, skiplist): 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'] } - )) + ).animated(introFrames)) # 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 - )) + outfile = 'outro.ts' + ).animated(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'], + } + ).animated(bbFrames)) + + for person in persons(scheduleUrl, personmap, taglinemap): + queue.put(Rendertask( + infile = 'insert.svg', + outfile = "insert_{}.png".format(person['person'].replace("/", "_")), parameters = { '$PERSON': person['person'], '$TAGLINE': person['tagline'], diff --git a/renderlib.py b/renderlib.py index d29221f..ff9e8f0 100644 --- a/renderlib.py +++ b/renderlib.py @@ -40,20 +40,31 @@ def easeDelay(easer, delay, t, b, c, d, *args): class Rendertask: - def __init__(self, infile, sequence, parameters={}, outfile=None, workdir='.'): + def __init__(self, infile, parameters={}, outfile=None, workdir='.', sequence=None): if isinstance(infile, list): self.infile = infile[0] # self.audiofile = infile[1] else: self.infile = infile self.audiofile = None - self.sequence = sequence self.parameters = parameters self.outfile = outfile self.workdir = workdir + self.sequence = sequence # deprecated, use animated() + + def animated(self, sequence): + atask = self + atask.sequence = sequence + return atask + + def is_animated(self): + return self.sequence != None def fromtupel(tuple): - return Rendertask(tuple[0], tuple[2], tuple[3], tuple[1]) + task = Rendertask(tuple[0], tuple[2], tuple[1]) + if len(tuple) > 3: + task = task.animated(tuple[3]) + return task def ensure(input): if isinstance(input, tuple): @@ -63,7 +74,6 @@ class Rendertask: else: return None - # try to create all folders needed and skip, they already exist def ensurePathExists(path): try: @@ -79,39 +89,62 @@ def ensureFilesRemoved(pattern): os.unlink(f) -def rendertask(task): - global args - # in debug mode we have no thread-worker which prints its progress - if debug: - print("generating {0} from {1}".format(task.outfile, task.infile)) - - if args.skip_frames and 'only_rerender_frames_after' not in task.parameters: - if os.path.isdir(os.path.join(task.workdir, '.frames')): - shutil.rmtree(os.path.join(task.workdir, '.frames')) - - # make sure a .frames-directory exists in out workdir - ensurePathExists(os.path.join(task.workdir, '.frames')) - - # open and parse the input file +def svgTemplate_open(task): with open(os.path.join(task.workdir, task.infile), 'r') as fp: - svgstr = fp.read() - for key in task.parameters.keys(): - svgstr = svgstr.replace(key, xmlescape(str(task.parameters[key]))) + return fp.read() - parser = etree.XMLParser(huge_tree=True) - svg = etree.fromstring(svgstr.encode('utf-8'), parser) +def svgTemplate_replacetext(svgstr, task): + for key in task.parameters.keys(): + svgstr = svgstr.replace(key, xmlescape(str(task.parameters[key]))) + return svgstr + +def svgTemplate_transform(svgstr, frame, task): + parser = etree.XMLParser(huge_tree=True) + svg = etree.fromstring(svgstr.encode('utf-8'), parser) + # apply the replace-pairs to the input text, by finding the specified xml-elements by their id and modify their css-parameter the correct value + for replaceinfo in frame: + (id, type, key, value) = replaceinfo + for el in svg.findall(".//*[@id='" + id.replace("'", "\\'") + "']"): + if type == 'style': + style = cssutils.parseStyle(el.attrib['style'] if 'style' in el.attrib else '') + style[key] = str(value) + el.attrib['style'] = style.cssText + elif type == 'attr': + el.attrib[key] = str(value) + elif type == 'text': + el.text = str(value) # if '$subtitle' in task.parameters and task.parameters['$subtitle'] == '': # child = svg.findall(".//*[@id='subtitle']")[0] # child.getparent().remove(child) + return etree.tostring(svg, encoding='unicode') - # frame-number counter - frameNr = 0 +def svgTemplate_write(svgstr, task): + # open the output-file (named ".gen.svg" in the workdir) + outfile = os.path.join(task.workdir, '.gen.svg') + with open(outfile, 'w') as fp: + # write the generated svg-text into the output-file + fp.write(svgstr) + return outfile - # iterate through the animation seqence frame by frame - # frame is a ... tbd - cache = {} - for frame in task.sequence(task.parameters): +def renderFrame(infile, task, outfile): + width = 1920 + height = 1080 + infile = '{0}/.gen.svg'.format(task.workdir) + if args.imagemagick: + # invoke imagemagick to convert the generated svg-file into a png inside the .frames-directory + with Image(filename=infile) as img: + with img.convert('png') as converted: + converted.save(filename=outfile) + else: + # invoke inkscape to convert the generated svg-file into a png inside the .frames-directory + cmd = 'cd {0} && inkscape --export-background=white --export-background-opacity=0 --export-width={1} --export-height={2} --export-png="{3}" "{4}" 2>&1 >/dev/null'.format(task.workdir, width, height, outfile, infile) + errorReturn = subprocess.check_output(cmd, shell=True, universal_newlines=True, stderr=subprocess.STDOUT) + if errorReturn != '': + print("inkscape exitted with error\n" + errorReturn) + # sys.exit(42) + +def cachedRenderFrame(frame, frameNr, task, cache): skip_rendering = False # skip first n frames, to speed up rerendering during debugging if 'only_rerender_frames_after' in task.parameters: @@ -135,58 +168,35 @@ def rendertask(task): framedir = task.workdir + "/.frames/" shutil.copyfile("{0}/{1:04d}.png".format(framedir, cache[frame]), "{0}/{1:04d}.png".format(framedir, frameNr)) - frameNr += 1 - continue + return elif not skip_rendering: cache[frame] = frameNr - # 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, type, key, value) = replaceinfo + svgstr = svgTemplate_open(task) + svgstr = svgTemplate_replacetext(svgstr, task) + svgstr = svgTemplate_transform(svgstr, frame, task) + svgfile = svgTemplate_write(svgstr, task) - for el in svg.findall(".//*[@id='" + id.replace("'", "\\'") + "']"): - if type == 'style': - style = cssutils.parseStyle(el.attrib['style'] if 'style' in el.attrib else '') - style[key] = str(value) - el.attrib['style'] = style.cssText - - elif type == 'attr': - el.attrib[key] = str(value) - - elif type == 'text': - el.text = str(value) - - if not skip_rendering: - # open the output-file (named ".gen.svg" in the workdir) - with open(os.path.join(task.workdir, '.gen.svg'), 'w') as fp: - # 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') or task.outfile.endswith('.mkv'): - width = 1920 - height = 1080 - else: - width = 1024 - height = 576 - - if args.imagemagick: - # invoke imagemagick to convert the generated svg-file into a png inside the .frames-directory - infile = '{0}/.gen.svg'.format(task.workdir) - outfile = '{0}/.frames/{1:04d}.png'.format(task.workdir, frameNr) - with Image(filename=infile) as img: - with img.convert('png') as converted: - converted.save(filename=outfile) - else: - # invoke inkscape to convert the generated svg-file into a png inside the .frames-directory - cmd = 'cd {0} && inkscape --export-background=white --export-background-opacity=0 --export-width={2} --export-height={3} --export-png=$(pwd)/.frames/{1:04d}.png $(pwd)/.gen.svg 2>&1 >/dev/null'.format(task.workdir, frameNr, width, height) - errorReturn = subprocess.check_output(cmd, shell=True, universal_newlines=True, stderr=subprocess.STDOUT) - if errorReturn != '': - print("inkscape exitted with error\n" + errorReturn) - # sys.exit(42) + outfile = '{0}/.frames/{1:04d}.png'.format(task.workdir, frameNr) + renderFrame(svgfile, task, outfile) # increment frame-number frameNr += 1 + +def rendertask_image(task): + svgstr = svgTemplate_open(task) + svgstr = svgTemplate_replacetext(svgstr, task) + svgfile = svgTemplate_write(svgstr, task) + renderFrame(svgfile, task, task.outfile) + +def rendertask_video(task): + # iterate through the animation sequence frame by frame + # frame is a ... tbd + cache = {} + for frameNr, frame in enumerate(task.sequence(task.parameters)): + cachedRenderFrame(frame, frameNr, task, cache) + if args.only_frame: task.outfile = '{0}.frame{1:04d}.png'.format(task.outfile, args.only_frame) @@ -231,15 +241,33 @@ def rendertask(task): if r != 0: sys.exit() +def rendertask(task): + global args + # in debug mode we have no thread-worker which prints its progress + if debug: + print("generating {0} from {1}".format(task.outfile, task.infile)) + + if args.skip_frames and 'only_rerender_frames_after' not in task.parameters: + if os.path.isdir(os.path.join(task.workdir, '.frames')): + shutil.rmtree(os.path.join(task.workdir, '.frames')) + + # make sure a .frames-directory exists in out workdir + ensurePathExists(os.path.join(task.workdir, '.frames')) + + if task.is_animated(): + rendertask_video(task) + else: + rendertask_image(task) + if not debug: print("cleanup") # remove the generated svg ensureFilesRemoved(os.path.join(task.workdir, '.gen.svg')) + + # Download the Events-Schedule and parse all Events out of it. Yield a tupel for each Event - - def downloadSchedule(scheduleUrl): print("downloading schedule") @@ -322,7 +350,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 } From d6f1f14207c75723a9b39c5c09bd5d9194926994 Mon Sep 17 00:00:00 2001 From: Daniel Molkentin Date: Thu, 1 Aug 2019 23:22:56 +0200 Subject: [PATCH 2/6] ignore pngs not in artwork --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 87676a5..b5f2439 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ *.mp4 *.dv *.ts +*.mkv *.mov +/*/*.png /*.png *.pyc schedule.de.xml From b6e3ef39e8fa829826963514c7b85f7f53d539d9 Mon Sep 17 00:00:00 2001 From: Daniel Molkentin Date: Thu, 1 Aug 2019 23:39:00 +0200 Subject: [PATCH 3/6] refactor svgtemplate into seperate file --- renderlib.py | 59 ++++++++------------------------------------------ svgtemplate.py | 43 ++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 50 deletions(-) create mode 100644 svgtemplate.py diff --git a/renderlib.py b/renderlib.py index ff9e8f0..5c26035 100644 --- a/renderlib.py +++ b/renderlib.py @@ -1,4 +1,3 @@ -#!/usr/bin/python3 # vim: tabstop=4 shiftwidth=4 expandtab import os @@ -7,11 +6,10 @@ import re import glob import shutil import errno -from lxml import etree -from xml.sax.saxutils import escape as xmlescape -import cssutils import logging import subprocess +import svgtemplate +from lxml import etree from urllib.request import urlopen from wand.image import Image @@ -88,45 +86,6 @@ def ensureFilesRemoved(pattern): for f in glob.glob(pattern): os.unlink(f) - -def svgTemplate_open(task): - with open(os.path.join(task.workdir, task.infile), 'r') as fp: - return fp.read() - - -def svgTemplate_replacetext(svgstr, task): - for key in task.parameters.keys(): - svgstr = svgstr.replace(key, xmlescape(str(task.parameters[key]))) - return svgstr - -def svgTemplate_transform(svgstr, frame, task): - parser = etree.XMLParser(huge_tree=True) - svg = etree.fromstring(svgstr.encode('utf-8'), parser) - # apply the replace-pairs to the input text, by finding the specified xml-elements by their id and modify their css-parameter the correct value - for replaceinfo in frame: - (id, type, key, value) = replaceinfo - for el in svg.findall(".//*[@id='" + id.replace("'", "\\'") + "']"): - if type == 'style': - style = cssutils.parseStyle(el.attrib['style'] if 'style' in el.attrib else '') - style[key] = str(value) - el.attrib['style'] = style.cssText - elif type == 'attr': - el.attrib[key] = str(value) - elif type == 'text': - el.text = str(value) - # if '$subtitle' in task.parameters and task.parameters['$subtitle'] == '': - # child = svg.findall(".//*[@id='subtitle']")[0] - # child.getparent().remove(child) - return etree.tostring(svg, encoding='unicode') - -def svgTemplate_write(svgstr, task): - # open the output-file (named ".gen.svg" in the workdir) - outfile = os.path.join(task.workdir, '.gen.svg') - with open(outfile, 'w') as fp: - # write the generated svg-text into the output-file - fp.write(svgstr) - return outfile - def renderFrame(infile, task, outfile): width = 1920 height = 1080 @@ -172,10 +131,10 @@ def cachedRenderFrame(frame, frameNr, task, cache): elif not skip_rendering: cache[frame] = frameNr - svgstr = svgTemplate_open(task) - svgstr = svgTemplate_replacetext(svgstr, task) - svgstr = svgTemplate_transform(svgstr, frame, task) - svgfile = svgTemplate_write(svgstr, task) + svgstr = svgtemplate.open(task) + svgstr = svgtemplate.replacetext(svgstr, task) + svgstr = svgtemplate.transform(svgstr, frame, task) + svgfile = svgtemplate.write(svgstr, task) outfile = '{0}/.frames/{1:04d}.png'.format(task.workdir, frameNr) renderFrame(svgfile, task, outfile) @@ -185,9 +144,9 @@ def cachedRenderFrame(frame, frameNr, task, cache): def rendertask_image(task): - svgstr = svgTemplate_open(task) - svgstr = svgTemplate_replacetext(svgstr, task) - svgfile = svgTemplate_write(svgstr, task) + svgstr = svgtemplate.open(task) + svgstr = svgtemplate.replacetext(svgstr, task) + svgfile = svgtemplate.write(svgstr, task) renderFrame(svgfile, task, task.outfile) def rendertask_video(task): diff --git a/svgtemplate.py b/svgtemplate.py new file mode 100644 index 0000000..f774fe3 --- /dev/null +++ b/svgtemplate.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 expandtab +import builtins +import cssutils +import os +from lxml import etree +from xml.sax.saxutils import escape as xmlescape + +def open(task): + with builtins.open(os.path.join(task.workdir, task.infile), 'r') as fp: + return fp.read() + +def replacetext(svgstr, task): + for key in task.parameters.keys(): + svgstr = svgstr.replace(key, xmlescape(str(task.parameters[key]))) + return svgstr + +def transform(svgstr, frame, task): + parser = etree.XMLParser(huge_tree=True) + svg = etree.fromstring(svgstr.encode('utf-8'), parser) + # apply the replace-pairs to the input text, by finding the specified xml-elements by their id and modify their css-parameter the correct value + for replaceinfo in frame: + (id, type, key, value) = replaceinfo + for el in svg.findall(".//*[@id='" + id.replace("'", "\\'") + "']"): + if type == 'style': + style = cssutils.parseStyle(el.attrib['style'] if 'style' in el.attrib else '') + style[key] = str(value) + el.attrib['style'] = style.cssText + elif type == 'attr': + el.attrib[key] = str(value) + elif type == 'text': + el.text = str(value) + # if '$subtitle' in task.parameters and task.parameters['$subtitle'] == '': + # child = svg.findall(".//*[@id='subtitle']")[0] + # child.getparent().remove(child) + return etree.tostring(svg, encoding='unicode') + +def write(svgstr, task): + # open the output-file (named ".gen.svg" in the workdir) + outfile = os.path.join(task.workdir, '.gen.svg') + with builtins.open(outfile, 'w') as fp: + # write the generated svg-text into the output-file + fp.write(svgstr) + return outfile From a05718366668880f596a7ef402d60eb1934d26cd Mon Sep 17 00:00:00 2001 From: Daniel Molkentin Date: Fri, 2 Aug 2019 00:10:49 +0200 Subject: [PATCH 4/6] Refactor svgtemplate into a neat context class SVGTemplate --- renderlib.py | 21 ++++++-------- svgtemplate.py | 77 ++++++++++++++++++++++++++++---------------------- 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/renderlib.py b/renderlib.py index 5c26035..0f99adf 100644 --- a/renderlib.py +++ b/renderlib.py @@ -6,9 +6,8 @@ import re import glob import shutil import errno -import logging import subprocess -import svgtemplate +from svgtemplate import SVGTemplate from lxml import etree from urllib.request import urlopen from wand.image import Image @@ -18,10 +17,6 @@ fps = 25 debug = True args = None -cssutils.ser.prefs.lineSeparator = ' ' -cssutils.log.setLevel(logging.FATAL) - - def loadProject(projectname): sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), projectname)) return __import__(projectname) @@ -131,10 +126,10 @@ def cachedRenderFrame(frame, frameNr, task, cache): elif not skip_rendering: cache[frame] = frameNr - svgstr = svgtemplate.open(task) - svgstr = svgtemplate.replacetext(svgstr, task) - svgstr = svgtemplate.transform(svgstr, frame, task) - svgfile = svgtemplate.write(svgstr, task) + with SVGTemplate(task) as svg: + svg.replacetext() + svg.transform(frame) + svgfile = svg.write() outfile = '{0}/.frames/{1:04d}.png'.format(task.workdir, frameNr) renderFrame(svgfile, task, outfile) @@ -144,9 +139,9 @@ def cachedRenderFrame(frame, frameNr, task, cache): def rendertask_image(task): - svgstr = svgtemplate.open(task) - svgstr = svgtemplate.replacetext(svgstr, task) - svgfile = svgtemplate.write(svgstr, task) + with SVGTemplate(task) as svg: + svg.replacetext() + svgfile = svg.write() renderFrame(svgfile, task, task.outfile) def rendertask_video(task): diff --git a/svgtemplate.py b/svgtemplate.py index f774fe3..f6797b0 100644 --- a/svgtemplate.py +++ b/svgtemplate.py @@ -1,43 +1,54 @@ # vim: tabstop=4 shiftwidth=4 expandtab import builtins import cssutils +import logging import os from lxml import etree from xml.sax.saxutils import escape as xmlescape -def open(task): - with builtins.open(os.path.join(task.workdir, task.infile), 'r') as fp: - return fp.read() +cssutils.ser.prefs.lineSeparator = ' ' +cssutils.log.setLevel(logging.FATAL) -def replacetext(svgstr, task): - for key in task.parameters.keys(): - svgstr = svgstr.replace(key, xmlescape(str(task.parameters[key]))) - return svgstr +class SVGTemplate: + def __init__(self, task): + self.task = task -def transform(svgstr, frame, task): - parser = etree.XMLParser(huge_tree=True) - svg = etree.fromstring(svgstr.encode('utf-8'), parser) - # apply the replace-pairs to the input text, by finding the specified xml-elements by their id and modify their css-parameter the correct value - for replaceinfo in frame: - (id, type, key, value) = replaceinfo - for el in svg.findall(".//*[@id='" + id.replace("'", "\\'") + "']"): - if type == 'style': - style = cssutils.parseStyle(el.attrib['style'] if 'style' in el.attrib else '') - style[key] = str(value) - el.attrib['style'] = style.cssText - elif type == 'attr': - el.attrib[key] = str(value) - elif type == 'text': - el.text = str(value) - # if '$subtitle' in task.parameters and task.parameters['$subtitle'] == '': - # child = svg.findall(".//*[@id='subtitle']")[0] - # child.getparent().remove(child) - return etree.tostring(svg, encoding='unicode') + def __enter__(self): + with builtins.open(os.path.join(self.task.workdir, self.task.infile), 'r') as fp: + self.svgstr = fp.read() + return self -def write(svgstr, task): - # open the output-file (named ".gen.svg" in the workdir) - outfile = os.path.join(task.workdir, '.gen.svg') - with builtins.open(outfile, 'w') as fp: - # write the generated svg-text into the output-file - fp.write(svgstr) - return outfile + def write(self): + # open the output-file (named ".gen.svg" in the workdir) + outfile = os.path.join(self.task.workdir, '.gen.svg') + with builtins.open(outfile, 'w') as fp: + # write the generated svg-text into the output-file + fp.write(self.svgstr) + return outfile + + def replacetext(self): + for key in self.task.parameters.keys(): + self.svgstr = self.svgstr.replace(key, xmlescape(str(self.task.parameters[key]))) + + def transform(self, frame): + parser = etree.XMLParser(huge_tree=True) + svg = etree.fromstring(self.svgstr.encode('utf-8'), parser) + # apply the replace-pairs to the input text, by finding the specified xml-elements by their id and modify their css-parameter the correct value + for replaceinfo in frame: + (id, type, key, value) = replaceinfo + for el in svg.findall(".//*[@id='" + id.replace("'", "\\'") + "']"): + if type == 'style': + style = cssutils.parseStyle(el.attrib['style'] if 'style' in el.attrib else '') + style[key] = str(value) + el.attrib['style'] = style.cssText + elif type == 'attr': + el.attrib[key] = str(value) + elif type == 'text': + el.text = str(value) + # if '$subtitle' in task.parameters and task.parameters['$subtitle'] == '': + # child = svg.findall(".//*[@id='subtitle']")[0] + # child.getparent().remove(child) + self.xmlstr = etree.tostring(svg, encoding='unicode') + + def __exit__(self, exception_type, exception_value, traceback): + pass From 4956a1a1d6a92007bafdab09b2b320e162e4d9af Mon Sep 17 00:00:00 2001 From: Daniel Molkentin Date: Fri, 16 Aug 2019 11:36:23 +0200 Subject: [PATCH 5/6] Add suselabs18 that I forgot to commit --- suselabs18/__init__.py | 129 ++ suselabs18/artwork/intro.svg | 3378 ++++++++++++++++++++++++++++++++++ suselabs18/artwork/outro.svg | 436 +++++ suselabs18/artwork/pause.svg | 180 ++ 4 files changed, 4123 insertions(+) create mode 100644 suselabs18/__init__.py create mode 100644 suselabs18/artwork/intro.svg create mode 100644 suselabs18/artwork/outro.svg create mode 100644 suselabs18/artwork/pause.svg diff --git a/suselabs18/__init__.py b/suselabs18/__init__.py new file mode 100644 index 0000000..0b4687e --- /dev/null +++ b/suselabs18/__init__.py @@ -0,0 +1,129 @@ +#!/usr/bin/python + +from renderlib import * +from easing import * + +# URL to Schedule-XML +scheduleUrl = 'https://live.ber.c3voc.de/releases/suselabs18/schedule.xml' + +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): + # 3 Sekunde Text Fadein + frames = 1*fps + for i in range(0, frames): + yield ( + ('textblock', 'style', 'opacity', "%.4f" % easeLinear(i, 0, 1, frames)), + ('fadeout', 'style', 'opacity', "%.4f" % 0), + ) + + # 4 Sekunden stehen lassen + frames = 4*fps + for i in range(0, frames): + yield ( + ('fadeout', 'style', 'opacity', "%.4f" % 0), + ) + + # 1 Sekunde Fade to black layer + frames = 1*fps + for i in range(0, frames): + yield ( + ('fadeout', 'style', 'opacity', "%.4f" % easeOutCubic(i, 0, 1, frames)), + ) + +def pauseFrames(parameters): + frames = 3*fps + colors = ['#21A4D4', '#73BA25', '#6DA741', '#35B9AB', '#00A489', '#173F4F'] + yield ( + ('pause_bg', 'style', 'fill', "%s" % '#173F4F'), + ('pause_bg', 'attr', 'opacity', '%.4f' % 1.0), + ) + for i in range(0, len(colors)): + z = 0 + for z in range(0,frames): + yield ( + ('pause_bg_alt', 'style', 'fill', "%s" % colors[i]), + ('pause_bg_alt', 'attr', 'opacity', '%.4f' % easeLinear(z, 0.0, 1.0, frames)), + ) + yield ( + ('pause_bg', 'style', 'fill', "%s" % colors[i]), + ('pause_bg', 'attr', 'opacity', '%.4f' % 1.0), + ) + +def outroFrames(p): + # 5 Sekunden stehen bleiben + frames = 5*fps + for i in range(0, frames): + yield [] + +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( + '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 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 + )) + + # place the pause-sequence into the queue + if not "pause" in skiplist: + queue.put(Rendertask( + infile = 'pause.svg', + outfile = 'pause.ts', + sequence = pauseFrames + )) diff --git a/suselabs18/artwork/intro.svg b/suselabs18/artwork/intro.svg new file mode 100644 index 0000000..fee1795 --- /dev/null +++ b/suselabs18/artwork/intro.svg @@ -0,0 +1,3378 @@ + + + +image/svg+xml + +$TITLE$SPEAKER$TITLE2018 + \ No newline at end of file diff --git a/suselabs18/artwork/outro.svg b/suselabs18/artwork/outro.svg new file mode 100644 index 0000000..0be25ef --- /dev/null +++ b/suselabs18/artwork/outro.svg @@ -0,0 +1,436 @@ + + + +image/svg+xml + +SUSE Labs Conference 2018was brought to you by: +Video +Team +c3voc.de +2018 + \ No newline at end of file diff --git a/suselabs18/artwork/pause.svg b/suselabs18/artwork/pause.svg new file mode 100644 index 0000000..e601797 --- /dev/null +++ b/suselabs18/artwork/pause.svg @@ -0,0 +1,180 @@ + + + +image/svg+xmlPause + \ No newline at end of file From 655c3978875c658ec1e209a92b2930ecc0f8cea3 Mon Sep 17 00:00:00 2001 From: derchris Date: Tue, 20 Aug 2019 17:42:48 +0200 Subject: [PATCH 6/6] cccamp19: add schedule to config file --- cccamp19/config.ini | 1 + make-ffmpeg.py | 65 +++++++++++++++++++++++---------------------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/cccamp19/config.ini b/cccamp19/config.ini index 838379d..c1bbba0 100644 --- a/cccamp19/config.ini +++ b/cccamp19/config.ini @@ -1,4 +1,5 @@ [default] +schedule = https://fahrplan.events.ccc.de/camp/2019/Fahrplan/schedule.xml template = cccamp19_talks_intro_1080p.mov [title] diff --git a/make-ffmpeg.py b/make-ffmpeg.py index 0b13287..6d048f3 100755 --- a/make-ffmpeg.py +++ b/make-ffmpeg.py @@ -9,25 +9,21 @@ import argparse import shlex from PIL import ImageFont from configparser import ConfigParser +import json # Parse arguments parser = argparse.ArgumentParser( description='C3VOC Intro-Outro-Generator - Variant which renders only using video filters in ffmpeg', - usage="./make-ffmpeg.py yourproject/ https://url/to/schedule.xml", + usage="./make-ffmpeg.py yourproject/", formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('project', action="store", metavar='Project folder', type=str, help=''' Path to your project folder ''') -parser.add_argument('schedule', action="store", metavar='Schedule-URL', type=str, nargs='?', help=''' - URL or Path to your schedule.xml - ''') - parser.add_argument('--debug', action="store_true", default=False, help=''' Run script in debug mode and render with placeholder texts, - not parsing or accessing a schedule. Schedule-URL can be left blank when - used with --debug + not parsing or accessing a schedule. This argument must not be used together with --id Usage: ./make-ffmpeg.py yourproject/ --debug ''') @@ -71,30 +67,6 @@ def error(str): parser.print_help() sys.exit(1) - -if not (os.path.exists(os.path.join(args.project, 'config.ini'))): - error("config.ini file in Project Path is missing") - -if not args.project: - error("The Project Path is a required argument") - -if not args.debug and not args.schedule: - error("Either specify --debug or supply a schedule") - -if args.debug: - persons = ['Thomas Roth', 'Dmitry Nedospasov', 'Josh Datko'] - events = [{ - 'id': 'debug', - 'title': 'wallet.fail', - 'subtitle': 'Hacking the most popular cryptocurrency hardware wallets', - 'persons': persons, - 'personnames': ', '.join(persons), - 'room': 'Borg', - }] - -else: - events = list(renderlib.events(args.schedule)) - parser = ConfigParser() parser.read(os.path.join(os.path.dirname(args.project), 'config.ini')) template = parser['default']['template'] @@ -131,6 +103,31 @@ font_tt = os.path.join(os.path.dirname(args.project), text_font) fileformat = os.path.splitext(template)[1] infile = os.path.join(os.path.dirname(args.project), template) +schedule = parser['default']['schedule'] + +if not (os.path.exists(os.path.join(args.project, 'config.ini'))): + error("config.ini file in Project Path is missing") + +if not args.project: + error("The Project Path is a required argument") + +if not args.debug and not schedule: + error("Either specify --debug or supply a schedule in config.ini") + +if args.debug: + persons = ['Thomas Roth', 'Dmitry Nedospasov', 'Josh Datko'] + events = [{ + 'id': 'debug', + 'title': 'wallet.fail', + 'subtitle': 'Hacking the most popular cryptocurrency hardware wallets', + 'persons': persons, + 'personnames': ', '.join(persons), + 'room': 'Borg', + }] + +else: + events = list(renderlib.events(schedule)) + def describe_event(event): return "#{}: {}".format(event['id'], event['title']) @@ -202,6 +199,9 @@ def enqueue_job(event): event_title = str(event['title']) event_personnames = str(event['personnames']) + event_title = event_title.replace('"', '') + event_title = event_title.replace('\'', '') + event_personnames = event_personnames.replace('"', '') t = fit_title(event_title) s = fit_speaker(event_personnames) @@ -220,7 +220,8 @@ def enqueue_job(event): videofilter += "drawtext=enable='between(n,{0},{1})':fontfile={2}:fontsize={3}:fontcolor={4}:x={5}:y={6}:text={7}".format(text_in, text_out, font_tt, text_fontsize, text_fontcolor, text_x, text_y, text_text) if fileformat == '.mov': - cmd = 'ffmpeg -y -i "{0}" -vf "{1}" -vcodec prores_ks -pix_fmt yuva444p10le -profile:v 4444 -shortest -movflags faststart -f mov "{2}"'.format(infile, videofilter, outfile) + cmd = 'ffmpeg -y -i "{0}" -vf "{1}" -shortest -c:v qtrle -movflags faststart -f mov "{2}"'.format(infile, videofilter, outfile) + #cmd = 'ffmpeg -y -i "{0}" -vf "{1}" -vcodec prores_ks -pix_fmt yuva444p10le -profile:v 4444 -shortest -movflags faststart -f mov "{2}"'.format(infile, videofilter, outfile) else: cmd = 'ffmpeg -y -i "{0}" -vf "{1}" -map 0:0 -c:v mpeg2video -q:v 2 -aspect 16:9 -map 0:1 -c:a mp2 -b:a 384k -shortest -f mpegts "{2}"'.format(infile, videofilter, outfile) if args.debug: