2018-06-27 15:09:32 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
#
|
|
|
|
# ENNTool -- mIRC art animation tool (for EFnet #MiRCART) (WIP)
|
|
|
|
# Copyright (c) 2018 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
|
|
|
|
# This project is licensed under the terms of the MIT license.
|
|
|
|
#
|
|
|
|
# TODO:
|
2018-07-05 13:14:37 +00:00
|
|
|
# 1) -A, -S: replace w/ -s, implement animation script: render frame #1, render frame #2, ...; scrolling script; effects: rotate, smash into bricks, swirl, wave, ...
|
|
|
|
# 2) Feature: include ETA(s) @ progress bar(s)
|
|
|
|
# 3) Feature: autodetect video width from widest mircart
|
|
|
|
# 4) Feature: render mircart as 3D blocks vs flat surface
|
|
|
|
|
|
|
|
#
|
|
|
|
# 1) Optimisation: speed up ENNToolMiRCARTImporter
|
|
|
|
# 2) Cleanup: use names @ optdict + set from optdefaults
|
|
|
|
# 3) Feature: scrolling speed as <how many Y units>x<count of frame(s)>
|
|
|
|
# 4) Cleanup: use VAOs + glVertexAttribFormat + glVertexAttribBinding
|
|
|
|
# 5) Optimisation: split mIRC art into separate VBOs & implement rudimentary culling
|
|
|
|
# 6) Optimisation: only call glReadPixels() when changes were made relative to the last call
|
|
|
|
# 7) Split video output into separate module, switch to GUI
|
|
|
|
# 8) FBOs http://www.songho.ca/opengl/gl_fbo.html
|
2018-06-27 15:09:32 +00:00
|
|
|
#
|
|
|
|
|
|
|
|
from getopt import getopt, GetoptError
|
|
|
|
from glob import glob
|
|
|
|
from OpenGL.GL import *
|
|
|
|
import os, sys, time
|
|
|
|
import wx
|
|
|
|
|
2018-07-05 13:14:37 +00:00
|
|
|
from ENNToolGLCanvasPanel import ENNToolGLCanvas, ENNToolGLPanel
|
2018-07-02 20:49:43 +00:00
|
|
|
from ENNToolGLTTFTexture import ENNToolGLTTFTexture
|
2018-07-03 16:17:23 +00:00
|
|
|
from ENNToolGLVideoWriter import ENNToolGLVideoWriter
|
2018-06-27 15:09:32 +00:00
|
|
|
from ENNToolMiRCARTImporter import ENNToolMiRCARTImporter
|
|
|
|
|
|
|
|
class ENNToolApp(object):
|
|
|
|
"""XXX"""
|
|
|
|
|
|
|
|
# {{{ parseArgv(self, argv): XXX
|
|
|
|
def parseArgv(self, argv):
|
|
|
|
def usage(argv0):
|
|
|
|
print("usage: {}".format(os.path.basename(argv0)), file=sys.stderr)
|
2018-07-25 12:26:47 +00:00
|
|
|
print(" [-a] [-c x,y,z] [-f fps] [-h] [-o fname]".format(os.path.basename(argv0)), file=sys.stderr)
|
2018-07-03 16:17:23 +00:00
|
|
|
print(" [-p] [-r WxH] [-R WxH] [-s fname]", file=sys.stderr)
|
2018-07-25 12:26:47 +00:00
|
|
|
print(" [-S] [-t float] [-v] [--] fname..", file=sys.stderr)
|
2018-06-27 15:09:32 +00:00
|
|
|
print("", file=sys.stderr)
|
2018-07-04 08:26:41 +00:00
|
|
|
print(" -a........: select animation mode (UNIMPLEMENTED)", file=sys.stderr)
|
2018-07-25 12:26:47 +00:00
|
|
|
print(" -c x,y,z..: specify camera position", file=sys.stderr)
|
2018-06-27 15:09:32 +00:00
|
|
|
print(" -f fps....: set video FPS; defaults to 25", file=sys.stderr)
|
|
|
|
print(" -h........: show this screen", file=sys.stderr)
|
2018-07-03 16:17:23 +00:00
|
|
|
print(" -o fname..: output video filename; extension determines video type", file=sys.stderr)
|
2018-06-27 15:09:32 +00:00
|
|
|
print(" -p........: play video after rendering", file=sys.stderr)
|
|
|
|
print(" -r WxH....: set video resolution; defaults to 1152x864", file=sys.stderr)
|
|
|
|
print(" -R WxH....: set MiRCART cube resolution; defaults to 0.1x0.2", file=sys.stderr)
|
2018-07-03 16:17:23 +00:00
|
|
|
print(" -s fname..: input script filename", file=sys.stderr)
|
2018-06-27 15:09:32 +00:00
|
|
|
print(" -S........: select scrolling mode", file=sys.stderr)
|
2018-07-25 12:26:47 +00:00
|
|
|
print(" -t float..: scrolling rate in Y coordinates per frame", file=sys.stderr)
|
2018-06-27 15:09:32 +00:00
|
|
|
print(" -v........: be verbose", file=sys.stderr)
|
|
|
|
try:
|
2018-07-25 12:26:47 +00:00
|
|
|
optlist, argv = getopt(argv[1:], "Ac:f:ho:pr:R:s:St:v")
|
2018-06-27 15:09:32 +00:00
|
|
|
optdict = dict(optlist)
|
|
|
|
|
|
|
|
if "-h" in optdict:
|
|
|
|
usage(sys.argv[0]); exit(0);
|
|
|
|
elif not len(argv):
|
2018-07-03 16:17:23 +00:00
|
|
|
raise GetoptError("at least one MiRCART input fname must be specified")
|
2018-06-27 15:09:32 +00:00
|
|
|
|
|
|
|
if not "-f" in optdict:
|
|
|
|
optdict["-f"] = "25"
|
|
|
|
if not "-r" in optdict:
|
|
|
|
optdict["-r"] = "1152x864"
|
|
|
|
if not "-R" in optdict:
|
|
|
|
optdict["-R"] = "0.1x0.2"
|
|
|
|
|
2018-07-25 12:26:47 +00:00
|
|
|
if "-c" in optdict:
|
|
|
|
optdict["-c"] = [float(r) for r in optdict["-c"].split(",")][0:3]
|
2018-06-27 15:09:32 +00:00
|
|
|
if "-r" in optdict:
|
|
|
|
optdict["-r"] = [int(r) for r in optdict["-r"].split("x")][0:2]
|
|
|
|
if "-R" in optdict:
|
|
|
|
optdict["-R"] = [float(r) for r in optdict["-R"].split("x")][0:2]
|
|
|
|
except GetoptError as e:
|
|
|
|
print(e.msg); usage(sys.argv[0]); exit(1);
|
|
|
|
return argv, optdict
|
|
|
|
# }}}
|
|
|
|
# {{{ printProgress(self, progressCur, progressMax): XXX
|
|
|
|
def printProgress(self, progressCur, progressMax):
|
|
|
|
progressDiv = float(progressCur / progressMax)
|
|
|
|
if progressDiv >= 1:
|
|
|
|
progressDiv = 1; endChar = "\n";
|
|
|
|
else:
|
|
|
|
endChar = ""
|
|
|
|
print("\r[{:<50}] {}%".format(
|
|
|
|
("=" * int(progressDiv * 50)), int(progressDiv * 100)), end=endChar)
|
|
|
|
# }}}
|
2018-07-25 12:26:47 +00:00
|
|
|
# {{{ modeScroll(self, argv, optdict, GLVideoWriter, GLpanel, GLpanel, fps=25): XXX
|
|
|
|
def modeScroll(self, argv, optdict, GLVideoWriter, GLcanvas, GLpanel, fps=25):
|
2018-06-27 15:09:32 +00:00
|
|
|
MiRCART = []
|
2018-07-25 12:26:47 +00:00
|
|
|
if "-t" in optdict:
|
|
|
|
scrollRate = float(optdict["-t"])
|
|
|
|
else:
|
|
|
|
scrollRate = 0.1
|
2018-07-04 08:26:41 +00:00
|
|
|
if "-v" in optdict:
|
|
|
|
time0 = time.time()
|
2018-06-27 15:09:32 +00:00
|
|
|
for inFileArg in argv:
|
|
|
|
for inFile in sorted(glob(inFileArg)):
|
|
|
|
MiRCART += ENNToolMiRCARTImporter(inFile).outMap
|
2018-07-04 08:26:41 +00:00
|
|
|
if "-v" in optdict:
|
|
|
|
print("mIRC art import delta {:.3f}ms".format((time.time() - time0) * 1000))
|
2018-06-27 15:09:32 +00:00
|
|
|
|
2018-07-04 08:26:41 +00:00
|
|
|
if "-v" in optdict:
|
|
|
|
time0 = time.time()
|
2018-07-02 20:49:43 +00:00
|
|
|
artTextureId, artInfo = ENNToolGLTTFTexture(MiRCART, optdict["-R"], optdict["-r"]).getParams()
|
2018-07-04 08:26:41 +00:00
|
|
|
if "-v" in optdict:
|
|
|
|
print("TTF texture generation delta {:.3f}ms".format((time.time() - time0) * 1000))
|
2018-07-05 13:14:37 +00:00
|
|
|
artVbo, artVboLen, lastY, numVertices = GLcanvas.renderMiRCART(artInfo, MiRCART, cubeSize=optdict["-R"])
|
2018-06-27 15:09:32 +00:00
|
|
|
if "-v" in optdict:
|
|
|
|
print("{} vertices".format(numVertices))
|
2018-07-05 13:14:37 +00:00
|
|
|
def scrollFrameFun():
|
|
|
|
curY, rotateX, rotateY, translateY = 0, 0, 0, scrollRate
|
|
|
|
w, h = GLcanvas.GetClientSize(); w, h = max(w, 1.0), max(h, 1.0);
|
|
|
|
def scrollFrame():
|
|
|
|
nonlocal curY
|
|
|
|
self.printProgress(curY, lastY)
|
|
|
|
GLcanvas.renderFrame(artTextureId, artVbo, artVboLen)
|
2018-06-27 15:09:32 +00:00
|
|
|
if translateY:
|
|
|
|
glTranslatef(0, translateY, 0); curY += translateY
|
|
|
|
if rotateX:
|
|
|
|
glRotatef(rotateX * (180.0/w), 0.0, 1.0, 0.0)
|
|
|
|
if rotateY:
|
|
|
|
glRotatef(rotateY * (180.0/h), 1.0, 0.0, 0.0)
|
2018-07-05 13:14:37 +00:00
|
|
|
if "-o" in optdict:
|
|
|
|
GLVideoWriter.saveFrame()
|
|
|
|
else:
|
|
|
|
GLcanvas.SwapBuffers()
|
|
|
|
if curY >= lastY:
|
|
|
|
self.printProgress(curY, lastY)
|
|
|
|
if "-o" in optdict:
|
|
|
|
GLVideoWriter.saveVideo()
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
return scrollFrame
|
|
|
|
if "-o" in optdict:
|
|
|
|
frameFun = scrollFrameFun()
|
|
|
|
while True:
|
|
|
|
if not frameFun():
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
GLpanel.frameFun = scrollFrameFun()
|
|
|
|
self.wxApp.MainLoop()
|
2018-06-27 15:09:32 +00:00
|
|
|
# }}}
|
|
|
|
# {{{ __init__(self, argv): XXX
|
|
|
|
def __init__(self, argv):
|
|
|
|
argv, optdict = self.parseArgv(argv)
|
2018-07-05 13:14:37 +00:00
|
|
|
self.wxApp = wx.App(False)
|
|
|
|
appFrameSize = [c + 128 for c in optdict["-r"]]
|
|
|
|
self.appFrame = wx.Frame(None, size=appFrameSize)
|
|
|
|
appPanelSkin = wx.Panel(self.appFrame, wx.ID_ANY)
|
|
|
|
|
|
|
|
videoFps, videoPath = int(optdict["-f"]), optdict["-o"] if "-o" in optdict else None
|
|
|
|
GLpanel = ENNToolGLPanel(appPanelSkin, size=optdict["-r"], parentFrame=self.appFrame)
|
|
|
|
GLcanvas = ENNToolGLCanvas(GLpanel, optdict["-r"])
|
2018-07-25 12:26:47 +00:00
|
|
|
if "-c" in optdict:
|
|
|
|
GLcanvas.initOpenGL(cameraPos=optdict["-c"])
|
|
|
|
else:
|
|
|
|
GLcanvas.initOpenGL()
|
2018-07-05 13:14:37 +00:00
|
|
|
GLcanvas.initShaders()
|
|
|
|
GLVideoWriter = ENNToolGLVideoWriter(videoPath, GLpanel.GetClientSize(), videoFps=videoFps)
|
2018-06-27 15:09:32 +00:00
|
|
|
|
2018-07-05 13:14:37 +00:00
|
|
|
if "-o" in optdict:
|
|
|
|
self.appFrame.Hide()
|
|
|
|
else:
|
|
|
|
self.appFrame.Show(); self.appFrame.SetFocus();
|
2018-06-27 15:09:32 +00:00
|
|
|
|
|
|
|
if "-v" in optdict:
|
|
|
|
time0 = time.time()
|
2018-07-05 13:14:37 +00:00
|
|
|
self.modeScroll(argv, optdict, GLVideoWriter, GLcanvas, GLpanel, fps=videoFps)
|
2018-06-27 15:09:32 +00:00
|
|
|
if "-v" in optdict:
|
|
|
|
print("delta {}s".format(time.time() - time0))
|
2018-07-05 13:14:37 +00:00
|
|
|
if "-o" in optdict \
|
|
|
|
and "-p" in optdict:
|
2018-06-27 15:09:32 +00:00
|
|
|
os.startfile(videoPath)
|
|
|
|
# }}}
|
|
|
|
|
|
|
|
#
|
|
|
|
# Entry point
|
|
|
|
def main(*argv):
|
|
|
|
ENNToolApp(argv)
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main(*sys.argv)
|
|
|
|
|
|
|
|
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120
|