diff --git a/make-adobe-after-effects.py b/make-adobe-after-effects.py index 5e4cbca..e65931d 100755 --- a/make-adobe-after-effects.py +++ b/make-adobe-after-effects.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# vim: tabstop=4 shiftwidth=4 expandtab import subprocess import renderlib @@ -8,227 +9,235 @@ import shlex import time import sys import os -import re import platform - -from xml.sax.saxutils import escape as xmlescape from shutil import copyfile # Parse arguments parser = argparse.ArgumentParser( - 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", - formatter_class=argparse.RawTextHelpFormatter) + 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", + formatter_class=argparse.RawTextHelpFormatter) 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) - ''') -parser.add_argument('schedule', action="store", metavar='Schedule-URL', type=str, nargs='?', help=''' - URL or Path to your schedule.xml - ''') + 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=''' + URL or Path to your schedule.xml + ''') parser.add_argument('--debug', action="store_true", default=False, help=''' - Run script in debug mode and render with placeholder texts, - not parsing or accessing a schedule. Schedule-URL can be left blank when - used with --debug - This argument must not be used together with --id - Usage: ./make-adobe-after-effects.py yourproject/ --debug - ''') + Run script in debug mode and render with placeholder texts, + not parsing or accessing a schedule. Schedule-URL can be left blank when + used with --debug + This argument must not be used together with --id + Usage: ./make-adobe-after-effects.py yourproject/ --debug + ''') parser.add_argument('--id', dest='ids', nargs='+', action="store", type=int, help=''' - Only render the given ID(s) from your projects schedule. - This argument must not be used together with --debug - Usage: ./make-adobe-after-effects.py yourproject/ --id 4711 0815 4223 1337 - ''') + Only render the given ID(s) from your projects schedule. + This argument must not be used together with --debug + Usage: ./make-adobe-after-effects.py yourproject/ --id 4711 0815 4223 1337 + ''') parser.add_argument('--room', dest='rooms', nargs='+', action="store", type=str, help=''' - Only render the given room(s) from your projects schedule. - This argument must not be used together with --debug - Usage: ./make-adobe-after-effects.py yourproject/ --room "HfG_Studio" "ZKM_Vortragssaal" - ''') + Only render the given room(s) from your projects schedule. + This argument must not be used together with --debug + Usage: ./make-adobe-after-effects.py yourproject/ --room "HfG_Studio" "ZKM_Vortragssaal" + ''') 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=''' - Force render if file exists. - ''') + Force render if file exists. + ''') 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=''' - 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=''' - 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() + def headline(str): - print("##################################################") - print(str) - print("##################################################") - print() + print("##################################################") + print(str) + print("##################################################") + print() + def error(str): - headline(str) - parser.print_help() - sys.exit(1) + headline(str) + parser.print_help() + sys.exit(1) + 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: - error("Either specify --debug, --pause, --outro or supply a schedule") + error("Either specify --debug, --pause, --outro or supply a schedule") if args.debug: - persons = ['watz'] - events = [{ - 'id': 1, - 'title': 'Eröffnungsveranstaltung', - 'subtitle': 'Easterhegg 2018', - 'persons': persons, - 'personnames': ', '.join(persons), - 'room': 'Heisenberg 1', - }] + persons = ['watz'] + events = [{ + 'id': 1, + 'title': 'Eröffnungsveranstaltung', + 'subtitle': 'Easterhegg 2018', + 'persons': persons, + 'personnames': ', '.join(persons), + 'room': 'Heisenberg 1', + }] elif args.pause: - events = [{ - 'id': 'pause', - 'title': 'Pause Loop', - }] + events = [{ + 'id': 'pause', + 'title': 'Pause Loop', + }] elif args.outro: - events = [{ - 'id': 'outro', - 'title': 'Outro', - }] + events = [{ + 'id': 'outro', + 'title': 'Outro', + }] elif args.bgloop: - events = [{ - 'id': 'bgloop', - 'title': 'Background Loop', - }] + events = [{ + 'id': 'bgloop', + 'title': 'Background Loop', + }] else: - events = list(renderlib.events(args.schedule)) + events = list(renderlib.events(args.schedule)) + def describe_event(event): - return "#{}: {}".format(event['id'], event['title']) + return "#{}: {}".format(event['id'], event['title']) + def event_print(event, message): - print("{} – {}".format(describe_event(event), message)) + print("{} – {}".format(describe_event(event), message)) + tempdir = tempfile.TemporaryDirectory() -print('working in '+tempdir.name) +print('working in ' + tempdir.name) + def fmt_command(command, **kwargs): - args = {} - for key, value in kwargs.items(): - args[key] = shlex.quote(value) + args = {} + for key, value in kwargs.items(): + 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): - DETACHED_PROCESS = 0x00000008 - return subprocess.Popen( - fmt_command(command, **kwargs), - shell=False, - stdin=None, - stdout=None, - stderr=None, - close_fds=True, - creationflags=DETACHED_PROCESS) + DETACHED_PROCESS = 0x00000008 + return subprocess.Popen( + fmt_command(command, **kwargs), + shell=False, + stdin=None, + stdout=None, + stderr=None, + close_fds=True, + creationflags=DETACHED_PROCESS) + def run(command, **kwargs): - return subprocess.check_call( - fmt_command(command, **kwargs), - stderr=subprocess.STDOUT, - stdout=subprocess.DEVNULL) + return subprocess.check_call( + fmt_command(command, **kwargs), + stderr=subprocess.STDOUT, + stdout=subprocess.DEVNULL) + def enqueue_job(event): - 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: - event_print(event, "file exist, skipping "+str(event['id'])) - return - work_doc = os.path.join(tempdir.name, event_id+'.aep') - script_doc = os.path.join(tempdir.name, event_id+'.jsx') - ascript_doc = os.path.join(tempdir.name, event_id+'.scpt') - intermediate_clip = os.path.join(tempdir.name, event_id+'.mov') + 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: + event_print(event, "file exist, skipping " + str(event['id'])) + return + work_doc = os.path.join(tempdir.name, event_id + '.aep') + script_doc = os.path.join(tempdir.name, event_id + '.jsx') + ascript_doc = os.path.join(tempdir.name, event_id + '.scpt') + intermediate_clip = os.path.join(tempdir.name, event_id + '.mov') - if event_id == 'pause' or event_id == 'outro' or event_id == 'bgloop': - copyfile(args.project+event_id+'.aep',work_doc) - if platform.system() == 'Darwin': - run('/Applications/Adobe\ After\ Effects\ CC\ 2018/aerender -project {jobpath} -comp {comp} -output {locationpath}', - jobpath=work_doc, - comp=event_id, - locationpath=intermediate_clip) + if event_id == 'pause' or event_id == 'outro' or event_id == 'bgloop': + copyfile(args.project + event_id + '.aep', work_doc) + if platform.system() == 'Darwin': + run(r'/Applications/Adobe\ After\ Effects\ CC\ 2018/aerender -project {jobpath} -comp {comp} -output {locationpath}', + jobpath=work_doc, + comp=event_id, + locationpath=intermediate_clip) - if platform.system() == 'Windows': - run('C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/aerender.exe -project {jobpath} -comp {comp} -output {locationpath}', - jobpath=work_doc, - comp=event_id, - locationpath=intermediate_clip) + if platform.system() == 'Windows': + run(r'C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/aerender.exe -project {jobpath} -comp {comp} -output {locationpath}', + jobpath=work_doc, + comp=event_id, + locationpath=intermediate_clip) - else: - with open(args.project+'intro.jsx', 'r') as fp: - scriptstr = fp.read() + else: + with open(args.project + 'intro.jsx', 'r') as fp: + scriptstr = fp.read() - for key, value in event.items(): - value = str(value).replace('"', '\\"') - scriptstr = scriptstr.replace("$"+str(key), value) + for key, value in event.items(): + value = str(value).replace('"', '\\"') + scriptstr = scriptstr.replace("$" + str(key), value) - with open(script_doc, 'w', encoding='utf-8') as fp: - fp.write(scriptstr) + with open(script_doc, 'w', encoding='utf-8') as fp: + fp.write(scriptstr) - copyfile(args.project+'intro.aep',work_doc) + copyfile(args.project + 'intro.aep', work_doc) - if platform.system() == 'Darwin': - copyfile(args.project+'intro.scpt',ascript_doc) - run('osascript {ascript_path} {jobpath} {scriptpath}', - jobpath=work_doc, - scriptpath=script_doc, - ascript_path=ascript_doc) + if platform.system() == 'Darwin': + copyfile(args.project + 'intro.scpt', ascript_doc) + run('osascript {ascript_path} {jobpath} {scriptpath}', + jobpath=work_doc, + scriptpath=script_doc, + ascript_path=ascript_doc) - run('/Applications/Adobe\ After\ Effects\ CC\ 2018/aerender -project {jobpath} -comp "intro" -output {locationpath}', - jobpath=work_doc, - locationpath=intermediate_clip) + run(r'/Applications/Adobe\ After\ Effects\ CC\ 2018/aerender -project {jobpath} -comp "intro" -output {locationpath}', + jobpath=work_doc, + locationpath=intermediate_clip) - if platform.system() == 'Windows': - run_once('C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/AfterFX.exe -noui {jobpath}', - jobpath=work_doc) - time.sleep(15) + if platform.system() == 'Windows': + run_once(r'C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/AfterFX.exe -noui {jobpath}', + jobpath=work_doc) + time.sleep(15) - run_once('C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/AfterFX.exe -noui -r {scriptpath}', - scriptpath=script_doc) - time.sleep(5) + run_once(r'C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/AfterFX.exe -noui -r {scriptpath}', + scriptpath=script_doc) + time.sleep(5) - run('C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/aerender.exe -project {jobpath} -comp "intro" -output {locationpath}', - jobpath=work_doc, - locationpath=intermediate_clip) + run(r'C:/Program\ Files/Adobe/Adobe\ After\ Effects\ CC\ 2018/Support\ Files/aerender.exe -project {jobpath} -comp "intro" -output {locationpath}', + jobpath=work_doc, + locationpath=intermediate_clip) + + return event_id - return event_id def finalize_job(job_id, event): - event_id = str(event['id']) - intermediate_clip = os.path.join(tempdir.name, event_id+'.mov') - final_clip = os.path.join(os.path.dirname(args.project), event_id+'.ts') + event_id = str(event['id']) + intermediate_clip = os.path.join(tempdir.name, event_id + '.mov') + 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}" -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, - output=final_clip) + 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}"', + input=intermediate_clip, + output=final_clip) - if event_id == 'pause' or event_id == 'outro' or event_id == 'bgloop': - event_print(event, "finalized "+str(event_id)+" to "+final_clip) - else: - event_print(event, "finalized intro to "+final_clip) + if event_id == 'pause' or event_id == 'outro' or event_id == 'bgloop': + event_print(event, "finalized " + str(event_id) + " to " + final_clip) + else: + event_print(event, "finalized intro to " + final_clip) if args.ids: @@ -243,34 +252,34 @@ else: print("enqueuing {} jobs into aerender".format(len(events))) for event in events: - if args.ids and event['id'] not in args.ids: - continue + if args.ids and event['id'] not in args.ids: + continue - if args.rooms and event['room'] not in args.rooms: - print("skipping room %s (%s)" % (event['room'], event['title'])) - continue + if args.rooms and event['room'] not in args.rooms: + print("skipping room %s (%s)" % (event['room'], event['title'])) + continue - event_print(event, "enqueued as "+str(event['id'])) + event_print(event, "enqueued as " + str(event['id'])) - job_id = enqueue_job(event) - if not job_id: - event_print(event, "job was not enqueued successfully, skipping postprocessing") - continue + job_id = enqueue_job(event) + if not job_id: + event_print(event, "job was not enqueued successfully, skipping postprocessing") + continue - if not args.nof: - event_print(event, "finalizing job") - finalize_job(job_id, event) + if not args.nof: + event_print(event, "finalizing job") + 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: - 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: - 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) + 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() diff --git a/make-apple-motion.py b/make-apple-motion.py index 8098809..3513619 100755 --- a/make-apple-motion.py +++ b/make-apple-motion.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# vim: tabstop=4 shiftwidth=4 expandtab import subprocess import renderlib @@ -15,179 +16,185 @@ from xml.sax.saxutils import escape as xmlescape # Parse arguments parser = argparse.ArgumentParser( - description='C3VOC Intro-Outro-Generator - Variant to use with apple Motion Files', - usage="./make.py gpn17/Intro.motn https://url/to/schedule.xml", - formatter_class=argparse.RawTextHelpFormatter) + description='C3VOC Intro-Outro-Generator - Variant to use with apple Motion Files', + usage="./make.py gpn17/Intro.motn https://url/to/schedule.xml", + formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('motn', action="store", metavar='Motion-File', type=str, help=''' - Path to your Motion-File .motn-File - ''') -parser.add_argument('schedule', action="store", metavar='Schedule-URL', type=str, nargs='?', help=''' - URL or Path to your schedule.xml - ''') + Path to your Motion-File .motn-File + ''') +parser.add_argument('schedule', action="store", metavar='Schedule-URL', type=str, nargs='?', help=''' + URL or Path to your schedule.xml + ''') parser.add_argument('--debug', action="store_true", default=False, help=''' - Run script in debug mode and render with placeholder texts, - not parsing or accessing a schedule. Schedule-URL can be left blank when - used with --debug - This argument must not be used together with --id - Usage: ./make.py yourproject/ --debug - ''') + Run script in debug mode and render with placeholder texts, + not parsing or accessing a schedule. Schedule-URL can be left blank when + used with --debug + This argument must not be used together with --id + Usage: ./make.py yourproject/ --debug + ''') parser.add_argument('--id', dest='ids', nargs='+', action="store", type=int, help=''' - Only render the given ID(s) from your projects schedule. - This argument must not be used together with --debug - Usage: ./make.py yourproject/ --id 4711 0815 4223 1337 - ''') + Only render the given ID(s) from your projects schedule. + This argument must not be used together with --debug + Usage: ./make.py yourproject/ --id 4711 0815 4223 1337 + ''') 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. - Usage: ./make.py yourproject/ --exclude-id 1 8 15 16 23 42 - ''') + Do not render the given ID(s) from your projects schedule. + Usage: ./make.py yourproject/ --exclude-id 1 8 15 16 23 42 + ''') args = parser.parse_args() + def headline(str): - print("##################################################") - print(str) - print("##################################################") - print() + print("##################################################") + print(str) + print("##################################################") + print() + def error(str): - headline(str) - parser.print_help() - sys.exit(1) + headline(str) + parser.print_help() + sys.exit(1) + 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: - error("Either specify --debug or supply a schedule") + error("Either specify --debug or supply a schedule") if args.debug: - persons = ['Arnulf Christl', 'Astrid Emde', 'Dominik Helle', 'Till Adams'] - events = [{ - 'id': 3773, - 'title': 'Was ist Open Source, wie funktioniert das?', - 'subtitle': 'Die Organisation der Open Geo- und GIS-Welt. Worauf man achten sollte.', - 'persons': persons, - 'personnames': ', '.join(persons), - 'room': 'Großer Saal', - }] + persons = ['Arnulf Christl', 'Astrid Emde', 'Dominik Helle', 'Till Adams'] + events = [{ + 'id': 3773, + 'title': 'Was ist Open Source, wie funktioniert das?', + 'subtitle': 'Die Organisation der Open Geo- und GIS-Welt. Worauf man achten sollte.', + 'persons': persons, + 'personnames': ', '.join(persons), + 'room': 'Großer Saal', + }] else: - events = list(renderlib.events(args.schedule)) + events = list(renderlib.events(args.schedule)) + def describe_event(event): - return "#{}: {}".format(event['id'], event['title']) + return "#{}: {}".format(event['id'], event['title']) + def event_print(event, message): - print("{} – {}".format(describe_event(event), message)) + print("{} – {}".format(describe_event(event), message)) + tempdir = tempfile.TemporaryDirectory() -print('working in '+tempdir.name) +print('working in ' + tempdir.name) def fmt_command(command, **kwargs): - args = {} - for key, value in kwargs.items(): - args[key] = shlex.quote(value) + args = {} + for key, value in kwargs.items(): + 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): - return subprocess.check_call( - fmt_command(command, **kwargs)) + return subprocess.check_call( + fmt_command(command, **kwargs)) + def run_output(command, **kwargs): - return subprocess.check_output( - fmt_command(command, **kwargs), - encoding='utf-8', - stderr=subprocess.STDOUT) + return subprocess.check_output( + fmt_command(command, **kwargs), + encoding='utf-8', + stderr=subprocess.STDOUT) def enqueue_job(event): - event_id = str(event['id']) - work_doc = os.path.join(tempdir.name, event_id+'.motn') - intermediate_clip = os.path.join(tempdir.name, event_id+'.mov') + event_id = str(event['id']) + work_doc = os.path.join(tempdir.name, event_id + '.motn') + intermediate_clip = os.path.join(tempdir.name, event_id + '.mov') - with open(args.motn, 'r') as fp: - xmlstr = fp.read() + with open(args.motn, 'r') as fp: + xmlstr = fp.read() - for key, value in event.items(): - xmlstr = xmlstr.replace("$"+str(key), xmlescape(str(value))) + for key, value in event.items(): + xmlstr = xmlstr.replace("$" + str(key), xmlescape(str(value))) - with open(work_doc, 'w') as fp: - fp.write(xmlstr) + with open(work_doc, 'w') as fp: + fp.write(xmlstr) - compressor_info = run_output( - '/Applications/Compressor.app/Contents/MacOS/Compressor -batchname {batchname} -jobpath {jobpath} -settingpath apple-prores-4444.cmprstng -locationpath {locationpath}', - batchname=describe_event(event), - jobpath=work_doc, - locationpath=intermediate_clip) + compressor_info = run_output( + '/Applications/Compressor.app/Contents/MacOS/Compressor -batchname {batchname} -jobpath {jobpath} -settingpath apple-prores-4444.cmprstng -locationpath {locationpath}', + batchname=describe_event(event), + jobpath=work_doc, + locationpath=intermediate_clip) - match = re.search("", compressor_info) - if not match: - event_print(event, "unexpected output from compressor: \n"+compressor_info) - return + match = re.search(r"", compressor_info) + if not match: + event_print(event, "unexpected output from compressor: \n" + compressor_info) + return + + return match.group(1) - return match.group(1) def fetch_job_status(): - compressor_status = run_output('/Applications/Compressor.app/Contents/MacOS/Compressor -monitor') - job_status_matches = re.finditer("", compressor_status) + compressor_status = run_output('/Applications/Compressor.app/Contents/MacOS/Compressor -monitor') + job_status_matches = re.finditer(r"", compressor_status) - status_dict = {} - for match in job_status_matches: - lexer = shlex.shlex(match.group(1), posix=True) - 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 + status_dict = {} + for match in job_status_matches: + lexer = shlex.shlex(match.group(1), posix=True) + 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 def filter_finished_jobs(active_jobs): - job_status = fetch_job_status() + job_status = fetch_job_status() - new_active_jobs = [] - finished_jobs = [] - for job_id, event in active_jobs: - if job_id not in job_status: - status = 'Processing' - else: - status = job_status[job_id]['status'] + new_active_jobs = [] + finished_jobs = [] + for job_id, event in active_jobs: + if job_id not in job_status: + status = 'Processing' + else: + status = job_status[job_id]['status'] - if status == 'Processing': - new_active_jobs.append((job_id, event)) - continue - elif status == 'Successful': - finished_jobs.append((job_id, event)) - else: - event_print(event, "failed with staus="+status+" – removing from postprocessing queue") + if status == 'Processing': + new_active_jobs.append((job_id, event)) + continue + elif status == 'Successful': + finished_jobs.append((job_id, event)) + else: + 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): - event_id = str(event['id']) - intermediate_clip = os.path.join(tempdir.name, event_id+'.mov') - 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') + event_id = str(event['id']) + intermediate_clip = os.path.join(tempdir.name, event_id + '.mov') + 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') - 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}"', - input=intermediate_clip, - output=final_clip) - - event_print(event, "finalized intro to "+final_clip) + 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, + output=final_clip) + event_print(event, "finalized intro to " + final_clip) active_jobs = [] @@ -199,25 +206,25 @@ filtered_events = list(filtered_events) print("enqueuing {} jobs into compressor".format(len(filtered_events))) for event in filtered_events: - job_id = enqueue_job(event) - if not job_id: - event_print(event, "job was not enqueued successfully, skipping postprocessing") - continue + job_id = enqueue_job(event) + if not job_id: + event_print(event, "job was not enqueued successfully, skipping postprocessing") + continue - event_print(event, "enqueued as "+job_id) - active_jobs.append((job_id, event)) + event_print(event, "enqueued as " + job_id) + active_jobs.append((job_id, event)) print("waiting for rendering to complete") while len(active_jobs) > 0: - time.sleep(15) - active_jobs, finished_jobs = filter_finished_jobs(active_jobs) + time.sleep(15) + active_jobs, finished_jobs = filter_finished_jobs(active_jobs) - print("{} jobs in queue, {} ready to finalize".format(len(active_jobs), len(finished_jobs))) - for job_id, event in finished_jobs: - event_print(event, "finalizing job") - finalize_job(job_id, event) + print("{} jobs in queue, {} ready to finalize".format(len(active_jobs), len(finished_jobs))) + for job_id, event in finished_jobs: + event_print(event, "finalizing job") + finalize_job(job_id, event) -print('all done, cleaning up '+tempdir.name) +print('all done, cleaning up ' + tempdir.name) tempdir.cleanup() diff --git a/make.py b/make.py index 367a0e6..fca6911 100755 --- a/make.py +++ b/make.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 +# vim: tabstop=4 shiftwidth=4 expandtab import sys import os import time import shutil -from lxml import etree import tempfile import threading import multiprocessing @@ -19,103 +19,103 @@ parser.add_argument('projectpath', action="store", metavar='yourproject/', type= Path to your project is a required argument. Usage: ./make.py yourproject/ Without any further argument(s) given, your whole project will be rendered. - ''') + ''') parser.add_argument('--debug', action="store_true", default=False, help=''' Run script in debug mode and just render the debug values given in your projects __init.py__ This argument must not be used together with --id Usage: ./make.py yourproject/ --debug - ''') + ''') 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. Usage: ./make.py yourproject/ --debug --only-frame 300 ./make.py yourproject/ --only-frame 300 - ''') + ''') parser.add_argument('--id', nargs='+', action="store", type=int, help=''' Only render the given ID(s) from your projects schedule. This argument must not be used together with --debug Usage: ./make.py yourproject/ --id 4711 0815 4223 1337 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=''' Skip outro, pause and/or background files in rendering if not needed. This argument must not be used together with --debug Usage: ./make.py yourproject/ --skip pause out bg Example - only generate outro: ./make.py yourproject/ --skip pause bg Example - only generate pause and background: ./make.py yourproject/ --skip out - ''') + ''') parser.add_argument('--skip-frames', action="store", default=None, type=int, help=''' Skip first n frames e.g. to quickly rerender during debugging. Usage: ./make.py yourproject/ --debug --skip-frames 300 - ''') + ''') if len(sys.argv) < 2: - parser.print_help() - sys.exit(1) + parser.print_help() + sys.exit(1) args = parser.parse_args() -if not (args.debug==False or args.id==None): - print("##################################################") - print("Error! You must not use --debug and --id together!") - print("##################################################") - parser.print_help() - sys.exit(1) +if not (args.debug is False or args.id is None): + print("##################################################") + print("Error! You must not use --debug and --id together!") + print("##################################################") + parser.print_help() + sys.exit(1) -if not (args.debug==False or args.skip==None): - print("####################################################") - print("Error! You must not use --debug and --skip together!") - print("####################################################") - parser.print_help() - sys.exit(1) +if not (args.debug is False or args.skip is None): + print("####################################################") + print("Error! You must not use --debug and --skip together!") + print("####################################################") + parser.print_help() + sys.exit(1) print(args) # Set values from argparse -projectname=args.projectpath.strip('/') -projectpath=args.projectpath +projectname = args.projectpath.strip('/') +projectpath = args.projectpath # Check if project exists try: - project = renderlib.loadProject(projectname) + project = renderlib.loadProject(projectname) 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)) - raise + 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 # using --debug skips the threading, the network fetching of the schedule and # just renders one type of video renderlib.debug = args.debug renderlib.args = args -#sys.exit(1) +# sys.exit(1) + 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) - return renderlib.rendertask(task) + task = renderlib.Rendertask(infile=infile, outfile=outfile, sequence=sequence, parameters=parameters, workdir=workdir) + return renderlib.rendertask(task) + # debug-mode selected by --debug switch if renderlib.debug: - print("!!! DEBUG MODE !!!") + print("!!! DEBUG MODE !!!") - # expose debug-render method - project.render = render - - # call into project which calls render as needed - project.debug() - - # exit early - sys.exit(0) + # expose debug-render method + project.render = render + # call into project which calls render as needed + project.debug() + # exit early + sys.exit(0) # threaded task queue tasks = Queue() -#initialize args.id and args.skip, if they are not given by the user -if (args.id==None): - args.id = [] +# initialize args.id and args.skip, if they are not given by the user +if (args.id is None): + args.id = [] -if (args.skip==None): - args.skip = [] +if (args.skip is None): + args.skip = [] # call into project which generates the tasks 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 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 # and avoids output from multiple threads intermixing printLock = Lock() + + def tprint(str): - # aquire lock - printLock.acquire() + # aquire lock + printLock.acquire() - # print thread-name and message - print(threading.current_thread().name+': '+str) + # print thread-name and message + print(threading.current_thread().name + ': ' + str) - # release lock - printLock.release() + # release lock + printLock.release() # thread worker def worker(): - # generate a tempdir for this worker-thread and use the artwork-subdir as temporary folder - tempdir = tempfile.mkdtemp() - workdir = os.path.join(tempdir, 'artwork') + # generate a tempdir for this worker-thread and use the artwork-subdir as temporary folder + tempdir = tempfile.mkdtemp() + workdir = os.path.join(tempdir, 'artwork') - # save the current working dir as output-dir - outdir = os.path.join(os.getcwd(), projectname) + # save the current working dir as output-dir + outdir = os.path.join(os.getcwd(), projectname) - # print a message that we're about to initialize our environment - tprint("initializing worker in {0}, writing result to {1}".format(tempdir, outdir)) + # print a message that we're about to initialize our environment + tprint("initializing worker in {0}, writing result to {1}".format(tempdir, outdir)) - # copy the artwork-dir into the tempdir - shutil.copytree(os.path.join(projectname, 'artwork'), workdir) + # copy the artwork-dir into the tempdir + shutil.copytree(os.path.join(projectname, 'artwork'), workdir) - # loop until all tasks are done (when the thread fetches a sentinal from the queue) - while True: - # fetch a task from the queue - task = renderlib.Rendertask.ensure(tasks.get()) + # loop until all tasks are done (when the thread fetches a sentinal from the queue) + while True: + # fetch a task from the queue + task = renderlib.Rendertask.ensure(tasks.get()) - # if it is a stop-sentinal break out of the loop - if task == None: - break + # if it is a stop-sentinal break out of the loop + if task is None: + break - # print that we're about to render a task - tprint('rendering {0} from {1}'.format(task.outfile, task.infile)) + # print that we're about to render a task + tprint('rendering {0} from {1}'.format(task.outfile, task.infile)) - # prepend workdir to input file - task.infile = os.path.join(workdir, task.infile) - task.outfile = os.path.join(outdir, task.outfile) - task.workdir = workdir + # prepend workdir to input file + task.infile = os.path.join(workdir, task.infile) + task.outfile = os.path.join(outdir, task.outfile) + task.workdir = workdir - # render with these arguments - renderlib.rendertask(task) + # render with these arguments + renderlib.rendertask(task) - # print that we're finished - tprint('finished {0}, {1} tasks left'.format(task.outfile, max(0, tasks.qsize() - num_worker_threads))) + # print that we're finished + tprint('finished {0}, {1} tasks left'.format(task.outfile, max(0, tasks.qsize() - num_worker_threads))) - # mark the task as finished - tasks.task_done() + # mark the task as finished + tasks.task_done() - # all tasks from the queue done, clean up - tprint("cleaning up worker") + # all tasks from the queue done, clean up + tprint("cleaning up worker") - # remove the tempdir - shutil.rmtree(tempdir) + # remove the tempdir + shutil.rmtree(tempdir) + + # mark the sentinal as done + tasks.task_done() - # mark the sentinal as done - tasks.task_done() # List of running threads threads = [] # generate and start the threads for i in range(num_worker_threads): - t = Thread(target=worker) - t.daemon = True - t.start() - threads.append(t) + t = Thread(target=worker) + t.daemon = True + t.start() + threads.append(t) # wait until they finished doing the work # 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 while True: - if tasks.empty() == True: - break + if tasks.empty() is True: + break - # sleep while the workers work - time.sleep(1) + # sleep while the workers work + time.sleep(1) print("all worker threads ended") diff --git a/renderlib.py b/renderlib.py index 1b42979..a4e9fda 100644 --- a/renderlib.py +++ b/renderlib.py @@ -1,10 +1,10 @@ #!/usr/bin/python3 +# vim: tabstop=4 shiftwidth=4 expandtab import os import sys import re import glob -import math import shutil import errno from lxml import etree @@ -22,263 +22,267 @@ args = None cssutils.ser.prefs.lineSeparator = ' ' cssutils.log.setLevel(logging.FATAL) + def loadProject(projectname): - sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), projectname)) - return __import__(projectname) + sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), projectname)) + return __import__(projectname) + def easeDelay(easer, delay, t, b, c, d, *args): - if t < delay: - return b + if t < delay: + return b - if t - delay > d: - return b+c + if t - delay > d: + return b + c + + return easer(t - delay, b, c, d, *args) - return easer(t - delay, b, c, d, *args) class Rendertask: - def __init__(self, infile, sequence, parameters={}, outfile=None, workdir='.'): - 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 + def __init__(self, infile, sequence, parameters={}, outfile=None, workdir='.'): + 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 - def fromtupel(tuple): - return Rendertask(tuple[0], tuple[2], tuple[3], tuple[1]) + def fromtupel(tuple): + 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 def ensurePathExists(path): - try: - os.makedirs(path) - except OSError as exception: - if exception.errno != errno.EEXIST: - raise + try: + os.makedirs(path) + except OSError as exception: + if exception.errno != errno.EEXIST: + raise + # remove the files matched by the pattern def ensureFilesRemoved(pattern): - for f in glob.glob(pattern): - os.unlink(f) + for f in glob.glob(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)) + 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 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')): shutil.rmtree(os.path.join(task.workdir, '.frames')) - # make sure a .frames-directory exists in out workdir - ensurePathExists(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: - svgstr = fp.read() - for key in task.parameters.keys(): - svgstr = svgstr.replace(key, xmlescape(str(task.parameters[key]))) + # open and parse the input file + 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]))) - parser = etree.XMLParser(huge_tree=True) - svg = etree.fromstring(svgstr.encode('utf-8'), parser) + parser = etree.XMLParser(huge_tree=True) + svg = etree.fromstring(svgstr.encode('utf-8'), parser) - #if '$subtitle' in task.parameters and task.parameters['$subtitle'] == '': - # child = svg.findall(".//*[@id='subtitle']")[0] - # child.getparent().remove(child) + # if '$subtitle' in task.parameters and task.parameters['$subtitle'] == '': + # child = svg.findall(".//*[@id='subtitle']")[0] + # child.getparent().remove(child) - # frame-number counter - frameNr = 0 + # frame-number counter + frameNr = 0 - # iterate through the animation seqence frame by frame - # frame is a ... tbd - cache = {} - for frame in task.sequence(task.parameters): - skip_rendering = False - # skip first n frames, to speed up rerendering during debugging - if 'only_rerender_frames_after' in task.parameters: - skip_rendering = (frameNr <= task.parameters['only_rerender_frames_after']) + # iterate through the animation seqence frame by frame + # frame is a ... tbd + cache = {} + for frame in task.sequence(task.parameters): + skip_rendering = False + # skip first n frames, to speed up rerendering during debugging + if 'only_rerender_frames_after' in task.parameters: + skip_rendering = (frameNr <= task.parameters['only_rerender_frames_after']) - if args.skip_frames: - skip_rendering = (frameNr <= args.skip_frames) + if args.skip_frames: + skip_rendering = (frameNr <= args.skip_frames) - if args.only_frame: - skip_rendering = (frameNr != args.only_frame) + if args.only_frame: + skip_rendering = (frameNr != args.only_frame) - # print a line for each and every frame generated - if debug and not skip_rendering: - print("frameNr {0:3d} => {1}".format(frameNr, frame)) + # print a line for each and every frame generated + if debug and not skip_rendering: + print("frameNr {0:3d} => {1}".format(frameNr, frame)) - frame = tuple(frame) - if frame in cache: - if debug: - print("cache hit, reusing frame {0}".format(cache[frame])) + frame = tuple(frame) + if frame in cache: + if debug: + print("cache hit, reusing frame {0}".format(cache[frame])) - framedir = task.workdir + "/.frames/" - shutil.copyfile("{0}/{1:04d}.png".format(framedir, cache[frame]), "{0}/{1:04d}.png".format(framedir, frameNr)) + framedir = task.workdir + "/.frames/" + shutil.copyfile("{0}/{1:04d}.png".format(framedir, cache[frame]), "{0}/{1:04d}.png".format(framedir, frameNr)) - frameNr += 1 - continue - elif not skip_rendering: - cache[frame] = frameNr + frameNr += 1 + continue + 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 + # 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 - 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 + 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 == 'attr': + el.attrib[key] = str(value) - elif type == 'text': - el.text = 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 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'): - width = 1920 - height = 1080 - else: - width = 1024 - height = 576 + if task.outfile.endswith('.ts') or task.outfile.endswith('.mov'): + width = 1920 + height = 1080 + else: + width = 1024 + height = 576 - # 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) + # 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 - frameNr += 1 + # increment frame-number + 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: - task.outfile = '{0}.frame{1:04d}.png'.format(task.outfile, args.only_frame) + if task.outfile.endswith('.png'): + 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 - ensureFilesRemoved(os.path.join(task.workdir, task.outfile)) + cmd += '-map 0:0 -c:v mpeg2video -q:v 2 -aspect 16:9 ' - if task.outfile.endswith('.png'): - cmd = 'cd {0} && cp ".frames/{1:04d}.png" "{2}"'.format(task.workdir, args.only_frame, task.outfile) + if task.audiofile is None: + 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 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) + if debug: + print(cmd) - 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: - 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) + # as before, in non-debug-mode the thread-worker does all progress messages + if debug: + if r != 0: + sys.exit() - if debug: - print(cmd) - - 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')) + 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 events(scheduleUrl, titlemap={}): - print("downloading schedule") + print("downloading schedule") - # download the schedule - response = urlopen(scheduleUrl) + # download the schedule + response = urlopen(scheduleUrl) - # read xml-source - xml = response.read() + # read xml-source + xml = response.read() - # parse into ElementTree - parser = etree.XMLParser(huge_tree=True) - schedule = etree.fromstring(xml, parser) + # parse into ElementTree + parser = etree.XMLParser(huge_tree=True) + schedule = etree.fromstring(xml, parser) - # iterate all days - for day in schedule.iter('day'): - # iterate all rooms - for room in day.iter('room'): - # iterate events on that day in this room - for event in room.iter('event'): - # aggregate names of the persons holding this talk - personnames = [] - if event.find('persons') is not None: - for person in event.find('persons').iter('person'): - personname = re.sub( '\s+', ' ', person.text ).strip() - personnames.append(personname) + # iterate all days + for day in schedule.iter('day'): + # iterate all rooms + for room in day.iter('room'): + # iterate events on that day in this room + for event in room.iter('event'): + # aggregate names of the persons holding this talk + personnames = [] + if event.find('persons') is not None: + for person in event.find('persons').iter('person'): + personname = re.sub(r'\s+', ' ', person.text).strip() + personnames.append(personname) - id = int(event.get('id')) + id = int(event.get('id')) - if id in titlemap: - title = titlemap[id] - elif event.find('title') is not None and event.find('title').text is not None: - title = re.sub( '\s+', ' ', event.find('title').text ).strip() - else: - title = '' + if id in titlemap: + title = titlemap[id] + elif event.find('title') is not None and event.find('title').text is not None: + title = re.sub(r'\s+', ' ', event.find('title').text).strip() + else: + title = '' - if event.find('subtitle') is not None and event.find('subtitle').text is not None: - subtitle = re.sub( '\s+', ' ', event.find('subtitle').text ).strip() - else: - subtitle = '' + if event.find('subtitle') is not None and event.find('subtitle').text is not None: + subtitle = re.sub(r'\s+', ' ', event.find('subtitle').text).strip() + else: + 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: - from termcolor import colored + from termcolor import colored except ImportError: - def colored(str, col): - return str + def colored(str, col): + return str