MJPEG Panda3d streaming server

Here is a hacked snippet that renders the scene into a texture buffer and saves that buffer to a jpeg every now and then. The jpegs are send out as an mjpeg stream which kann be viewed by a lot of browsers without plugin or with Vlc-player. over network. Its an mjpeg server. The server can also serve jpeg files or html files.

To open the stream in browser or vlc-player, open the url: localhost:8080/something.mjpeg

Actually the speed is quite descent. If someone has an idea to make it faster they are very welcome.

Here is the snippet:

from math import pi, sin, cos
from direct.directbase.DirectStart import *
from panda3d.core import *
import string,cgi,time
from os import curdir, sep
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from SocketServer import ThreadingMixIn
import re

count=0
global imgdata
global cameraQuality
cameraQuality=50
class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        global imgdata,count,cameraQuality
        try:
            self.path=re.sub('[^.a-zA-Z0-9]', "",str(self.path))
            if self.path=="" or self.path==None or self.path[:1]==".":
                return
            if self.path.endswith(".html"):
                f = open(curdir + sep + self.path)
                self.send_response(200)
                self.send_header('Content-type',	'text/html')
                self.end_headers()
                self.wfile.write(f.read())
                f.close()
                return
            if self.path.endswith(".mjpeg"):
                self.send_response(200)
                self.wfile.write("Content-Type: multipart/x-mixed-replace; boundary=--aaboundary")
                self.wfile.write("\r\n\r\n")
                while 1:
                    JpegData=imgdata
                    countval=count
                    self.wfile.write("--aaboundary\r\n")
                    self.wfile.write("Content-Type: image/jpeg\r\n")
                    self.wfile.write("Content-length: "+str(len(JpegData))+"\r\n\r\n" )
                    self.wfile.write(JpegData)
                    self.wfile.write("\r\n\r\n\r\n")
                    while countval==count:
                        time.sleep(0.01)
                return
            if self.path.endswith(".jpeg"):
                f = open(curdir + sep + self.path)
                self.send_response(200)
                self.send_header('Content-type','image/jpeg')
                self.end_headers()
                self.wfile.write(f.read())
                f.close()
                return
            return
        except IOError:
            self.send_error(404,'File Not Found: %s' % self.path)
    def do_POST(self):
        global rootnode , cameraQuality
        try:
            ctype, pdict = cgi.parse_header(self.headers.getheader('content-type'))
            if ctype == 'multipart/form-data':
                query=cgi.parse_multipart(self.rfile, pdict)
            self.send_response(301)
            
            self.end_headers()
            upfilecontent = query.get('upfile')
            print "filecontent", upfilecontent[0]
            value=int(upfilecontent[0])
            cameraQuality=max(2, min(99, value))
            self.wfile.write("<HTML>POST OK. Camera Set to<BR><BR>");
            self.wfile.write(str(cameraQuality));
            
        except :
            pass

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
#class ThreadedHTTPServer(HTTPServer):
    """Handle requests in a separate thread."""



tex = Texture('tex')

def makepic(task):
    global tex,imgdata,count
    
    if not tex.hasRamImage():
        return task.cont
        
    pnmbimg=PNMImage()
    ss=StringStream()
    tex.store(pnmbimg)
    pnmbimg.write(ss,"test.jpeg")
    imgdata=ss.getData()
      
    count=count^1
    return task.again


mybuffer = base.win.makeTextureBuffer("My Buffer", 640, 480, tex, True)
mybuffer.setSort(-100)
mycamera = base.makeCamera(mybuffer)
myscene = NodePath("My Scene")
mycamera.reparentTo(myscene)

m = loader.loadModel('environment.egg')
m.reparentTo(myscene)
m.setPos(0, 0, 0)
sm = loader.loadModel('box.egg')
sm.reparentTo(myscene)
sm.setPos(0, 0, 0)
sm.setScale(2)
myInterval4 = sm.hprInterval(8.0, Vec3(360, 360, 0))
myInterval4.loop()


cm = CardMaker('card')
cm.setFrame(-1.0, 1.0, -1.0, 1.0)
card = aspect2d.attachNewNode(cm.generate())
card.setTexture(tex)


import thread

def handleserver():
    global MyHandler
    try:
        server = ThreadedHTTPServer(('localhost', 8080), MyHandler)
        print 'started httpserver...'
        #thread.start_new_thread(pandathread,())
        server.serve_forever()
    except KeyboardInterrupt:
        print '^C received, shutting down server'
        server.socket.close()

thread.start_new_thread(handleserver,())
    
    
# Define a procedure to move the camera.
def spinCameraTask(task):
    global mycamera,cv,server,cameraQuality,sm
    angleDegrees = task.time * 6.0
    angleRadians = angleDegrees * (pi / 180.0)
    mycamera.setPos(20 * sin(angleRadians), -20.0 * cos(angleRadians), 3)
    mycamera.setHpr(angleDegrees, 0, 0)
    sm.setZ(cameraQuality/10.0)
    
    return task.cont


taskMgr.add(spinCameraTask, "SpinCameraTask")
taskMgr.doMethodLater(0.1, makepic, 'TakeScene snapshot')
 

run() 

Before i forget,
if you have in the dir of the server the test.html file with the following content:

<HTML>
<BODY>
<!--<div id="hiddenDiv" style="display:none;visible:hidden;">-->
<div id="hiddenDiv" style="display:none;visible:hidden;">
 <iframe src="hidden.html" height="1" width="1" border="0" scrolling="no" name="hiddenFrame" id="hiddenFrame" ></iframe >
</div>


<img src="something.mjpeg" alt="Panda3dview" height="480" width="640">

<form target="hiddenFrame" method='POST' enctype='multipart/form-data'>
<!-- Set Quality: <input type=Value name=upfile> <input type=submit value=Set> -->
    <div align="center">
        Set Box Height:
        <select name="upfile" width="50">
            <option value="2"> 2 </option>
            <option value="5"> 5 </option>
             <option value="10"> 10 </option>
            <option value="20"> 20 </option>
             <option value="30"> 30 </option>
            <option value="50"> 50 </option>
            <option value="75"> 75 </option>
            <option value="80"> 80 </option>
            <option value="90"> 90 </option>
            <option value="96"> 96 </option>
             </select>
        <input type=submit value=Set>
     </div>
</form>


</BODY>
</HTML>

If you open in the browser localhost:8080/test.html you get an interface to adjust the height of the rotating cube from the client. This could be cool for your next remote visualisation and controll project.