refactor renderlib to allow for single frame rendering
This commit is contained in:
parent
aac613e920
commit
44101834ae
2 changed files with 117 additions and 82 deletions
|
@ -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'],
|
||||||
|
|
180
renderlib.py
180
renderlib.py
|
@ -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()
|
||||||
for key in task.parameters.keys():
|
|
||||||
svgstr = svgstr.replace(key, xmlescape(str(task.parameters[key])))
|
|
||||||
|
|
||||||
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'] == '':
|
# 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("'", "\\'") + "']"):
|
outfile = '{0}/.frames/{1:04d}.png'.format(task.workdir, frameNr)
|
||||||
if type == 'style':
|
renderFrame(svgfile, task, outfile)
|
||||||
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)
|
|
||||||
|
|
||||||
# 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")
|
||||||
|
|
||||||
|
@ -322,7 +350,7 @@ def events(scheduleUrl, titlemap={}):
|
||||||
'personnames': ', '.join(personnames),
|
'personnames': ', '.join(personnames),
|
||||||
'room': room.attrib['name'],
|
'room': room.attrib['name'],
|
||||||
'track': event.find('track').text,
|
'track': event.find('track').text,
|
||||||
#'url': event.find('url').text
|
#'url': event.find('url').text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue