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( queue.put(Rendertask(
infile = 'intro.svg', infile = 'intro.svg',
outfile = str(event['id'])+".ts", outfile = str(event['id'])+".ts",
sequence = introFrames,
parameters = { parameters = {
'$ID': event['id'], '$ID': event['id'],
'$TITLE': event['title'], '$TITLE': event['title'],
'$SUBTITLE': event['subtitle'], '$SUBTITLE': event['subtitle'],
'$SPEAKER': event['personnames'] '$SPEAKER': event['personnames']
} }
)) ).animated(introFrames))
# place a task for the outro into the queue # place a task for the outro into the queue
if not "out" in skiplist: if not "out" in skiplist:
queue.put(Rendertask( queue.put(Rendertask(
infile = 'outro.svg', infile = 'outro.svg',
outfile = 'outro.ts', outfile = 'outro.ts'
sequence = outroFrames ).animated(outroFrames))
))
for person in persons(scheduleUrl, personmap, taglinemap): for person in persons(scheduleUrl, personmap, taglinemap):
queue.put(Rendertask( queue.put(Rendertask(
infile = 'insert.svg', infile = 'insert.svg',
outfile = "insert_{}.mkv".format(person['person'].replace("/", "_")), 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 = { parameters = {
'$PERSON': person['person'], '$PERSON': person['person'],
'$TAGLINE': person['tagline'], '$TAGLINE': person['tagline'],

View file

@ -40,20 +40,31 @@ def easeDelay(easer, delay, t, b, c, d, *args):
class Rendertask: 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): if isinstance(infile, list):
self.infile = infile[0] self.infile = infile[0]
# self.audiofile = infile[1] # self.audiofile = infile[1]
else: else:
self.infile = infile self.infile = infile
self.audiofile = None self.audiofile = None
self.sequence = sequence
self.parameters = parameters self.parameters = parameters
self.outfile = outfile self.outfile = outfile
self.workdir = workdir 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): 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): def ensure(input):
if isinstance(input, tuple): if isinstance(input, tuple):
@ -63,7 +74,6 @@ class Rendertask:
else: else:
return None return None
# try to create all folders needed and skip, they already exist # try to create all folders needed and skip, they already exist
def ensurePathExists(path): def ensurePathExists(path):
try: try:
@ -79,39 +89,62 @@ def ensureFilesRemoved(pattern):
os.unlink(f) os.unlink(f)
def rendertask(task): def svgTemplate_open(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
with open(os.path.join(task.workdir, task.infile), 'r') as fp: with open(os.path.join(task.workdir, task.infile), 'r') as fp:
svgstr = fp.read() return fp.read()
def svgTemplate_replacetext(svgstr, task):
for key in task.parameters.keys(): for key in task.parameters.keys():
svgstr = svgstr.replace(key, xmlescape(str(task.parameters[key]))) svgstr = svgstr.replace(key, xmlescape(str(task.parameters[key])))
return svgstr
def svgTemplate_transform(svgstr, frame, task):
parser = etree.XMLParser(huge_tree=True) parser = etree.XMLParser(huge_tree=True)
svg = etree.fromstring(svgstr.encode('utf-8'), parser) 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'] == '': # if '$subtitle' in task.parameters and task.parameters['$subtitle'] == '':
# child = svg.findall(".//*[@id='subtitle']")[0] # child = svg.findall(".//*[@id='subtitle']")[0]
# child.getparent().remove(child) # child.getparent().remove(child)
return etree.tostring(svg, encoding='unicode')
# frame-number counter def svgTemplate_write(svgstr, task):
frameNr = 0 # 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 def renderFrame(infile, task, outfile):
# frame is a ... tbd width = 1920
cache = {} height = 1080
for frame in task.sequence(task.parameters): 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_rendering = False
# skip first n frames, to speed up rerendering during debugging # skip first n frames, to speed up rerendering during debugging
if 'only_rerender_frames_after' in task.parameters: if 'only_rerender_frames_after' in task.parameters:
@ -135,58 +168,35 @@ def rendertask(task):
framedir = task.workdir + "/.frames/" framedir = task.workdir + "/.frames/"
shutil.copyfile("{0}/{1:04d}.png".format(framedir, cache[frame]), "{0}/{1:04d}.png".format(framedir, frameNr)) shutil.copyfile("{0}/{1:04d}.png".format(framedir, cache[frame]), "{0}/{1:04d}.png".format(framedir, frameNr))
frameNr += 1 return
continue
elif not skip_rendering: elif not skip_rendering:
cache[frame] = frameNr 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 svgstr = svgTemplate_open(task)
for replaceinfo in frame: svgstr = svgTemplate_replacetext(svgstr, task)
(id, type, key, value) = replaceinfo 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) outfile = '{0}/.frames/{1:04d}.png'.format(task.workdir, frameNr)
with Image(filename=infile) as img: renderFrame(svgfile, task, outfile)
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)
# increment frame-number # increment frame-number
frameNr += 1 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: if args.only_frame:
task.outfile = '{0}.frame{1:04d}.png'.format(task.outfile, 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: if r != 0:
sys.exit() 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: if not debug:
print("cleanup") print("cleanup")
# remove the generated svg # remove the generated svg
ensureFilesRemoved(os.path.join(task.workdir, '.gen.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 # Download the Events-Schedule and parse all Events out of it. Yield a tupel for each Event
def downloadSchedule(scheduleUrl): def downloadSchedule(scheduleUrl):
print("downloading schedule") print("downloading schedule")