Created
January 7, 2021 05:34
-
-
Save baku89/9293282456d35ebaed684cad21aba06c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os, sys, subprocess | |
import shutil | |
import hou | |
import toolutils | |
import itertools | |
import tempfile | |
import datetime | |
#---------------------------------------- | |
# Common | |
def getCachePath(): | |
node = hou.pwd() | |
out_path = node.parm('output').eval() | |
out_dir = os.path.dirname(out_path) | |
cache_name = os.path.splitext(os.path.basename(out_path))[0] + '_cache' | |
cache_path = os.path.join(out_dir, cache_name, '%s.$F4.jpg' % cache_name) | |
return cache_path | |
def getFrameRange(mode): | |
node = hou.pwd() | |
start, end, step = node.parmTuple('frange').evalAsInts() | |
if mode == 'current': | |
start = min(int(hou.frame()), end) | |
return (start, end, step) | |
#---------------------------------------- | |
# Flipbook | |
def getViewname(): | |
viewer = toolutils.sceneViewer() | |
viewname = { | |
'desktop' : viewer.pane().desktop().name(), | |
'pane' : viewer.name(), | |
'type' :'world', | |
'viewport': viewer.curViewport().name() | |
} | |
return '{desktop}.{pane}.{type}.{viewport}'.format(**viewname) | |
def viewwrite(options='', outpath='ip'): | |
current_view = getViewname() | |
cmd = "viewwrite %s %s '%s'" % (options, current_view, outpath) | |
hou.hscript(cmd) | |
#---------------------------------------- | |
# Export video using FFMPEG | |
flatten = lambda x: [z for y in x for z in (flatten(y) if hasattr(y, '__iter__') and not isinstance(y, str) else (y,))] | |
def addDateSuffix(path): | |
directory, filename = os.path.split(path) | |
name, ext = os.path.splitext(filename) | |
suffix = datetime.datetime.now().strftime('_%y-%m-%d_%H-%M-%S') | |
return os.path.join(directory, name + suffix + ext) | |
def runCommand(command, cwd): | |
fh = open("NUL","w") | |
process = subprocess.Popen(command, stdout=subprocess.PIPE, cwd=cwd) | |
while True: | |
output = process.stdout.readline() | |
if output == '' and process.poll() is not None: | |
break | |
if output: | |
pass | |
print output.strip() | |
rc = process.poll() | |
try: | |
process.terminate() | |
except: | |
pass | |
return rc | |
def openFile(filename): | |
if sys.platform == "win32": | |
os.startfile(filename) | |
else: | |
opener ="open" if sys.platform == "darwin" else "xdg-open" | |
subprocess.call([opener, filename]) | |
def execute(range_mode): | |
node = hou.pwd() | |
start, end, step = getFrameRange(range_mode) | |
fps = node.parm('fps').evalAsInt() | |
resx, resy = node.parmTuple('res').evalAsInts() | |
cache_path = getCachePath() | |
cache_dir = os.path.dirname(cache_path) | |
out_file = os.path.abspath(node.parm('output').eval()) | |
# Export Cache | |
try: | |
if os.path.exists(cache_dir): | |
shutil.rmtree(cache_dir) | |
os.makedirs(cache_dir) | |
except OSError: | |
raise(Exception('Cannot create cache directory.')) | |
options = '-f %d %d -i %d -c -r %d %d' % (start, end, step, resx, resy) | |
viewwrite(options, cache_path) | |
# Manage when output file already exists | |
if os.path.exists(out_file): | |
overwrite_mode = node.parm('overwrite').evalAsString() | |
if overwrite_mode == 'date': | |
out_file = addDateSuffix(out_file) | |
elif overwrite_mode == 'skip': | |
return | |
# Gather cached images | |
files = [(i, f, hou.expandStringAtFrame(cache_path, f)) for i, f in enumerate(range(start, end + 1, step))] | |
# Filter only existing files | |
files = [x for x in files if os.path.exists(x[2])] | |
# Make all paths relative to the list file | |
files = [(i, f, os.path.relpath(path, cache_dir)) for i, f, path in files] | |
# Determine the location of the list file | |
txt_path = os.path.abspath(os.path.join(cache_dir, hou.expandString("filelist.export_mp4.txt"))) | |
# Export file list | |
txt = "" | |
for i, frame, path in files: | |
duration = 1 / float(fps) | |
if i + 1 < len(files): | |
duration *= files[i+1][1] - frame | |
txt += "file '%s'\nduration %f\n" % (path, duration) | |
with open(txt_path, 'w') as txt_file: | |
txt_file.write(txt) | |
# Create intermediate directories | |
out_dir = os.path.dirname(out_file) | |
if not os.path.exists(out_dir): | |
os.makedirs(out_dir) | |
# Execute | |
ffmpeg_path = node.parm('ffmpeg').eval() | |
inputs = ['-i', txt_path] | |
# Setup audio | |
audio_file = node.parm('audio').eval() | |
if audio_file: | |
audio_file = os.path.abspath(audio_file) | |
audio_start = hou.frameToTime(node.parm('audio_offset_frame').eval()) | |
video_start = hou.frameToTime(start) | |
offset = audio_start - video_start | |
inputs.extend(['-ss', str(-offset), '-i', audio_file]) | |
# Setup filters | |
filter_complex = ['fps=%d' % fps] | |
# Resize | |
# filter_complex.append("scale=%d:%d" % node.parmTuple('res').eval()) | |
if node.parm('usebgcolor').eval(): | |
color = node.parmTuple('bgcolor').eval() | |
color = '#%02x%02x%02x' % tuple([int(v * 255) for v in color]) | |
filter_complex.append("split=2[bg][fg];[bg]drawbox=c=%s@1:replace=1:t=fill[bg];[bg][fg]overlay=format=auto" % color) | |
args = flatten([ | |
ffmpeg_path, | |
'-hide_banner', | |
'-loglevel', 'panic', | |
'-y', | |
'-threads', '64', | |
'-apply_trc', 'bt709', | |
'-f', 'concat', | |
'-safe', '0', | |
inputs, | |
'-strict', '-2', | |
'-c:v', 'libx264', | |
'-preset', 'ultrafast', | |
'-qp', '0', | |
'-crf', '20', | |
'-pix_fmt', 'yuv420p', | |
'-filter_complex', '[0]' + ','.join(filter_complex), | |
['-c:a', 'aac'] if audio_file else [], | |
'-shortest', | |
out_file]) | |
# Execute the conversion | |
rc = runCommand(args, cache_dir) | |
if rc != 0: | |
raise(Exception('Exporting unsuccessfully finished.')) | |
if node.parm('play_output').eval(): | |
openFile(out_file) | |
# Clear cache images | |
try: | |
shutil.rmtree(cache_dir) | |
except OSError: | |
pass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment