flake8 code cleanup, minor fixes

This commit is contained in:
derchris 2019-01-20 06:15:57 +01:00
parent b9eb2d234e
commit 43f08547f0
4 changed files with 606 additions and 583 deletions

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# vim: tabstop=4 shiftwidth=4 expandtab
import subprocess import subprocess
import renderlib import renderlib
@ -8,227 +9,235 @@ import shlex
import time import time
import sys import sys
import os import os
import re
import platform import platform
from xml.sax.saxutils import escape as xmlescape
from shutil import copyfile from shutil import copyfile
# Parse arguments # Parse arguments
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='C3VOC Intro-Outro-Generator - Variant to use with Adobe After Effects Files', description='C3VOC Intro-Outro-Generator - Variant to use with Adobe After Effects Files',
usage="./make-adobe-after-effects.py yourproject/ https://url/to/schedule.xml", usage="./make-adobe-after-effects.py yourproject/ https://url/to/schedule.xml",
formatter_class=argparse.RawTextHelpFormatter) formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('project', action="store", metavar='Project folder', type=str, help=''' parser.add_argument('project', action="store", metavar='Project folder', type=str, help='''
Path to your project folder with After Effects Files (intro.aep/scpt/jsx) Path to your project folder with After Effects Files (intro.aep/scpt/jsx)
''') ''')
parser.add_argument('schedule', action="store", metavar='Schedule-URL', type=str, nargs='?', help=''' parser.add_argument('schedule', action="store", metavar='Schedule-URL', type=str, nargs='?', help='''
URL or Path to your schedule.xml URL or Path to your schedule.xml
''') ''')
parser.add_argument('--debug', action="store_true", default=False, help=''' parser.add_argument('--debug', action="store_true", default=False, help='''
Run script in debug mode and render with placeholder texts, Run script in debug mode and render with placeholder texts,
not parsing or accessing a schedule. Schedule-URL can be left blank when not parsing or accessing a schedule. Schedule-URL can be left blank when
used with --debug used with --debug
This argument must not be used together with --id This argument must not be used together with --id
Usage: ./make-adobe-after-effects.py yourproject/ --debug Usage: ./make-adobe-after-effects.py yourproject/ --debug
''') ''')
parser.add_argument('--id', dest='ids', nargs='+', action="store", type=int, help=''' parser.add_argument('--id', dest='ids', nargs='+', action="store", type=int, help='''
Only render the given ID(s) from your projects schedule. Only render the given ID(s) from your projects schedule.
This argument must not be used together with --debug This argument must not be used together with --debug
Usage: ./make-adobe-after-effects.py yourproject/ --id 4711 0815 4223 1337 Usage: ./make-adobe-after-effects.py yourproject/ --id 4711 0815 4223 1337
''') ''')
parser.add_argument('--room', dest='rooms', nargs='+', action="store", type=str, help=''' parser.add_argument('--room', dest='rooms', nargs='+', action="store", type=str, help='''
Only render the given room(s) from your projects schedule. Only render the given room(s) from your projects schedule.
This argument must not be used together with --debug This argument must not be used together with --debug
Usage: ./make-adobe-after-effects.py yourproject/ --room "HfG_Studio" "ZKM_Vortragssaal" Usage: ./make-adobe-after-effects.py yourproject/ --room "HfG_Studio" "ZKM_Vortragssaal"
''') ''')
parser.add_argument('--pause', action="store_true", default=False, help=''' parser.add_argument('--pause', action="store_true", default=False, help='''
Render a pause loop from the pause.aep file in the project folder. Render a pause loop from the pause.aep file in the project folder.
''') ''')
parser.add_argument('--force', action="store_true", default=False, help=''' parser.add_argument('--force', action="store_true", default=False, help='''
Force render if file exists. Force render if file exists.
''') ''')
parser.add_argument('--no-finalize', dest='nof', action="store_true", default=False, help=''' parser.add_argument('--no-finalize', dest='nof', action="store_true", default=False, help='''
Skip finalize job. Skip finalize job.
''') ''')
parser.add_argument('--outro', action="store_true", default=False, help=''' parser.add_argument('--outro', action="store_true", default=False, help='''
Render outro from the outro.aep file in the project folder. Render outro from the outro.aep file in the project folder.
''') ''')
parser.add_argument('--bgloop', action="store_true", default=False, help=''' parser.add_argument('--bgloop', action="store_true", default=False, help='''
Render background loop from the bgloop.aep file in the project folder. Render background loop from the bgloop.aep file in the project folder.
''') ''')
args = parser.parse_args() args = parser.parse_args()
def headline(str): def headline(str):
print("##################################################") print("##################################################")
print(str) print(str)
print("##################################################") print("##################################################")
print() print()
def error(str): def error(str):
headline(str) headline(str)
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
if not args.project: if not args.project:
error("The Path to your project with After Effect Files is a required argument") error("The Path to your project with After Effect Files is a required argument")
if not args.debug and not args.pause and not args.outro and not args.bgloop and not args.schedule: if not args.debug and not args.pause and not args.outro and not args.bgloop and not args.schedule:
error("Either specify --debug, --pause, --outro or supply a schedule") error("Either specify --debug, --pause, --outro or supply a schedule")
if args.debug: if args.debug:
persons = ['watz'] persons = ['watz']
events = [{ events = [{
'id': 1, 'id': 1,
'title': 'Eröffnungsveranstaltung', 'title': 'Eröffnungsveranstaltung',
'subtitle': 'Easterhegg 2018', 'subtitle': 'Easterhegg 2018',
'persons': persons, 'persons': persons,
'personnames': ', '.join(persons), 'personnames': ', '.join(persons),
'room': 'Heisenberg 1', 'room': 'Heisenberg 1',
}] }]
elif args.pause: elif args.pause:
events = [{ events = [{
'id': 'pause', 'id': 'pause',
'title': 'Pause Loop', 'title': 'Pause Loop',
}] }]
elif args.outro: elif args.outro:
events = [{ events = [{
'id': 'outro', 'id': 'outro',
'title': 'Outro', 'title': 'Outro',
}] }]
elif args.bgloop: elif args.bgloop:
events = [{ events = [{
'id': 'bgloop', 'id': 'bgloop',
'title': 'Background Loop', 'title': 'Background Loop',
}] }]
else: else:
events = list(renderlib.events(args.schedule)) events = list(renderlib.events(args.schedule))
def describe_event(event): def describe_event(event):
return "#{}: {}".format(event['id'], event['title']) return "#{}: {}".format(event['id'], event['title'])
def event_print(event, message): def event_print(event, message):
print("{} {}".format(describe_event(event), message)) print("{} {}".format(describe_event(event), message))
tempdir = tempfile.TemporaryDirectory() tempdir = tempfile.TemporaryDirectory()
print('working in '+tempdir.name) print('working in ' + tempdir.name)
def fmt_command(command, **kwargs): def fmt_command(command, **kwargs):
args = {} args = {}
for key, value in kwargs.items(): for key, value in kwargs.items():
args[key] = shlex.quote(value) args[key] = shlex.quote(value)
command = command.format(**args)
return shlex.split(command)
command = command.format(**args)
return shlex.split(command)
def run_once(command, **kwargs): def run_once(command, **kwargs):
DETACHED_PROCESS = 0x00000008 DETACHED_PROCESS = 0x00000008
return subprocess.Popen( return subprocess.Popen(
fmt_command(command, **kwargs), fmt_command(command, **kwargs),
shell=False, shell=False,
stdin=None, stdin=None,
stdout=None, stdout=None,
stderr=None, stderr=None,
close_fds=True, close_fds=True,
creationflags=DETACHED_PROCESS) creationflags=DETACHED_PROCESS)
def run(command, **kwargs): def run(command, **kwargs):
return subprocess.check_call( return subprocess.check_call(
fmt_command(command, **kwargs), fmt_command(command, **kwargs),
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
stdout=subprocess.DEVNULL) stdout=subprocess.DEVNULL)
def enqueue_job(event): def enqueue_job(event):
event_id = str(event['id']) event_id = str(event['id'])
if os.path.exists(os.path.join(args.project, event_id+'.ts')) or os.path.exists(os.path.join(args.project, event_id+'.mov')) and not args.force: if os.path.exists(os.path.join(args.project, event_id + '.ts')) or os.path.exists(os.path.join(args.project, event_id + '.mov')) and not args.force:
event_print(event, "file exist, skipping "+str(event['id'])) event_print(event, "file exist, skipping " + str(event['id']))
return return
work_doc = os.path.join(tempdir.name, event_id+'.aep') work_doc = os.path.join(tempdir.name, event_id + '.aep')
script_doc = os.path.join(tempdir.name, event_id+'.jsx') script_doc = os.path.join(tempdir.name, event_id + '.jsx')
ascript_doc = os.path.join(tempdir.name, event_id+'.scpt') ascript_doc = os.path.join(tempdir.name, event_id + '.scpt')
intermediate_clip = os.path.join(tempdir.name, event_id+'.mov') intermediate_clip = os.path.join(tempdir.name, event_id + '.mov')
if event_id == 'pause' or event_id == 'outro' or event_id == 'bgloop': if event_id == 'pause' or event_id == 'outro' or event_id == 'bgloop':
copyfile(args.project+event_id+'.aep',work_doc) copyfile(args.project + event_id + '.aep', work_doc)
if platform.system() == 'Darwin': if platform.system() == 'Darwin':
run('/Applications/Adobe\ After\ Effects\ CC\ 2018/aerender -project {jobpath} -comp {comp} -output {locationpath}', run(r'/Applications/Adobe\ After\ Effects\ CC\ 2018/aerender -project {jobpath} -comp {comp} -output {locationpath}',
jobpath=work_doc, jobpath=work_doc,
comp=event_id, comp=event_id,
locationpath=intermediate_clip) locationpath=intermediate_clip)
if platform.system() == 'Windows': if platform.system() == 'Windows':
run('C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/aerender.exe -project {jobpath} -comp {comp} -output {locationpath}', run(r'C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/aerender.exe -project {jobpath} -comp {comp} -output {locationpath}',
jobpath=work_doc, jobpath=work_doc,
comp=event_id, comp=event_id,
locationpath=intermediate_clip) locationpath=intermediate_clip)
else: else:
with open(args.project+'intro.jsx', 'r') as fp: with open(args.project + 'intro.jsx', 'r') as fp:
scriptstr = fp.read() scriptstr = fp.read()
for key, value in event.items(): for key, value in event.items():
value = str(value).replace('"', '\\"') value = str(value).replace('"', '\\"')
scriptstr = scriptstr.replace("$"+str(key), value) scriptstr = scriptstr.replace("$" + str(key), value)
with open(script_doc, 'w', encoding='utf-8') as fp: with open(script_doc, 'w', encoding='utf-8') as fp:
fp.write(scriptstr) fp.write(scriptstr)
copyfile(args.project+'intro.aep',work_doc) copyfile(args.project + 'intro.aep', work_doc)
if platform.system() == 'Darwin': if platform.system() == 'Darwin':
copyfile(args.project+'intro.scpt',ascript_doc) copyfile(args.project + 'intro.scpt', ascript_doc)
run('osascript {ascript_path} {jobpath} {scriptpath}', run('osascript {ascript_path} {jobpath} {scriptpath}',
jobpath=work_doc, jobpath=work_doc,
scriptpath=script_doc, scriptpath=script_doc,
ascript_path=ascript_doc) ascript_path=ascript_doc)
run('/Applications/Adobe\ After\ Effects\ CC\ 2018/aerender -project {jobpath} -comp "intro" -output {locationpath}', run(r'/Applications/Adobe\ After\ Effects\ CC\ 2018/aerender -project {jobpath} -comp "intro" -output {locationpath}',
jobpath=work_doc, jobpath=work_doc,
locationpath=intermediate_clip) locationpath=intermediate_clip)
if platform.system() == 'Windows': if platform.system() == 'Windows':
run_once('C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/AfterFX.exe -noui {jobpath}', run_once(r'C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/AfterFX.exe -noui {jobpath}',
jobpath=work_doc) jobpath=work_doc)
time.sleep(15) time.sleep(15)
run_once('C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/AfterFX.exe -noui -r {scriptpath}', run_once(r'C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/AfterFX.exe -noui -r {scriptpath}',
scriptpath=script_doc) scriptpath=script_doc)
time.sleep(5) time.sleep(5)
run('C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/aerender.exe -project {jobpath} -comp "intro" -output {locationpath}', run(r'C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/aerender.exe -project {jobpath} -comp "intro" -output {locationpath}',
jobpath=work_doc, jobpath=work_doc,
locationpath=intermediate_clip) locationpath=intermediate_clip)
return event_id
return event_id
def finalize_job(job_id, event): def finalize_job(job_id, event):
event_id = str(event['id']) event_id = str(event['id'])
intermediate_clip = os.path.join(tempdir.name, event_id+'.mov') intermediate_clip = os.path.join(tempdir.name, event_id + '.mov')
final_clip = os.path.join(os.path.dirname(args.project), event_id+'.ts') final_clip = os.path.join(os.path.dirname(args.project), event_id + '.ts')
run('ffmpeg -y -hide_banner -loglevel error -i {input} -f lavfi -i anullsrc -ar 48000 -ac 2 -map 0:v -c:v mpeg2video -q:v 0 -aspect 16:9 -map 1:a -map 1:a -map 1:a -map 1:a -shortest -f mpegts {output}', run('ffmpeg -y -hide_banner -loglevel error -i {input} -f lavfi -i anullsrc -ar 48000 -ac 2 -map 0:v -c:v mpeg2video -q:v 0 -aspect 16:9 -map 1:a -map 1:a -map 1:a -map 1:a -shortest -f mpegts {output}',
#run('ffmpeg -y -hide_banner -loglevel error -i "{input}" -ar 48000 -ac 1 -map 0:v -c:v mpeg2video -q:v 0 -aspect 16:9 -map 1:0 -c:a copy -map 2:0 -c:a copy -shortest -f mpegts "{output}"', # run('ffmpeg -y -hide_banner -loglevel error -i "{input}" -ar 48000 -ac 1 -map 0:v -c:v mpeg2video -q:v 0 -aspect 16:9 -map 1:0 -c:a copy -map 2:0 -c:a copy -shortest -f mpegts "{output}"',
input=intermediate_clip, input=intermediate_clip,
output=final_clip) output=final_clip)
if event_id == 'pause' or event_id == 'outro' or event_id == 'bgloop': if event_id == 'pause' or event_id == 'outro' or event_id == 'bgloop':
event_print(event, "finalized "+str(event_id)+" to "+final_clip) event_print(event, "finalized " + str(event_id) + " to " + final_clip)
else: else:
event_print(event, "finalized intro to "+final_clip) event_print(event, "finalized intro to " + final_clip)
if args.ids: if args.ids:
@ -243,34 +252,34 @@ else:
print("enqueuing {} jobs into aerender".format(len(events))) print("enqueuing {} jobs into aerender".format(len(events)))
for event in events: for event in events:
if args.ids and event['id'] not in args.ids: if args.ids and event['id'] not in args.ids:
continue continue
if args.rooms and event['room'] not in args.rooms: if args.rooms and event['room'] not in args.rooms:
print("skipping room %s (%s)" % (event['room'], event['title'])) print("skipping room %s (%s)" % (event['room'], event['title']))
continue continue
event_print(event, "enqueued as "+str(event['id'])) event_print(event, "enqueued as " + str(event['id']))
job_id = enqueue_job(event) job_id = enqueue_job(event)
if not job_id: if not job_id:
event_print(event, "job was not enqueued successfully, skipping postprocessing") event_print(event, "job was not enqueued successfully, skipping postprocessing")
continue continue
if not args.nof: if not args.nof:
event_print(event, "finalizing job") event_print(event, "finalizing job")
finalize_job(job_id, event) finalize_job(job_id, event)
else:
event_id = str(event['id'])
event_print(event, "skipping finalizing job")
if platform.system() == 'Windows':
intermediate_clip = os.path.join(tempdir.name, event_id + '.avi')
final_clip = os.path.join(os.path.dirname(args.project), event_id + '.avi')
else: else:
event_id = str(event['id']) intermediate_clip = os.path.join(tempdir.name, event_id + '.mov')
event_print(event, "skipping finalizing job") final_clip = os.path.join(os.path.dirname(args.project), event_id + '.mov')
if platform.system() == 'Windows': copyfile(intermediate_clip, final_clip)
intermediate_clip = os.path.join(tempdir.name, event_id+'.avi') event_print(event, "copied intermediate clip to " + final_clip)
final_clip = os.path.join(os.path.dirname(args.project), event_id+'.avi')
else:
intermediate_clip = os.path.join(tempdir.name, event_id+'.mov')
final_clip = os.path.join(os.path.dirname(args.project), event_id+'.mov')
copyfile(intermediate_clip, final_clip)
event_print(event, "copied intermediate clip to "+final_clip)
print('all done, cleaning up '+tempdir.name) print('all done, cleaning up ' + tempdir.name)
tempdir.cleanup() tempdir.cleanup()

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# vim: tabstop=4 shiftwidth=4 expandtab
import subprocess import subprocess
import renderlib import renderlib
@ -15,179 +16,185 @@ from xml.sax.saxutils import escape as xmlescape
# Parse arguments # Parse arguments
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='C3VOC Intro-Outro-Generator - Variant to use with apple Motion Files', description='C3VOC Intro-Outro-Generator - Variant to use with apple Motion Files',
usage="./make.py gpn17/Intro.motn https://url/to/schedule.xml", usage="./make.py gpn17/Intro.motn https://url/to/schedule.xml",
formatter_class=argparse.RawTextHelpFormatter) formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('motn', action="store", metavar='Motion-File', type=str, help=''' parser.add_argument('motn', action="store", metavar='Motion-File', type=str, help='''
Path to your Motion-File .motn-File Path to your Motion-File .motn-File
''') ''')
parser.add_argument('schedule', action="store", metavar='Schedule-URL', type=str, nargs='?', help=''' parser.add_argument('schedule', action="store", metavar='Schedule-URL', type=str, nargs='?', help='''
URL or Path to your schedule.xml URL or Path to your schedule.xml
''') ''')
parser.add_argument('--debug', action="store_true", default=False, help=''' parser.add_argument('--debug', action="store_true", default=False, help='''
Run script in debug mode and render with placeholder texts, Run script in debug mode and render with placeholder texts,
not parsing or accessing a schedule. Schedule-URL can be left blank when not parsing or accessing a schedule. Schedule-URL can be left blank when
used with --debug used with --debug
This argument must not be used together with --id This argument must not be used together with --id
Usage: ./make.py yourproject/ --debug Usage: ./make.py yourproject/ --debug
''') ''')
parser.add_argument('--id', dest='ids', nargs='+', action="store", type=int, help=''' parser.add_argument('--id', dest='ids', nargs='+', action="store", type=int, help='''
Only render the given ID(s) from your projects schedule. Only render the given ID(s) from your projects schedule.
This argument must not be used together with --debug This argument must not be used together with --debug
Usage: ./make.py yourproject/ --id 4711 0815 4223 1337 Usage: ./make.py yourproject/ --id 4711 0815 4223 1337
''') ''')
parser.add_argument('--exclude-id', dest='exclude_ids', nargs='+', action="store", type=int, help=''' parser.add_argument('--exclude-id', dest='exclude_ids', nargs='+', action="store", type=int, help='''
Do not render the given ID(s) from your projects schedule. Do not render the given ID(s) from your projects schedule.
Usage: ./make.py yourproject/ --exclude-id 1 8 15 16 23 42 Usage: ./make.py yourproject/ --exclude-id 1 8 15 16 23 42
''') ''')
args = parser.parse_args() args = parser.parse_args()
def headline(str): def headline(str):
print("##################################################") print("##################################################")
print(str) print(str)
print("##################################################") print("##################################################")
print() print()
def error(str): def error(str):
headline(str) headline(str)
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
if not args.motn: if not args.motn:
error("The Motion-File is a rquired argument") error("The Motion-File is a rquired argument")
if not args.debug and not args.schedule: if not args.debug and not args.schedule:
error("Either specify --debug or supply a schedule") error("Either specify --debug or supply a schedule")
if args.debug: if args.debug:
persons = ['Arnulf Christl', 'Astrid Emde', 'Dominik Helle', 'Till Adams'] persons = ['Arnulf Christl', 'Astrid Emde', 'Dominik Helle', 'Till Adams']
events = [{ events = [{
'id': 3773, 'id': 3773,
'title': 'Was ist Open Source, wie funktioniert das?', 'title': 'Was ist Open Source, wie funktioniert das?',
'subtitle': 'Die Organisation der Open Geo- und GIS-Welt. Worauf man achten sollte.', 'subtitle': 'Die Organisation der Open Geo- und GIS-Welt. Worauf man achten sollte.',
'persons': persons, 'persons': persons,
'personnames': ', '.join(persons), 'personnames': ', '.join(persons),
'room': 'Großer Saal', 'room': 'Großer Saal',
}] }]
else: else:
events = list(renderlib.events(args.schedule)) events = list(renderlib.events(args.schedule))
def describe_event(event): def describe_event(event):
return "#{}: {}".format(event['id'], event['title']) return "#{}: {}".format(event['id'], event['title'])
def event_print(event, message): def event_print(event, message):
print("{} {}".format(describe_event(event), message)) print("{} {}".format(describe_event(event), message))
tempdir = tempfile.TemporaryDirectory() tempdir = tempfile.TemporaryDirectory()
print('working in '+tempdir.name) print('working in ' + tempdir.name)
def fmt_command(command, **kwargs): def fmt_command(command, **kwargs):
args = {} args = {}
for key, value in kwargs.items(): for key, value in kwargs.items():
args[key] = shlex.quote(value) args[key] = shlex.quote(value)
command = command.format(**args)
return shlex.split(command)
command = command.format(**args)
return shlex.split(command)
def run(command, **kwargs): def run(command, **kwargs):
return subprocess.check_call( return subprocess.check_call(
fmt_command(command, **kwargs)) fmt_command(command, **kwargs))
def run_output(command, **kwargs): def run_output(command, **kwargs):
return subprocess.check_output( return subprocess.check_output(
fmt_command(command, **kwargs), fmt_command(command, **kwargs),
encoding='utf-8', encoding='utf-8',
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
def enqueue_job(event): def enqueue_job(event):
event_id = str(event['id']) event_id = str(event['id'])
work_doc = os.path.join(tempdir.name, event_id+'.motn') work_doc = os.path.join(tempdir.name, event_id + '.motn')
intermediate_clip = os.path.join(tempdir.name, event_id+'.mov') intermediate_clip = os.path.join(tempdir.name, event_id + '.mov')
with open(args.motn, 'r') as fp: with open(args.motn, 'r') as fp:
xmlstr = fp.read() xmlstr = fp.read()
for key, value in event.items(): for key, value in event.items():
xmlstr = xmlstr.replace("$"+str(key), xmlescape(str(value))) xmlstr = xmlstr.replace("$" + str(key), xmlescape(str(value)))
with open(work_doc, 'w') as fp: with open(work_doc, 'w') as fp:
fp.write(xmlstr) fp.write(xmlstr)
compressor_info = run_output( compressor_info = run_output(
'/Applications/Compressor.app/Contents/MacOS/Compressor -batchname {batchname} -jobpath {jobpath} -settingpath apple-prores-4444.cmprstng -locationpath {locationpath}', '/Applications/Compressor.app/Contents/MacOS/Compressor -batchname {batchname} -jobpath {jobpath} -settingpath apple-prores-4444.cmprstng -locationpath {locationpath}',
batchname=describe_event(event), batchname=describe_event(event),
jobpath=work_doc, jobpath=work_doc,
locationpath=intermediate_clip) locationpath=intermediate_clip)
match = re.search("<jobID ([A-Z0-9\-]+) ?\/>", compressor_info) match = re.search(r"<jobID ([A-Z0-9\-]+) ?\/>", compressor_info)
if not match: if not match:
event_print(event, "unexpected output from compressor: \n"+compressor_info) event_print(event, "unexpected output from compressor: \n" + compressor_info)
return return
return match.group(1)
return match.group(1)
def fetch_job_status(): def fetch_job_status():
compressor_status = run_output('/Applications/Compressor.app/Contents/MacOS/Compressor -monitor') compressor_status = run_output('/Applications/Compressor.app/Contents/MacOS/Compressor -monitor')
job_status_matches = re.finditer("<jobStatus (.*) \/jobStatus>", compressor_status) job_status_matches = re.finditer(r"<jobStatus (.*) \/jobStatus>", compressor_status)
status_dict = {} status_dict = {}
for match in job_status_matches: for match in job_status_matches:
lexer = shlex.shlex(match.group(1), posix=True) lexer = shlex.shlex(match.group(1), posix=True)
lexer.wordchars += "=" lexer.wordchars += "="
job_status = dict(word.split("=", maxsplit=1) for word in lexer)
job_id = job_status['jobid']
status_dict[job_id] = job_status
return status_dict
job_status = dict(word.split("=", maxsplit=1) for word in lexer)
job_id = job_status['jobid']
status_dict[job_id] = job_status
return status_dict
def filter_finished_jobs(active_jobs): def filter_finished_jobs(active_jobs):
job_status = fetch_job_status() job_status = fetch_job_status()
new_active_jobs = [] new_active_jobs = []
finished_jobs = [] finished_jobs = []
for job_id, event in active_jobs: for job_id, event in active_jobs:
if job_id not in job_status: if job_id not in job_status:
status = 'Processing' status = 'Processing'
else: else:
status = job_status[job_id]['status'] status = job_status[job_id]['status']
if status == 'Processing': if status == 'Processing':
new_active_jobs.append((job_id, event)) new_active_jobs.append((job_id, event))
continue continue
elif status == 'Successful': elif status == 'Successful':
finished_jobs.append((job_id, event)) finished_jobs.append((job_id, event))
else: else:
event_print(event, "failed with staus="+status+" removing from postprocessing queue") event_print(event, "failed with staus=" + status + " removing from postprocessing queue")
return new_active_jobs, finished_jobs return new_active_jobs, finished_jobs
def finalize_job(job_id, event): def finalize_job(job_id, event):
event_id = str(event['id']) event_id = str(event['id'])
intermediate_clip = os.path.join(tempdir.name, event_id+'.mov') intermediate_clip = os.path.join(tempdir.name, event_id + '.mov')
final_clip = os.path.join(os.path.dirname(args.motn), event_id+'.ts') final_clip = os.path.join(os.path.dirname(args.motn), event_id + '.ts')
copy_clip = os.path.join(os.path.dirname(args.motn), event_id+'.mov') copy_clip = os.path.join(os.path.dirname(args.motn), event_id + '.mov')
shutil.copy(intermediate_clip, copy_clip) shutil.copy(intermediate_clip, copy_clip)
run('ffmpeg -y -hide_banner -loglevel error -i "{input}" -map 0:v -c:v mpeg2video -q:v 0 -aspect 16:9 -map 0:a -map 0:a -map 0:a -map 0:a -shortest -f mpegts "{output}"', run('ffmpeg -y -hide_banner -loglevel error -i {input} -f lavfi -i anullsrc -ar 48000 -ac 2 -map 0:v -c:v mpeg2video -q:v 0 -aspect 16:9 -map 1:a -map 1:a -map 1:a -map 1:a -shortest -f mpegts {output}',
input=intermediate_clip, input=intermediate_clip,
output=final_clip) output=final_clip)
event_print(event, "finalized intro to "+final_clip)
event_print(event, "finalized intro to " + final_clip)
active_jobs = [] active_jobs = []
@ -199,25 +206,25 @@ filtered_events = list(filtered_events)
print("enqueuing {} jobs into compressor".format(len(filtered_events))) print("enqueuing {} jobs into compressor".format(len(filtered_events)))
for event in filtered_events: for event in filtered_events:
job_id = enqueue_job(event) job_id = enqueue_job(event)
if not job_id: if not job_id:
event_print(event, "job was not enqueued successfully, skipping postprocessing") event_print(event, "job was not enqueued successfully, skipping postprocessing")
continue continue
event_print(event, "enqueued as "+job_id) event_print(event, "enqueued as " + job_id)
active_jobs.append((job_id, event)) active_jobs.append((job_id, event))
print("waiting for rendering to complete") print("waiting for rendering to complete")
while len(active_jobs) > 0: while len(active_jobs) > 0:
time.sleep(15) time.sleep(15)
active_jobs, finished_jobs = filter_finished_jobs(active_jobs) active_jobs, finished_jobs = filter_finished_jobs(active_jobs)
print("{} jobs in queue, {} ready to finalize".format(len(active_jobs), len(finished_jobs))) print("{} jobs in queue, {} ready to finalize".format(len(active_jobs), len(finished_jobs)))
for job_id, event in finished_jobs: for job_id, event in finished_jobs:
event_print(event, "finalizing job") event_print(event, "finalizing job")
finalize_job(job_id, event) finalize_job(job_id, event)
print('all done, cleaning up '+tempdir.name) print('all done, cleaning up ' + tempdir.name)
tempdir.cleanup() tempdir.cleanup()

187
make.py
View file

@ -1,10 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# vim: tabstop=4 shiftwidth=4 expandtab
import sys import sys
import os import os
import time import time
import shutil import shutil
from lxml import etree
import tempfile import tempfile
import threading import threading
import multiprocessing import multiprocessing
@ -19,103 +19,103 @@ parser.add_argument('projectpath', action="store", metavar='yourproject/', type=
Path to your project is a required argument. Path to your project is a required argument.
Usage: ./make.py yourproject/ Usage: ./make.py yourproject/
Without any further argument(s) given, your whole project will be rendered. Without any further argument(s) given, your whole project will be rendered.
''') ''')
parser.add_argument('--debug', action="store_true", default=False, help=''' parser.add_argument('--debug', action="store_true", default=False, help='''
Run script in debug mode and just render the debug values Run script in debug mode and just render the debug values
given in your projects __init.py__ given in your projects __init.py__
This argument must not be used together with --id This argument must not be used together with --id
Usage: ./make.py yourproject/ --debug Usage: ./make.py yourproject/ --debug
''') ''')
parser.add_argument('--only-frame', action="store", default=None, type=int, help=''' parser.add_argument('--only-frame', action="store", default=None, type=int, help='''
Only render the given frames (of the intro), e.g. to quickly render snapshots of the tiles frame. Only render the given frames (of the intro), e.g. to quickly render snapshots of the tiles frame.
Usage: ./make.py yourproject/ --debug --only-frame 300 Usage: ./make.py yourproject/ --debug --only-frame 300
./make.py yourproject/ --only-frame 300 ./make.py yourproject/ --only-frame 300
''') ''')
parser.add_argument('--id', nargs='+', action="store", type=int, help=''' parser.add_argument('--id', nargs='+', action="store", type=int, help='''
Only render the given ID(s) from your projects schedule. Only render the given ID(s) from your projects schedule.
This argument must not be used together with --debug This argument must not be used together with --debug
Usage: ./make.py yourproject/ --id 4711 0815 4223 1337 Usage: ./make.py yourproject/ --id 4711 0815 4223 1337
To skip all IDs (just generate intro/outro/background files) use it with --id 000000 To skip all IDs (just generate intro/outro/background files) use it with --id 000000
''') ''')
parser.add_argument('--skip', nargs='+', action="store", type=str, help=''' parser.add_argument('--skip', nargs='+', action="store", type=str, help='''
Skip outro, pause and/or background files in rendering if not needed. Skip outro, pause and/or background files in rendering if not needed.
This argument must not be used together with --debug This argument must not be used together with --debug
Usage: ./make.py yourproject/ --skip pause out bg Usage: ./make.py yourproject/ --skip pause out bg
Example - only generate outro: ./make.py yourproject/ --skip pause bg Example - only generate outro: ./make.py yourproject/ --skip pause bg
Example - only generate pause and background: ./make.py yourproject/ --skip out Example - only generate pause and background: ./make.py yourproject/ --skip out
''') ''')
parser.add_argument('--skip-frames', action="store", default=None, type=int, help=''' parser.add_argument('--skip-frames', action="store", default=None, type=int, help='''
Skip first n frames e.g. to quickly rerender during debugging. Skip first n frames e.g. to quickly rerender during debugging.
Usage: ./make.py yourproject/ --debug --skip-frames 300 Usage: ./make.py yourproject/ --debug --skip-frames 300
''') ''')
if len(sys.argv) < 2: if len(sys.argv) < 2:
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
args = parser.parse_args() args = parser.parse_args()
if not (args.debug==False or args.id==None): if not (args.debug is False or args.id is None):
print("##################################################") print("##################################################")
print("Error! You must not use --debug and --id together!") print("Error! You must not use --debug and --id together!")
print("##################################################") print("##################################################")
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
if not (args.debug==False or args.skip==None): if not (args.debug is False or args.skip is None):
print("####################################################") print("####################################################")
print("Error! You must not use --debug and --skip together!") print("Error! You must not use --debug and --skip together!")
print("####################################################") print("####################################################")
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
print(args) print(args)
# Set values from argparse # Set values from argparse
projectname=args.projectpath.strip('/') projectname = args.projectpath.strip('/')
projectpath=args.projectpath projectpath = args.projectpath
# Check if project exists # Check if project exists
try: try:
project = renderlib.loadProject(projectname) project = renderlib.loadProject(projectname)
except ImportError: except ImportError:
print("you must specify a project-name as first argument, eg. './make.py sotmeu14'. The supplied value '{0}' seems not to be a valid project (there is no '{0}/__init__.py').\n".format(projectname)) print("you must specify a project-name as first argument, eg. './make.py sotmeu14'. The supplied value '{0}' seems not to be a valid project (there is no '{0}/__init__.py').\n".format(projectname))
raise raise
# using --debug skips the threading, the network fetching of the schedule and # using --debug skips the threading, the network fetching of the schedule and
# just renders one type of video # just renders one type of video
renderlib.debug = args.debug renderlib.debug = args.debug
renderlib.args = args renderlib.args = args
#sys.exit(1) # sys.exit(1)
def render(infile, outfile, sequence, parameters={}, workdir=os.path.join(projectname, 'artwork')): def render(infile, outfile, sequence, parameters={}, workdir=os.path.join(projectname, 'artwork')):
task = renderlib.Rendertask(infile=infile, outfile=outfile, sequence=sequence, parameters=parameters, workdir=workdir) task = renderlib.Rendertask(infile=infile, outfile=outfile, sequence=sequence, parameters=parameters, workdir=workdir)
return renderlib.rendertask(task) return renderlib.rendertask(task)
# debug-mode selected by --debug switch # debug-mode selected by --debug switch
if renderlib.debug: if renderlib.debug:
print("!!! DEBUG MODE !!!") print("!!! DEBUG MODE !!!")
# expose debug-render method # expose debug-render method
project.render = render project.render = render
# call into project which calls render as needed
project.debug()
# exit early
sys.exit(0)
# call into project which calls render as needed
project.debug()
# exit early
sys.exit(0)
# threaded task queue # threaded task queue
tasks = Queue() tasks = Queue()
#initialize args.id and args.skip, if they are not given by the user # initialize args.id and args.skip, if they are not given by the user
if (args.id==None): if (args.id is None):
args.id = [] args.id = []
if (args.skip==None): if (args.skip is None):
args.skip = [] args.skip = []
# call into project which generates the tasks # call into project which generates the tasks
project.tasks(tasks, projectpath, args.id, args.skip) project.tasks(tasks, projectpath, args.id, args.skip)
@ -126,90 +126,93 @@ print("{0} tasks in queue, starting {1} worker threads".format(tasks.qsize(), nu
# put a sentinel for each thread into the queue to signal the end # put a sentinel for each thread into the queue to signal the end
for _ in range(num_worker_threads): for _ in range(num_worker_threads):
tasks.put(None) tasks.put(None)
# this lock ensures, that only one thread at a time is writing to stdout # this lock ensures, that only one thread at a time is writing to stdout
# and avoids output from multiple threads intermixing # and avoids output from multiple threads intermixing
printLock = Lock() printLock = Lock()
def tprint(str): def tprint(str):
# aquire lock # aquire lock
printLock.acquire() printLock.acquire()
# print thread-name and message # print thread-name and message
print(threading.current_thread().name+': '+str) print(threading.current_thread().name + ': ' + str)
# release lock # release lock
printLock.release() printLock.release()
# thread worker # thread worker
def worker(): def worker():
# generate a tempdir for this worker-thread and use the artwork-subdir as temporary folder # generate a tempdir for this worker-thread and use the artwork-subdir as temporary folder
tempdir = tempfile.mkdtemp() tempdir = tempfile.mkdtemp()
workdir = os.path.join(tempdir, 'artwork') workdir = os.path.join(tempdir, 'artwork')
# save the current working dir as output-dir # save the current working dir as output-dir
outdir = os.path.join(os.getcwd(), projectname) outdir = os.path.join(os.getcwd(), projectname)
# print a message that we're about to initialize our environment # print a message that we're about to initialize our environment
tprint("initializing worker in {0}, writing result to {1}".format(tempdir, outdir)) tprint("initializing worker in {0}, writing result to {1}".format(tempdir, outdir))
# copy the artwork-dir into the tempdir # copy the artwork-dir into the tempdir
shutil.copytree(os.path.join(projectname, 'artwork'), workdir) shutil.copytree(os.path.join(projectname, 'artwork'), workdir)
# loop until all tasks are done (when the thread fetches a sentinal from the queue) # loop until all tasks are done (when the thread fetches a sentinal from the queue)
while True: while True:
# fetch a task from the queue # fetch a task from the queue
task = renderlib.Rendertask.ensure(tasks.get()) task = renderlib.Rendertask.ensure(tasks.get())
# if it is a stop-sentinal break out of the loop # if it is a stop-sentinal break out of the loop
if task == None: if task is None:
break break
# print that we're about to render a task # print that we're about to render a task
tprint('rendering {0} from {1}'.format(task.outfile, task.infile)) tprint('rendering {0} from {1}'.format(task.outfile, task.infile))
# prepend workdir to input file # prepend workdir to input file
task.infile = os.path.join(workdir, task.infile) task.infile = os.path.join(workdir, task.infile)
task.outfile = os.path.join(outdir, task.outfile) task.outfile = os.path.join(outdir, task.outfile)
task.workdir = workdir task.workdir = workdir
# render with these arguments # render with these arguments
renderlib.rendertask(task) renderlib.rendertask(task)
# print that we're finished # print that we're finished
tprint('finished {0}, {1} tasks left'.format(task.outfile, max(0, tasks.qsize() - num_worker_threads))) tprint('finished {0}, {1} tasks left'.format(task.outfile, max(0, tasks.qsize() - num_worker_threads)))
# mark the task as finished # mark the task as finished
tasks.task_done() tasks.task_done()
# all tasks from the queue done, clean up # all tasks from the queue done, clean up
tprint("cleaning up worker") tprint("cleaning up worker")
# remove the tempdir # remove the tempdir
shutil.rmtree(tempdir) shutil.rmtree(tempdir)
# mark the sentinal as done
tasks.task_done()
# mark the sentinal as done
tasks.task_done()
# List of running threads # List of running threads
threads = [] threads = []
# generate and start the threads # generate and start the threads
for i in range(num_worker_threads): for i in range(num_worker_threads):
t = Thread(target=worker) t = Thread(target=worker)
t.daemon = True t.daemon = True
t.start() t.start()
threads.append(t) threads.append(t)
# wait until they finished doing the work # wait until they finished doing the work
# we're doing it the manual way because tasks.join() would wait until all tasks are done, # we're doing it the manual way because tasks.join() would wait until all tasks are done,
# even if the worker threads crash due to broken svgs, Ctrl-C termination or whatnot # even if the worker threads crash due to broken svgs, Ctrl-C termination or whatnot
while True: while True:
if tasks.empty() == True: if tasks.empty() is True:
break break
# sleep while the workers work # sleep while the workers work
time.sleep(1) time.sleep(1)
print("all worker threads ended") print("all worker threads ended")

View file

@ -1,10 +1,10 @@
#!/usr/bin/python3 #!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 expandtab
import os import os
import sys import sys
import re import re
import glob import glob
import math
import shutil import shutil
import errno import errno
from lxml import etree from lxml import etree
@ -22,263 +22,267 @@ args = None
cssutils.ser.prefs.lineSeparator = ' ' cssutils.ser.prefs.lineSeparator = ' '
cssutils.log.setLevel(logging.FATAL) cssutils.log.setLevel(logging.FATAL)
def loadProject(projectname): def loadProject(projectname):
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), projectname)) sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), projectname))
return __import__(projectname) return __import__(projectname)
def easeDelay(easer, delay, t, b, c, d, *args): def easeDelay(easer, delay, t, b, c, d, *args):
if t < delay: if t < delay:
return b return b
if t - delay > d: if t - delay > d:
return b+c return b + c
return easer(t - delay, b, c, d, *args)
return easer(t - delay, b, c, d, *args)
class Rendertask: class Rendertask:
def __init__(self, infile, sequence, parameters={}, outfile=None, workdir='.'): def __init__(self, infile, sequence, parameters={}, outfile=None, workdir='.'):
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.sequence = sequence
self.parameters = parameters self.parameters = parameters
self.outfile = outfile self.outfile = outfile
self.workdir = workdir self.workdir = workdir
def fromtupel(tuple): def fromtupel(tuple):
return Rendertask(tuple[0], tuple[2], tuple[3], tuple[1]) return Rendertask(tuple[0], tuple[2], tuple[3], tuple[1])
def ensure(input):
if isinstance(input, tuple):
return Rendertask.fromtupel(input)
elif isinstance(input, Rendertask):
return input
else:
return None
def ensure(input):
if isinstance(input, tuple):
return Rendertask.fromtupel(input)
elif isinstance(input, Rendertask):
return input
else:
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:
os.makedirs(path) os.makedirs(path)
except OSError as exception: except OSError as exception:
if exception.errno != errno.EEXIST: if exception.errno != errno.EEXIST:
raise raise
# remove the files matched by the pattern # remove the files matched by the pattern
def ensureFilesRemoved(pattern): def ensureFilesRemoved(pattern):
for f in glob.glob(pattern): for f in glob.glob(pattern):
os.unlink(f) os.unlink(f)
def rendertask(task): def rendertask(task):
global args global args
# in debug mode we have no thread-worker which prints its progress # in debug mode we have no thread-worker which prints its progress
if debug: if debug:
print("generating {0} from {1}".format(task.outfile, task.infile)) print("generating {0} from {1}".format(task.outfile, task.infile))
if not args.skip_frames and not 'only_rerender_frames_after' in task.parameters: if args.skip_frames and 'only_rerender_frames_after' not in task.parameters:
if os.path.isdir(os.path.join(task.workdir, '.frames')): if os.path.isdir(os.path.join(task.workdir, '.frames')):
shutil.rmtree(os.path.join(task.workdir, '.frames')) shutil.rmtree(os.path.join(task.workdir, '.frames'))
# make sure a .frames-directory exists in out workdir # make sure a .frames-directory exists in out workdir
ensurePathExists(os.path.join(task.workdir, '.frames')) ensurePathExists(os.path.join(task.workdir, '.frames'))
# open and parse the input file # 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() svgstr = fp.read()
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])))
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)
#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)
# frame-number counter # frame-number counter
frameNr = 0 frameNr = 0
# iterate through the animation seqence frame by frame # iterate through the animation seqence frame by frame
# frame is a ... tbd # frame is a ... tbd
cache = {} cache = {}
for frame in task.sequence(task.parameters): for frame in task.sequence(task.parameters):
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:
skip_rendering = (frameNr <= task.parameters['only_rerender_frames_after']) skip_rendering = (frameNr <= task.parameters['only_rerender_frames_after'])
if args.skip_frames: if args.skip_frames:
skip_rendering = (frameNr <= args.skip_frames) skip_rendering = (frameNr <= args.skip_frames)
if args.only_frame: if args.only_frame:
skip_rendering = (frameNr != args.only_frame) skip_rendering = (frameNr != args.only_frame)
# print a line for each and every frame generated # print a line for each and every frame generated
if debug and not skip_rendering: if debug and not skip_rendering:
print("frameNr {0:3d} => {1}".format(frameNr, frame)) print("frameNr {0:3d} => {1}".format(frameNr, frame))
frame = tuple(frame) frame = tuple(frame)
if frame in cache: if frame in cache:
if debug: if debug:
print("cache hit, reusing frame {0}".format(cache[frame])) print("cache hit, reusing frame {0}".format(cache[frame]))
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 frameNr += 1
continue 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 # 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: for replaceinfo in frame:
(id, type, key, value) = replaceinfo (id, type, key, value) = replaceinfo
for el in svg.findall(".//*[@id='"+id.replace("'", "\\'")+"']"): for el in svg.findall(".//*[@id='" + id.replace("'", "\\'") + "']"):
if type == 'style': if type == 'style':
style = cssutils.parseStyle( el.attrib['style'] if 'style' in el.attrib else '' ) style = cssutils.parseStyle(el.attrib['style'] if 'style' in el.attrib else '')
style[key] = str(value) style[key] = str(value)
el.attrib['style'] = style.cssText el.attrib['style'] = style.cssText
elif type == 'attr': elif type == 'attr':
el.attrib[key] = str(value) el.attrib[key] = str(value)
elif type == 'text': elif type == 'text':
el.text = str(value) el.text = str(value)
if not skip_rendering: if not skip_rendering:
# open the output-file (named ".gen.svg" in the workdir) # open the output-file (named ".gen.svg" in the workdir)
with open(os.path.join(task.workdir, '.gen.svg'), 'w') as fp: with open(os.path.join(task.workdir, '.gen.svg'), 'w') as fp:
# write the generated svg-text into the output-file # write the generated svg-text into the output-file
fp.write( etree.tostring(svg, encoding='unicode') ) fp.write(etree.tostring(svg, encoding='unicode'))
if task.outfile.endswith('.ts') or task.outfile.endswith('.mov'): if task.outfile.endswith('.ts') or task.outfile.endswith('.mov'):
width = 1920 width = 1920
height = 1080 height = 1080
else: else:
width = 1024 width = 1024
height = 576 height = 576
# invoke inkscape to convert the generated svg-file into a png inside the .frames-directory # 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) 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) errorReturn = subprocess.check_output(cmd, shell=True, universal_newlines=True, stderr=subprocess.STDOUT)
if errorReturn != '': if errorReturn != '':
print("inkscape exitted with error\n"+errorReturn) print("inkscape exitted with error\n" + errorReturn)
#sys.exit(42) # sys.exit(42)
# increment frame-number # increment frame-number
frameNr += 1 frameNr += 1
if args.only_frame:
task.outfile = '{0}.frame{1:04d}.png'.format(task.outfile, args.only_frame)
# remove the dv/ts we are about to (re-)generate
ensureFilesRemoved(os.path.join(task.workdir, task.outfile))
if args.only_frame: if task.outfile.endswith('.png'):
task.outfile = '{0}.frame{1:04d}.png'.format(task.outfile, args.only_frame) cmd = 'cd {0} && cp ".frames/{1:04d}.png" "{2}"'.format(task.workdir, args.only_frame, task.outfile)
# invoke avconv aka ffmpeg and renerate a lossles-dv from the frames
# if we're not in debug-mode, suppress all output
elif task.outfile.endswith('.ts'):
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 '
else:
cmd += '-i {0} -i {0} '.format(task.audiofile)
# remove the dv/ts we are about to (re-)generate cmd += '-map 0:0 -c:v mpeg2video -q:v 2 -aspect 16:9 '
ensureFilesRemoved(os.path.join(task.workdir, task.outfile))
if task.outfile.endswith('.png'): if task.audiofile is None:
cmd = 'cd {0} && cp ".frames/{1:04d}.png" "{2}"'.format(task.workdir, args.only_frame, task.outfile) cmd += '-map 1:0 -map 2:0 '
else:
cmd += '-map 1:0 -c:a copy -map 2:0 -c:a copy '
cmd += '-shortest -f mpegts "{0}"'.format(task.outfile)
elif task.outfile.endswith('.mov'):
cmd = 'cd {0} && '.format(task.workdir)
cmd += 'ffmpeg -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -f image2 -i .frames/%04d.png -r 25 -shortest -c:v qtrle -f mov "{0}"'.format(task.outfile)
else:
cmd = 'cd {0} && ffmpeg -ar 48000 -ac 2 -f s16le -i /dev/zero -f image2 -i .frames/%04d.png -target pal-dv -aspect 16:9 -shortest "{1}"'.format(task.workdir, task.outfile)
# invoke avconv aka ffmpeg and renerate a lossles-dv from the frames if debug:
# if we're not in debug-mode, suppress all output print(cmd)
elif task.outfile.endswith('.ts'):
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 '
else:
cmd += '-i {0} -i {0} '.format(task.audiofile)
cmd += '-map 0:0 -c:v mpeg2video -q:v 2 -aspect 16:9 ' r = os.system(cmd + ('' if debug else '>/dev/null 2>&1'))
if task.audiofile is None: # as before, in non-debug-mode the thread-worker does all progress messages
cmd += '-map 1:0 -map 2:0 ' if debug:
else: if r != 0:
cmd += '-map 1:0 -c:a copy -map 2:0 -c:a copy ' sys.exit()
cmd += '-shortest -f mpegts "{0}"'.format(task.outfile)
elif task.outfile.endswith('.mov'):
cmd = 'cd {0} && '.format(task.workdir)
cmd += 'ffmpeg -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -f image2 -i .frames/%04d.png -r 25 -shortest -c:v qtrle -f mov "{0}"'.format(task.outfile)
else:
cmd = 'cd {0} && ffmpeg -ar 48000 -ac 2 -f s16le -i /dev/zero -f image2 -i .frames/%04d.png -target pal-dv -aspect 16:9 -shortest "{1}"'.format(task.workdir, task.outfile)
if debug: if not debug:
print(cmd) print("cleanup")
r = os.system(cmd + ('' if debug else '>/dev/null 2>&1'))
# as before, in non-debug-mode the thread-worker does all progress messages
if debug:
if r != 0:
sys.exit()
if not debug:
print("cleanup")
# remove the generated svg
ensureFilesRemoved(os.path.join(task.workdir, '.gen.svg'))
# 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 # Download the Events-Schedule and parse all Events out of it. Yield a tupel for each Event
def events(scheduleUrl, titlemap={}): def events(scheduleUrl, titlemap={}):
print("downloading schedule") print("downloading schedule")
# download the schedule # download the schedule
response = urlopen(scheduleUrl) response = urlopen(scheduleUrl)
# read xml-source # read xml-source
xml = response.read() xml = response.read()
# parse into ElementTree # parse into ElementTree
parser = etree.XMLParser(huge_tree=True) parser = etree.XMLParser(huge_tree=True)
schedule = etree.fromstring(xml, parser) schedule = etree.fromstring(xml, parser)
# iterate all days # iterate all days
for day in schedule.iter('day'): for day in schedule.iter('day'):
# iterate all rooms # iterate all rooms
for room in day.iter('room'): for room in day.iter('room'):
# iterate events on that day in this room # iterate events on that day in this room
for event in room.iter('event'): for event in room.iter('event'):
# aggregate names of the persons holding this talk # aggregate names of the persons holding this talk
personnames = [] personnames = []
if event.find('persons') is not None: if event.find('persons') is not None:
for person in event.find('persons').iter('person'): for person in event.find('persons').iter('person'):
personname = re.sub( '\s+', ' ', person.text ).strip() personname = re.sub(r'\s+', ' ', person.text).strip()
personnames.append(personname) personnames.append(personname)
id = int(event.get('id')) id = int(event.get('id'))
if id in titlemap: if id in titlemap:
title = titlemap[id] title = titlemap[id]
elif event.find('title') is not None and event.find('title').text is not None: elif event.find('title') is not None and event.find('title').text is not None:
title = re.sub( '\s+', ' ', event.find('title').text ).strip() title = re.sub(r'\s+', ' ', event.find('title').text).strip()
else: else:
title = '' title = ''
if event.find('subtitle') is not None and event.find('subtitle').text is not None: if event.find('subtitle') is not None and event.find('subtitle').text is not None:
subtitle = re.sub( '\s+', ' ', event.find('subtitle').text ).strip() subtitle = re.sub(r'\s+', ' ', event.find('subtitle').text).strip()
else: else:
subtitle = '' subtitle = ''
# yield a tupel with the event-id, event-title and person-names
yield {
'id': id,
'title': title,
'subtitle': subtitle,
'persons': personnames,
'personnames': ', '.join(personnames),
'room': room.attrib['name'],
'track': event.find('track').text
}
# yield a tupel with the event-id, event-title and person-names
yield {
'id': id,
'title': title,
'subtitle': subtitle,
'persons': personnames,
'personnames': ', '.join(personnames),
'room': room.attrib['name'],
'track' : event.find('track').text
}
try: try:
from termcolor import colored from termcolor import colored
except ImportError: except ImportError:
def colored(str, col): def colored(str, col):
return str return str