uncomment quit command in after effects script example\

This commit is contained in:
Leif Niemczik 2024-10-06 14:18:58 +02:00
parent 94f09b880a
commit 22397986a7
2 changed files with 269 additions and 136 deletions

View file

@ -12,84 +12,171 @@ import os
import platform import platform
from shutil import copyfile from shutil import copyfile
titlemap = ( titlemap = ({"id": "000000", "title": "Short title goes here"},)
{'id': "000000", 'title': "Short title goes here"},
)
# 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 filename_of_intro.aepx", usage="./make-adobe-after-effects.py yourproject/ https://url/to/schedule.xml filename_of_intro.aepx",
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 Path to your project folder with After Effects Files
''') """,
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('introfile', action="store", metavar='Name of intro source file', default='intro.aepx', type=str, nargs='?', help=''' parser.add_argument(
"introfile",
action="store",
metavar="Name of intro source file",
default="intro.aepx",
type=str,
nargs="?",
help="""
Filename of the intro source file inside the project folder Filename of the intro source file inside the project folder
''') """,
)
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('--day', dest='days', nargs='+', action="store", type=str, help=''' parser.add_argument(
"--day",
dest="days",
nargs="+",
action="store",
type=str,
help="""
Only render from your projects schedule for the given days. Only render from your projects schedule for the given days.
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/ --day "1" "3" Usage: ./make-adobe-after-effects.py yourproject/ --day "1" "3"
''') """,
)
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.aepx file in the project folder. Render a pause loop from the pause.aepx file in the project folder.
''') """,
)
parser.add_argument('--alpha', action="store_true", default=False, help=''' parser.add_argument(
"--alpha",
action="store_true",
default=False,
help="""
Render intro/outro with alpha. Render intro/outro with alpha.
''') """,
)
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.aepx file in the project folder. Render outro from the outro.aepx 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.aepx file in the project folder. Render background loop from the bgloop.aepx file in the project folder.
''') """,
)
parser.add_argument('--keep', action="store_true", default=False, help=''' parser.add_argument(
"--keep",
action="store_true",
default=False,
help="""
Keep source file in the project folder after render. Keep source file in the project folder after render.
''') """,
)
parser.add_argument('--mp4', action="store_true", default=False, help=''' parser.add_argument(
"--mp4",
action="store_true",
default=False,
help="""
Also create a MP4 for preview. Also create a MP4 for preview.
''') """,
)
args = parser.parse_args() args = parser.parse_args()
@ -109,53 +196,67 @@ def error(str):
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 = ['blubbel'] # persons = ['blubbel']
persons = ['Vitor Sakaguti', 'Sara', 'A.L. Fehlhaber'] persons = ["Vitor Sakaguti", "Sara", "A.L. Fehlhaber"]
events = [{ events = [
'id': 11450, {
'title': 'PQ Mail: Enabling post quantum secure encryption for email communication', "id": 11450,
'subtitle': '', "title": "PQ Mail: Enabling post quantum secure encryption for email communication",
'persons': persons, "subtitle": "",
'personnames': ', '.join(persons), "persons": persons,
'room': 'rc1', "personnames": ", ".join(persons),
}] "room": "rc1",
}
]
elif args.pause: elif args.pause:
events = [{ events = [
'id': 'pause', {
'title': 'Pause Loop', "id": "pause",
}] "title": "Pause Loop",
}
]
elif args.outro: elif args.outro:
events = [{ events = [
'id': 'outro', {
'title': 'Outro', "id": "outro",
}] "title": "Outro",
}
]
elif args.bgloop: elif args.bgloop:
events = [{ events = [
'id': 'bgloop', {
'title': 'Background Loop', "id": "bgloop",
}] "title": "Background Loop",
}
]
else: else:
events = list(schedulelib.events(args.schedule)) events = list(schedulelib.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(dir=os.getcwd())
print('working in ' + tempdir.name) print("working in " + tempdir.name)
def fmt_command(command, **kwargs): def fmt_command(command, **kwargs):
@ -171,13 +272,14 @@ 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 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), stderr=subprocess.STDOUT
stderr=subprocess.STDOUT) )
def run_once(command, **kwargs): def run_once(command, **kwargs):
@ -189,35 +291,43 @@ def run_once(command, **kwargs):
stdout=None, stdout=None,
stderr=None, stderr=None,
close_fds=True, close_fds=True,
creationflags=DETACHED_PROCESS) creationflags=DETACHED_PROCESS,
)
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 (
event_print(event, "file exist, skipping " + str(event['id'])) 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 return
work_doc = os.path.join(tempdir.name, event_id + '.aepx') work_doc = os.path.join(tempdir.name, event_id + ".aepx")
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 + '.aepx', work_doc) copyfile(args.project + event_id + ".aepx", work_doc)
if platform.system() == 'Darwin': if platform.system() == "Darwin":
run(r'/Applications/Adobe\ After\ Effects\ 2024/aerender -project {jobpath} -comp {comp} -mp -output {locationpath}', run(
r"/Applications/Adobe\ After\ Effects\ 2024/aerender -project {jobpath} -comp {comp} -mp -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(r'C:/Program\ Files/Adobe/Adobe\ After\ Effects\ 2024/Support\ Files/aerender.exe -project {jobpath} -comp {comp} -mp -output {locationpath}', run(
r"C:/Program\ Files/Adobe/Adobe\ After\ Effects\ 2024/Support\ Files/aerender.exe -project {jobpath} -comp {comp} -mp -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()
scriptstr = scriptstr.replace("$filename", work_doc.replace("\\", "/")) scriptstr = scriptstr.replace("$filename", work_doc.replace("\\", "/"))
@ -225,91 +335,114 @@ def enqueue_job(event):
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+args.introfile, work_doc) copyfile(args.project + args.introfile, 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} {scriptpath}', run(
"osascript {ascript_path} {scriptpath}",
scriptpath=script_doc, scriptpath=script_doc,
ascript_path=ascript_doc) ascript_path=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(r'/Applications/Adobe\ After\ Effects\ 2024/aerender -project {jobpath} -comp "intro" -mp -output {locationpath}', run(
r'/Applications/Adobe\ After\ Effects\ 2024/aerender -project {jobpath} -comp "intro" -mp -output {locationpath}',
jobpath=work_doc, jobpath=work_doc,
locationpath=intermediate_clip) locationpath=intermediate_clip,
)
if platform.system() == 'Windows': if platform.system() == "Windows":
run_once(r'C:/Program\ Files/Adobe/Adobe\ After\ Effects\ 2024/Support\ Files/AfterFX.exe -noui -r {scriptpath}', run_once(
scriptpath=script_doc) r"C:/Program\ Files/Adobe/Adobe\ After\ Effects\ 2024/Support\ Files/AfterFX.exe -noui -r {scriptpath}",
scriptpath=script_doc,
)
time.sleep(5) time.sleep(5)
run(r'C:/Program\ Files/Adobe/Adobe\ After\ Effects\ 2024/Support\ Files/aerender.exe -project {jobpath} -comp "intro" -mfr on 100 -output {locationpath}', run(
r'C:/Program\ Files/Adobe/Adobe\ After\ Effects\ 2024/Support\ Files/aerender.exe -project {jobpath} -comp "intro" -mfr on 100 -output {locationpath}',
jobpath=work_doc, jobpath=work_doc,
locationpath=intermediate_clip) locationpath=intermediate_clip,
)
if args.debug or args.keep: if args.debug or args.keep:
path = tempdir.name path = tempdir.name
dirs = os.listdir(path) dirs = os.listdir(path)
for file in dirs: for file in dirs:
print(file) print(file)
copyfile(work_doc, args.project + event_id + '.aepx') copyfile(work_doc, args.project + event_id + ".aepx")
copyfile(script_doc, args.project + event_id + '.jsx') copyfile(script_doc, args.project + event_id + ".jsx")
copyfile(intermediate_clip, args.project + event_id + '.mov') copyfile(intermediate_clip, args.project + event_id + ".mov")
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")
preview = os.path.join(os.path.dirname(args.project), event_id + '.mp4') preview = os.path.join(os.path.dirname(args.project), event_id + ".mp4")
if args.alpha: if args.alpha:
ffprobe = run_output('ffprobe -i {input} -show_streams -select_streams a -loglevel error', ffprobe = run_output(
input=intermediate_clip) "ffprobe -i {input} -show_streams -select_streams a -loglevel error",
input=intermediate_clip,
)
if ffprobe: if ffprobe:
run('ffmpeg -threads 0 -y -hide_banner -loglevel error -i {input} -c:v qtrle -movflags faststart -aspect 16:9 -c:a mp2 -b:a 384k -shortest -f mov {output}', run(
"ffmpeg -threads 0 -y -hide_banner -loglevel error -i {input} -c:v qtrle -movflags faststart -aspect 16:9 -c:a mp2 -b:a 384k -shortest -f mov {output}",
input=intermediate_clip, input=intermediate_clip,
output=final_clip) output=final_clip,
)
else: else:
run('ffmpeg -threads 0 -y -hide_banner -loglevel error -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000 -i {input} -c:v qtrle -movflags faststart -aspect 16:9 -c:a mp2 -b:a 384k -shortest -f mov {output}', run(
"ffmpeg -threads 0 -y -hide_banner -loglevel error -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000 -i {input} -c:v qtrle -movflags faststart -aspect 16:9 -c:a mp2 -b:a 384k -shortest -f mov {output}",
input=intermediate_clip, input=intermediate_clip,
output=final_clip) output=final_clip,
)
else: else:
ffprobe = run_output('ffprobe -i {input} -show_streams -select_streams a -loglevel error', ffprobe = run_output(
input=intermediate_clip) "ffprobe -i {input} -show_streams -select_streams a -loglevel error",
input=intermediate_clip,
)
if ffprobe: if ffprobe:
event_print(event, "finalize with audio from source file") event_print(event, "finalize with audio from source file")
run('ffmpeg -threads 0 -y -hide_banner -loglevel error -i {input} -c:v mpeg2video -q:v 2 -aspect 16:9 -c:a mp2 -b:a 384k -shortest -f mpegts {output}', run(
"ffmpeg -threads 0 -y -hide_banner -loglevel error -i {input} -c:v mpeg2video -q:v 2 -aspect 16:9 -c:a mp2 -b:a 384k -shortest -f mpegts {output}",
input=intermediate_clip, input=intermediate_clip,
output=final_clip) output=final_clip,
)
else: else:
event_print(event, "finalize with silent audio") event_print(event, "finalize with silent audio")
run('ffmpeg -threads 0 -y -hide_banner -loglevel error -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000 -i {input} -c:v mpeg2video -q:v 2 -aspect 16:9 -c:a mp2 -b:a 384k -shortest -f mpegts {output}', run(
"ffmpeg -threads 0 -y -hide_banner -loglevel error -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000 -i {input} -c:v mpeg2video -q:v 2 -aspect 16:9 -c:a mp2 -b:a 384k -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.mp4: if args.mp4:
run('ffmpeg -threads 0 -y -hide_banner -loglevel error -i {input} {output}', run(
"ffmpeg -threads 0 -y -hide_banner -loglevel error -i {input} {output}",
input=final_clip, input=final_clip,
output=preview) output=preview,
)
event_print(event, "created mp4 preview " + preview) event_print(event, "created mp4 preview " + preview)
if args.ids: if args.ids:
if len(args.ids) == 1: if len(args.ids) == 1:
print("enqueuing {} job into aerender".format(len(args.ids))) print("enqueuing {} job into aerender".format(len(args.ids)))
@ -322,25 +455,25 @@ 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
if args.days and event['day'] not in args.days: if args.days and event["day"] not in args.days:
print("skipping day %s (%s)" % (event['day'], event['title'])) print("skipping day %s (%s)" % (event["day"], event["title"]))
continue continue
for item in titlemap: for item in titlemap:
if str(item['id']) == str(event['id']): if str(item["id"]) == str(event["id"]):
title = item['title'] title = item["title"]
event_print(event, "titlemap replacement") event_print(event, "titlemap replacement")
event_print(event, "replacing title %s with %s" % (event['title'], title)) event_print(event, "replacing title %s with %s" % (event["title"], title))
event['title'] = title event["title"] = title
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:
@ -351,19 +484,19 @@ for event in events:
event_print(event, "finalizing job") event_print(event, "finalizing job")
finalize_job(job_id, event) finalize_job(job_id, event)
else: else:
event_id = str(event['id']) event_id = str(event["id"])
event_print(event, "skipping finalizing job") event_print(event, "skipping finalizing job")
if platform.system() == 'Windows': if platform.system() == "Windows":
intermediate_clip = os.path.join(tempdir.name, event_id + '.avi') intermediate_clip = os.path.join(tempdir.name, event_id + ".avi")
final_clip = os.path.join(os.path.dirname(args.project), event_id + '.avi') final_clip = os.path.join(os.path.dirname(args.project), event_id + ".avi")
else: else:
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 + '.mov') final_clip = os.path.join(os.path.dirname(args.project), event_id + ".mov")
copyfile(intermediate_clip, final_clip) copyfile(intermediate_clip, final_clip)
event_print(event, "copied intermediate clip to " + final_clip) event_print(event, "copied intermediate clip to " + final_clip)
if args.debug or args.keep: if args.debug or args.keep:
print('keeping source files in ' + args.project) print("keeping source files in " + args.project)
else: else:
print('all done, cleaning up ' + tempdir.name) print("all done, cleaning up " + tempdir.name)
tempdir.cleanup() tempdir.cleanup()

View file

@ -26,4 +26,4 @@ textDocument_persons.text = "$personnames"
textProp_persons.setValue(textDocument_persons) textProp_persons.setValue(textDocument_persons)
app.project.save() app.project.save()
// app.quit() app.quit()