refactor renderlib to allow for single frame rendering

This commit is contained in:
Daniel Molkentin 2019-08-01 23:20:32 +02:00
parent aac613e920
commit 44101834ae
2 changed files with 117 additions and 82 deletions

View file

@ -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'],

View file

@ -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
}