Skip to content

Instantly share code, notes, and snippets.

@LittleToonCat
Last active August 3, 2020 12:22
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LittleToonCat/9f312f15e7e2b78956a2f40b7af29c08 to your computer and use it in GitHub Desktop.
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
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