Add generator for Adobe After Effects + Template
This commit is contained in:
parent
17536a75a7
commit
b9e7f008d0
5 changed files with 183 additions and 0 deletions
166
make-adobe-after-effects.py
Executable file
166
make-adobe-after-effects.py
Executable file
|
@ -0,0 +1,166 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import renderlib
|
||||||
|
import argparse
|
||||||
|
import tempfile
|
||||||
|
import shlex
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
''')
|
||||||
|
|
||||||
|
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
|
||||||
|
''')
|
||||||
|
|
||||||
|
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
|
||||||
|
''')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
def headline(str):
|
||||||
|
print("##################################################")
|
||||||
|
print(str)
|
||||||
|
print("##################################################")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def error(str):
|
||||||
|
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")
|
||||||
|
|
||||||
|
if not args.debug and not args.schedule:
|
||||||
|
error("Either specify --debug 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',
|
||||||
|
}]
|
||||||
|
|
||||||
|
else:
|
||||||
|
events = list(renderlib.events(args.schedule))
|
||||||
|
|
||||||
|
def describe_event(event):
|
||||||
|
return "#{}: {}".format(event['id'], event['title'])
|
||||||
|
|
||||||
|
|
||||||
|
def event_print(event, message):
|
||||||
|
print("{} – {}".format(describe_event(event), message))
|
||||||
|
|
||||||
|
|
||||||
|
tempdir = tempfile.TemporaryDirectory()
|
||||||
|
print('working in '+tempdir.name)
|
||||||
|
|
||||||
|
|
||||||
|
def fmt_command(command, **kwargs):
|
||||||
|
args = {}
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
args[key] = shlex.quote(value)
|
||||||
|
|
||||||
|
command = command.format(**args)
|
||||||
|
return shlex.split(command)
|
||||||
|
|
||||||
|
|
||||||
|
def run(command, **kwargs):
|
||||||
|
return subprocess.check_call(
|
||||||
|
fmt_command(command, **kwargs),
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
stdout=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
|
||||||
|
def enqueue_job(event):
|
||||||
|
event_id = str(event['id'])
|
||||||
|
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')
|
||||||
|
|
||||||
|
with open(args.project+'intro.jsx', 'r') as fp:
|
||||||
|
scriptstr = fp.read()
|
||||||
|
|
||||||
|
for key, value in event.items():
|
||||||
|
scriptstr = scriptstr.replace("$"+str(key), xmlescape(str(value)))
|
||||||
|
|
||||||
|
with open(script_doc, 'w') as fp:
|
||||||
|
fp.write(scriptstr)
|
||||||
|
|
||||||
|
copyfile(args.project+'intro.aep',work_doc)
|
||||||
|
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)
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
run('ffmpeg -y -hide_banner -loglevel error -i "{input}" -ar 48000 -ac 1 -f s16le -i /dev/zero -map 0:v -c:v mpeg2video -q:v 0 -aspect 16:9 -map 1:0 -map 1:0 -map 1:0 -map 1:0 -shortest -f mpegts "{output}"',
|
||||||
|
input=intermediate_clip,
|
||||||
|
output=final_clip)
|
||||||
|
|
||||||
|
event_print(event, "finalized intro to "+final_clip)
|
||||||
|
|
||||||
|
|
||||||
|
if args.ids:
|
||||||
|
print("enqueuing {} jobs into aerender".format(len(args.ids)))
|
||||||
|
else:
|
||||||
|
print("enqueuing {} jobs into aerender".format(len(events)))
|
||||||
|
for event in events:
|
||||||
|
if args.ids and event['id'] not in args.ids:
|
||||||
|
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)
|
||||||
|
|
||||||
|
event_print(event, "finalizing job")
|
||||||
|
finalize_job(job_id, event)
|
||||||
|
|
||||||
|
print('all done, cleaning up '+tempdir.name)
|
||||||
|
tempdir.cleanup()
|
BIN
voc_ae/(Footage)/c3voc.png
Normal file
BIN
voc_ae/(Footage)/c3voc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
BIN
voc_ae/intro.aep
Normal file
BIN
voc_ae/intro.aep
Normal file
Binary file not shown.
17
voc_ae/intro.jsx
Normal file
17
voc_ae/intro.jsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
var comp = app.project.item(2);
|
||||||
|
|
||||||
|
var layer_title = comp.layer('intro_title');
|
||||||
|
var textProp_title = layer_title.property("Source Text");
|
||||||
|
var textDocument_title = textProp_title.value;
|
||||||
|
|
||||||
|
var layer_persons = comp.layer('intro_personnames');
|
||||||
|
var textProp_persons = layer_persons.property("Source Text");
|
||||||
|
var textDocument_persons = textProp_persons.value;
|
||||||
|
|
||||||
|
textDocument_title.text = "$title";
|
||||||
|
textProp_title.setValue(textDocument_title);
|
||||||
|
|
||||||
|
textDocument_persons.text = "$personnames";
|
||||||
|
textProp_persons.setValue(textDocument_persons);
|
||||||
|
|
||||||
|
app.project.save();
|
BIN
voc_ae/intro.scpt
Normal file
BIN
voc_ae/intro.scpt
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue