Last active
August 3, 2020 12:22
-
-
Save LittleToonCat/9f312f15e7e2b78956a2f40b7af29c08 to your computer and use it in GitHub Desktop.
Record and encodes Panda3D's Hello World demo into an H.264 video format
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 subprocess, platform, sys | |
from math import pi, sin, cos | |
from direct.showbase.ShowBase import ShowBase | |
from direct.task import Task | |
from direct.actor.Actor import Actor | |
from direct.interval.IntervalGlobal import Sequence | |
from panda3d.core import Point3, ClockObject | |
''' | |
This script runs the Hello World demo from Panda3D's manual, but it will | |
capture every single frame, and encodes it into an H.264 video using FFmpeg. | |
''' | |
cmdstring = [ | |
'ffmpeg.exe' if platform.system() is 'Windows' else 'ffmpeg', | |
'-y', # overwrite the file w/o warning | |
'-r', '%f' % 60.0, # frame rate of encoded video | |
'-an', # no audio | |
'-analyzeduration', '0', # skip auto codec analysis | |
# input params | |
'-s', '800x600', # Panda's window size | |
'-f', 'rawvideo', # RamImage buffer is raw buffer | |
'-pix_fmt', 'bgra', # format of panda texure RamImage buffer | |
'-i', '-', # this means a pipe | |
'-vf', 'vflip', # Since our input is actually flipped upside down, this flag flips it back up. | |
# output params | |
'-vcodec', 'libx264', # Encode into H.264 | |
'HelloWorld.mp4' # output filepath | |
] | |
class MyApp(ShowBase): | |
def __init__(self): | |
ShowBase.__init__(self) | |
# Disable the camera trackball controls. | |
self.disableMouse() | |
# The first frame is a black frame, Make sure we don't capture that. | |
self.firstFrame = True | |
# Load the environment model. | |
self.scene = self.loader.loadModel("models/environment") | |
# Reparent the model to render. | |
self.scene.reparentTo(self.render) | |
# Apply scale and position transforms on the model. | |
self.scene.setScale(0.25, 0.25, 0.25) | |
self.scene.setPos(-8, 42, 0) | |
# Add the spinCameraTask procedure to the task manager. | |
self.taskMgr.add(self.spinCameraTask, "SpinCameraTask") | |
# Load and transform the panda actor. | |
self.pandaActor = Actor("models/panda-model", | |
{"walk": "models/panda-walk4"}) | |
self.pandaActor.setScale(0.005, 0.005, 0.005) | |
self.pandaActor.reparentTo(self.render) | |
# Loop its animation. | |
self.pandaActor.loop("walk") | |
# Create the four lerp intervals needed for the panda to | |
# walk back and forth. | |
pandaPosInterval1 = self.pandaActor.posInterval(13, | |
Point3(0, -10, 0), | |
startPos=Point3(0, 10, 0)) | |
pandaPosInterval2 = self.pandaActor.posInterval(13, | |
Point3(0, 10, 0), | |
startPos=Point3(0, -10, 0)) | |
pandaHprInterval1 = self.pandaActor.hprInterval(3, | |
Point3(180, 0, 0), | |
startHpr=Point3(0, 0, 0)) | |
pandaHprInterval2 = self.pandaActor.hprInterval(3, | |
Point3(0, 0, 0), | |
startHpr=Point3(180, 0, 0)) | |
# Create and play the sequence that coordinates the intervals. | |
self.pandaPace = Sequence(pandaPosInterval1, | |
pandaHprInterval1, | |
pandaPosInterval2, | |
pandaHprInterval2, | |
name="pandaPace") | |
self.pandaPace.loop() | |
# Start FFmpeg | |
self.ffmpeg = subprocess.Popen( | |
cmdstring, | |
stdin=subprocess.PIPE, | |
bufsize=-1, | |
shell=False, | |
) | |
# Set the clock's mode so Panda could render each and every single frame possible. | |
globalClock = ClockObject.getGlobalClock() | |
globalClock.setMode(ClockObject.MNonRealTime) | |
globalClock.setDt(1.0/float(60)) # 60 fps | |
# Add the record task to the task manager | |
taskMgr.add(self.recordTask, "ffmpegTask") | |
# Escape key to exit | |
self.accept('escape', self.exit) | |
# Define a procedure to move the camera. | |
def spinCameraTask(self, task): | |
angleDegrees = task.time * 6.0 | |
angleRadians = angleDegrees * (pi / 180.0) | |
self.camera.setPos(20 * sin(angleRadians), -20.0 * cos(angleRadians), 3) | |
self.camera.setHpr(angleDegrees, 0, 0) | |
return Task.cont | |
# This captures a shot for every frame and passes it over to FFmpeg for encoding | |
def recordTask(self, task): | |
tex = base.win.getScreenshot() # Captures the recently rendered image, and returns it as a Texture. | |
if tex is None: | |
print('WARNING: tex is none!') | |
return task.cont | |
if self.firstFrame: | |
# Don't capture first (black) frame. | |
self.firstFrame = False | |
return task.cont | |
buf = tex.getRamImage().getData() # Get the raw BGRA image data from that capture | |
self.ffmpeg.stdin.write(buf) # Pass it to ffmpeg as text buffer | |
return task.cont | |
def exit(self): | |
self.ffmpeg.stdin.close() # close the pipe so that ffmpeg will close the file properly | |
sys.exit() | |
app = MyApp() | |
app.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment