diff --git a/make.py b/make.py index 6c0834c..20ea5a0 100755 --- a/make.py +++ b/make.py @@ -56,6 +56,10 @@ Render frames using resvg instead of Inkscape. Usage: ./make.py yourproject/ --resvg ''') +parser.add_argument('--audio-streams', action="store", default=2, type=int, help=''' + Number of audio streams to generate. + Usage: ./make.py yourproject/ --audio-streams 4 + ''') if len(sys.argv) < 2: parser.print_help() diff --git a/renderlib.py b/renderlib.py index 57c9366..3f28b38 100644 --- a/renderlib.py +++ b/renderlib.py @@ -11,6 +11,7 @@ from lxml import etree from urllib.request import urlopen from wand.image import Image +from tempfile import NamedTemporaryFile # Frames per second. Increasing this renders more frames, the avconf-statements would still need modifications fps = 25 @@ -83,14 +84,18 @@ def ensureFilesRemoved(pattern): for f in glob.glob(pattern): os.unlink(f) -def renderFrame(infile, task, outfile): +def renderFrame(svg, task, outfile): width = 1920 height = 1080 + outfile = os.path.abspath(outfile) 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) + with NamedTemporaryFile(dir=task.workdir, suffix='.svg') as svgfile: + svgfile.write(svg.svgstr.encode('utf-8')) + svgfile.flush() + with Image(filename=svgfile.name) as img: + with img.convert('png') as converted: + converted.save(filename=outfile) elif args.resvg: # invoke inkscape to convert the generated svg-file into a png inside the .frames-directory cmd = 'resvg --background white --width={1} --height={2} "{4}" "{3}" 2>&1 >/dev/null'.format(task.workdir, width, height, outfile, infile) @@ -98,11 +103,10 @@ def renderFrame(infile, task, outfile): if errorReturn != '': print("resvg exited with error\n" + errorReturn) # sys.exit(42) - else: # invoke inkscape to convert the generated svg-file into a png inside the .frames-directory - cmd = 'inkscape --export-background=white --export-background-opacity=0 --export-width={1} --export-height={2} --export-filename="{3}" "{4}" --pipe 2>&1 >/dev/null'.format(task.workdir, width, height, os.path.abspath(outfile), os.path.abspath(infile)) - errorReturn = subprocess.check_output(cmd, shell=True, universal_newlines=True, stderr=subprocess.STDOUT, cwd=task.workdir) + cmd = 'inkscape --export-background=white --export-background-opacity=0 --export-width={1} --export-height={2} --export-filename="{3}" --pipe 2>&1 >/dev/null'.format(task.workdir, width, height, outfile) + errorReturn = subprocess.check_output(cmd, shell=True, universal_newlines=True, input=svg.svgstr, stderr=subprocess.STDOUT, cwd=task.workdir) if errorReturn != '': print("inkscape exited with error\n" + errorReturn) # sys.exit(42) @@ -135,26 +139,20 @@ def cachedRenderFrame(frame, frameNr, task, cache): elif not skip_rendering: cache[frame] = frameNr - svgfile = '{0}/.frames/{1:04d}.svg'.format(task.workdir, frameNr) - - with SVGTemplate(task, svgfile) as svg: + outfile = '{0}/.frames/{1:04d}.png'.format(task.workdir, frameNr) + with SVGTemplate(task) as svg: svg.replacetext() svg.transform(frame) - svg.write() - - outfile = '{0}/.frames/{1:04d}.png'.format(task.workdir, frameNr) - renderFrame(svgfile, task, outfile) + renderFrame(svg, task, outfile) # increment frame-number frameNr += 1 def rendertask_image(task): - svgfile = '{0}/image.svg'.format(task.workdir) - with SVGTemplate(task, svgfile) as svg: + with SVGTemplate(task) as svg: svg.replacetext() - svg.write() - renderFrame(svgfile, task, task.outfile) + renderFrame(svg, task, task.outfile) def rendertask_video(task): # iterate through the animation sequence frame by frame @@ -178,16 +176,18 @@ def rendertask_video(task): cmd = 'cd {0} && '.format(task.workdir) cmd += 'ffmpeg -f image2 -i .frames/%04d.png ' if task.audiofile is None: - cmd += '-ar 48000 -ac 1 -f s16le -i /dev/zero -ar 48000 -ac 1 -f s16le -i /dev/zero ' + audio_input = '-ar 48000 -ac 1 -f s16le -i /dev/zero ' else: - cmd += '-i {0} -i {0} '.format(task.audiofile) + audio_input = '-i {0} '.format(task.audiofile) + cmd += audio_input * args.audio_streams cmd += '-map 0:0 -c:v mpeg2video -q:v 2 -aspect 16:9 ' if task.audiofile is None: - cmd += '-map 1:0 -map 2:0 ' + audio_map = '-map {0}:0 ' else: - cmd += '-map 1:0 -c:a copy -map 2:0 -c:a copy ' + audio_map = '-map {0}:0 -c:a copy ' + cmd += ''.join(audio_map.format(index + 1) for index in range(args.audio_streams)) cmd += '-shortest -f mpegts "{0}"'.format(task.outfile) elif task.outfile.endswith('.mov'): cmd = 'cd {0} && '.format(task.workdir) diff --git a/svgtemplate.py b/svgtemplate.py index 4391214..57fea9d 100644 --- a/svgtemplate.py +++ b/svgtemplate.py @@ -12,21 +12,14 @@ cssutils.log.setLevel(logging.FATAL) class SVGTemplate: - def __init__(self, task, outfile): + def __init__(self, task): self.task = task - self.outfile = outfile 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(self): - # open the output-file (named ".gen.svg" in the workdir) - with builtins.open(self.outfile, 'w') as fp: - # write the generated svg-text into the output-file - fp.write(self.svgstr) - def replacetext(self): for key in self.task.parameters.keys(): self.svgstr = self.svgstr.replace(key, xmlescape(str(self.task.parameters[key]))) @@ -49,7 +42,7 @@ def transform(self, frame): # if '$subtitle' in task.parameters and task.parameters['$subtitle'] == '': # child = svg.findall(".//*[@id='subtitle']")[0] # child.getparent().remove(child) - self.svgstr = etree.tostring(svg, encoding='unicode') + self.svgstr = etree.tostring(svg).decode('UTF-8') def __exit__(self, exception_type, exception_value, traceback): pass