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(
|
||||
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'],
|
||||
|
|
180
renderlib.py
180
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
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue