* libvlc binding for python
authorCyril Deguet <asmax@videolan.org>
Sun, 16 Feb 2003 23:57:48 +0000 (23:57 +0000)
committerCyril Deguet <asmax@videolan.org>
Sun, 16 Feb 2003 23:57:48 +0000 (23:57 +0000)
* new VideoLAN RTSP Server, using libvlc.
  Not yet fully rfc2326-compliant, but it works ;)

python/Makefile [new file with mode: 0644]
python/setup.py [new file with mode: 0644]
python/vlcmodule.c [new file with mode: 0644]
python/vlrs/cfg.py [new file with mode: 0755]
python/vlrs/playlist.py [new file with mode: 0755]
python/vlrs/rtsp.py [new file with mode: 0755]
python/vlrs/session.py [new file with mode: 0755]
python/vlrs/streamer.py [new file with mode: 0755]
python/vlrs/vlrs.py [new file with mode: 0755]

diff --git a/python/Makefile b/python/Makefile
new file mode 100644 (file)
index 0000000..85c5b3e
--- /dev/null
@@ -0,0 +1,8 @@
+all:
+       python setup.py build
+
+install:
+       python setup.py install
+
+clean:
+       rm -Rf build
diff --git a/python/setup.py b/python/setup.py
new file mode 100644 (file)
index 0000000..58eebcc
--- /dev/null
@@ -0,0 +1,17 @@
+from distutils.core import setup, Extension
+
+FFMPEG_DIR = '/home/cyril/ffmpeg'
+
+vlc = Extension('vlc',
+                sources = ['vlcmodule.c'],
+                libraries = ['vlc', 'rt', 'dl' , 'pthread', 'ffmpeg', 'm', 
+                             'avcodec'],
+                library_dirs = ['../lib', '../modules/codec/ffmpeg', 
+                                FFMPEG_DIR + '/libavcodec'])
+
+
+setup (name = 'PackageName',
+       version = '1.0',
+       description = 'This is a demo package',
+       ext_modules = [vlc])
+
diff --git a/python/vlcmodule.c b/python/vlcmodule.c
new file mode 100644 (file)
index 0000000..8749411
--- /dev/null
@@ -0,0 +1,83 @@
+#include <Python.h>
+#include <vlc/vlc.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+
+static PyObject *vlc_create(PyObject *self, PyObject *args)
+{
+    int iRc;
+
+    iRc = VLC_Create();
+    return Py_BuildValue("i", iRc);
+}
+
+
+static PyObject *vlc_init(PyObject *self, PyObject *args)
+{
+    int iVlc;
+    char *file;
+    char *pArgv[] = { "vlc", "--sout", NULL };
+    int iRc;
+
+    if (!PyArg_ParseTuple(args, "iss", &iVlc, &file, &pArgv[2]))
+        return NULL;
+    iRc = VLC_Init(iVlc, 3, pArgv);
+    if (iRc >= 0)
+        iRc = VLC_AddTarget(iVlc, file, PLAYLIST_APPEND, PLAYLIST_END);
+    return Py_BuildValue("i", iRc);
+}
+
+
+static PyObject *vlc_play(PyObject *self, PyObject *args)
+{
+    int iVlc;
+    int iRc;
+
+    if (!PyArg_ParseTuple(args, "i", &iVlc))
+        return NULL;
+    iRc = VLC_Play(iVlc);
+    return Py_BuildValue("i", iRc);
+}
+
+
+static PyObject *vlc_stop(PyObject *self, PyObject *args)
+{
+    int iVlc;
+    int iRc;
+
+    if (!PyArg_ParseTuple(args, "i", &iVlc))
+        return NULL;
+    iRc = VLC_Stop(iVlc);
+    return Py_BuildValue("i", iRc);
+}
+
+
+static PyObject *vlc_pause(PyObject *self, PyObject *args)
+{
+    int iVlc;
+    int iRc;
+
+    if (!PyArg_ParseTuple(args, "i", &iVlc))
+        return NULL;
+    iRc = VLC_Pause(iVlc);
+    return Py_BuildValue("i", iRc);
+}
+
+
+static PyMethodDef VlcMethods[] = {
+    {"create", vlc_create, METH_VARARGS, "Create a vlc thread."},
+    {"init", vlc_init, METH_VARARGS, "Initialize a vlc thread."},
+    {"play", vlc_play, METH_VARARGS, "Play"},
+    {"stop", vlc_stop, METH_VARARGS, "Stop"},
+    {"pause", vlc_pause, METH_VARARGS, "Pause"},
+    {NULL, NULL, 0, NULL}        /* Sentinel */
+};
+
+
+void initvlc(void)
+{
+    Py_InitModule("vlc", VlcMethods);
+}
+
diff --git a/python/vlrs/cfg.py b/python/vlrs/cfg.py
new file mode 100755 (executable)
index 0000000..5d31a18
--- /dev/null
@@ -0,0 +1,3 @@
+# Nice kludge to share global variables ;-)
+
+pass
diff --git a/python/vlrs/playlist.py b/python/vlrs/playlist.py
new file mode 100755 (executable)
index 0000000..2778693
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/bin/python -O
+#
+# VideoLAN RTSP Server
+#
+# Author: Cyril Deguet <asmax@via.ecp.fr>
+
+
+import cfg, string, threading
+
+
+class PlayList:
+    "Contains the media playlist"
+
+    def __init__(self):
+        self.lock = threading.Lock()
+
+    def readConfig(self, filename):
+        "Read the playlist file"
+        f = open(filename)
+        newList = {}
+        while 1:
+            line = string.strip(f.readline())
+            if line == "":
+                break
+            items = string.split(line, '\t')
+            newList[items[0]] = {'file':items[1], 'name':items[2], 'addr':items[3]}
+        self.lock.acquire()
+        self.list = newList
+        self.lock.release()
+            
+    def getMedia(self, uri):
+        "Return the description of an item in the playlist"
+        self.lock.acquire()
+        if self.list.has_key(uri):
+            media = self.list[uri]
+        else:
+            media = None
+        self.lock.release()
+        return media
+
diff --git a/python/vlrs/rtsp.py b/python/vlrs/rtsp.py
new file mode 100755 (executable)
index 0000000..b44ac37
--- /dev/null
@@ -0,0 +1,271 @@
+#!/usr/bin/python
+#
+# VideoLAN RTSP Server
+#
+# Author: Cyril Deguet <asmax@via.ecp.fr>
+#
+# See: RFC 2326 Real Time Streaming Protocol
+#      RFC 2327 Session Description Protocol
+
+
+import cfg, mimetools, re, socket, time, SocketServer, string, sys
+
+
+def ntpTime():
+    "return the current time in NTP decimal format"
+    return "%d" % (int(time.time()) + 2208988800L)
+
+
+
+class SdpMessage:
+    "Build a SDP message"
+    uri = "http://www.videolan.org"
+
+    def __init__(self, sessionName, address, uri):
+        "Build the message"
+        self.sessionName = sessionName
+        self.address = address
+        self.uri = uri
+        
+    def getMessage(self):
+        "Return the SDP message"
+        msg = "v=0\r\n" + \
+              "o=asmax " + ntpTime() + " " + ntpTime() + \
+                  " IN IP4 sphinx.via.ecp.fr\r\n" + \
+              "s=" + self.sessionName + "\r\n" + \
+              "u=" + self.uri + "\r\n" + \
+              "t=0 0\r\n" + \
+              "c=IN IP4 " + self.address + "/1\r\n" + \
+              "m=video 1234 RTP/MP2T 33\r\n" + \
+              "a=control:" + self.uri + "\r\n"
+        return msg
+
+
+
+class RtspServerHandler(SocketServer.StreamRequestHandler):
+    "Request handler of the server socket"
+    
+    version = "RTSP/1.0"
+    ok = "200 OK"
+    badRequest = "400 Bad Request"
+    uriNotFound = "404 Not found"
+    sessionNotFound = "454 Session Not Found"
+    invalidHeader = "456 Header Field Not Valid for Resource"
+    internalError = "500 Internal Server Error"
+    notImplemented = "501 Not Implemented"
+    
+    def error(self, message, cseq):
+        self.wfile.write(self.version + " " + message + "\r\n" + \
+                         "Cseq: " + cseq + "\r\n" + \
+                         "\r\n")
+
+    def parseHeader(self, header):
+        "Split a RTCP header into a mapping of parameters"
+        list = map(string.strip, re.split('[; \n]*', header, re.S))
+        result = {}
+        for item in list:
+            m = re.match('([^=]*)(?:=(.*))?', item)
+            if m is None:
+                return None
+            result[m.group(1)] = m.group(2)
+        return result
+
+    def optionsMethod(self):
+        "Handle an OPTION request"
+        response = "Public: OPTIONS, DESCRIBE, SETUP, PLAY, PAUSE, PING, TEARDOWN\r\n" + \
+                   "\r\n"
+        return response
+
+    def pingMethod(self, msg):
+        "Handle an PING request"
+        cseq = msg.getheader('cseq')
+        id = msg.getheader('Session')
+        if id is None:
+            self.error(self.badRequest, cseq)
+            return
+        response = "Session: " + id + "\r\n" + \
+                   "\r\n"
+        return response
+
+    def describeMethod(self, msg, uri):
+        "Handle a DESCRIBE request"
+        cseq = msg.getheader('cseq')
+        
+        # Find the URI in the playlist
+        media = cfg.playlist.getMedia(uri)
+        if media is None:
+            self.error(self.uriNotFound, cseq)
+            return None
+
+        message = SdpMessage(media['name'], media['addr'], uri)
+        description = message.getMessage()
+        size = `len(description)`
+        response = "Content-Type: application/sdp\r\n" + \
+                   "Content-Length: " + size + "\r\n" + \
+                   "\r\n" + description
+        return response
+                         
+    def setupMethod(self, msg, uri):
+        "Handle a SETUP request" 
+        cseq = msg.getheader('cseq')
+        
+        # Find the URI in the playlist
+        media = cfg.playlist.getMedia(uri)
+        if media is None:
+            self.error(self.uriNotFound, cseq)
+            return None
+
+        transportHeader = msg.getheader('transport')
+        if transportHeader is None:
+            self.error(self.badRequest, cseq)
+            return None
+        transport = self.parseHeader(transportHeader)
+
+        # Check the multicast/unicast fields in the headers
+        if transport.has_key('multicast'):
+            type = "multicast"
+        elif transport.has_key('unicast'):
+            type = "unicast"
+        else:
+            self.error(self.invalidHeader, cseq)
+            return None
+            
+        # Check the destination field in the headers
+        dest= None
+        if transport.has_key('destination'):
+            dest = transport['destination']
+        if dest is None:
+            dest = media['addr']       # default destination address
+            
+        id = cfg.sessionList.newSession(uri, dest)
+        if id is None:
+            self.error(self.internalError, cseq)
+            return None
+        response = "Session: " + id + "\r\n" + \
+                   "Transport: RTP/MP2T/UDP;" + type + ";destination=" + dest + "\r\n" + \
+                   "\r\n"
+        return response
+
+    def playMethod(self, msg, uri):
+        "Handle a PLAY request"
+        cseq = msg.getheader('cseq')
+        
+        # Find the URI in the playlist
+        media = cfg.playlist.getMedia(uri)
+        if media is None:
+            self.error(self.uriNotFound, cseq)
+            return None
+
+        id = msg.getheader('Session')
+        session = cfg.sessionList.getSession(id)
+        if session is None:
+            self.error(self.sessionNotFound, cseq)
+            return None
+        if session.play() < 0:
+            self.error(self.internalError, cseq)
+            return None
+        response = "Session: " + id + "\r\n" + \
+                   "\r\n"
+        return response
+
+    def pauseMethod(self, msg, uri):
+        "Handle a PAUSE request"
+        cseq = msg.getheader('cseq')
+        
+        # Find the URI in the playlist
+        media = cfg.playlist.getMedia(uri)
+        if media is None:
+            self.error(self.uriNotFound, cseq)
+            return None
+            
+        id = msg.getheader('Session')
+        session = cfg.sessionList.getSession(id)
+        if session is None:
+            self.error(self.sessionNotFound, cseq)
+            return None
+        if session.pause() < 0:
+            self.error(self.internalError, cseq)
+            return None
+        response = "Session: " + id + "\r\n" + \
+                   "\r\n"
+        return response
+
+    def teardownMethod(self, msg, uri):
+        "Handle a TEARDOWN request"
+        cseq = msg.getheader('cseq')
+        
+        # Find the URI in the playlist
+        media = cfg.playlist.getMedia(uri)
+        if media is None:
+            self.error(self.uriNotFound, cseq)
+            return None
+            
+        id = msg.getheader('Session')
+        session = cfg.sessionList.getSession(id)
+        if session is None:
+            self.error(self.sessionNotFound, cseq)
+            return None
+        if session.stop() < 0:
+            self.error(self.internalError, cseq)
+            return None
+        if cfg.sessionList.delSession(id) < 0:
+            self.error(self.internalError, cseq)
+            return None
+        response = "\r\n"
+        return response
+
+    def parseRequest(self):
+        "Parse a RSTP request"
+        requestLine = self.rfile.readline()
+        m = re.match("(?P<method>[A-Z]+) (?P<uri>(\*|(?:(?P<protocol>rtsp|rtspu)://" + \
+                     "(?P<host>[^:/]*)(:(?P<port>\d*))?(?P<path>.*)))) " + \
+                     "RTSP/(?P<major>\d)\.(?P<minor>\d)", requestLine)
+        if m is None:
+            self.error(self.badRequest, "0")
+            return
+        uri = m.group('uri')
+        
+        # Get the message headers
+        msg = mimetools.Message(self.rfile, "0")
+        cseq = msg.getheader('CSeq')
+        if cseq is None:
+            self.error(self.badRequest, "0")
+            return
+            
+        method = m.group('method')
+        if method == 'OPTIONS':
+            response = self.optionsMethod()
+        elif method == 'DESCRIBE':
+            response = self.describeMethod(msg, uri)
+        elif method == 'SETUP':
+            response = self.setupMethod(msg, uri)
+        elif method == 'PLAY':
+            response = self.playMethod(msg, uri)
+        elif method == 'PAUSE':
+            response = self.pauseMethod(msg, uri)
+        elif method == 'PING':
+            response = self.pingMethod(msg)
+        elif method == 'TEARDOWN':
+            response = self.teardownMethod(msg, uri)
+        else:
+            self.error(self.notImplemented, cseq)
+            return
+        # Send the response
+        if response is None:
+            return
+        else:
+            self.wfile.write(self.version + " " + self.ok + "\r\n" + \
+                             "CSeq: " + cseq + "\r\n" + \
+                             response)
+            
+    def handle(self):
+        "Handle an incoming request"
+        while 1:
+            try:
+                self.parseRequest()
+            except IOError:
+                return
+                         
+
diff --git a/python/vlrs/session.py b/python/vlrs/session.py
new file mode 100755 (executable)
index 0000000..eadd10f
--- /dev/null
@@ -0,0 +1,108 @@
+#!/usr/bin/python -O
+#
+# VideoLAN RTSP Server
+#
+# Author: Cyril Deguet <asmax@via.ecp.fr>
+
+
+import cfg, random, time
+
+from streamer import VlcError, VlcStreamer
+
+
+class Session:
+    "RTSP Session"
+    
+    def __init__(self, id, uri, dest):
+        self.id = id
+        self.uri = uri
+        self.dest = dest
+        self.state = 'ready'
+        media = cfg.playlist.getMedia(self.uri)
+        self.fileName = media['file']
+        address = "rtp/ts://" + dest
+        self.streamer = VlcStreamer(self.fileName, address)
+        
+    def play(self):
+        "Play this session"
+        if self.state == 'playing':
+            print "Session " + self.id + " (" + self.fileName + "): already playing"
+            return 0
+        self.state = 'playing'
+        print "Session " + self.id + " (" + self.fileName + "): play"
+        try:
+            self.streamer.play()
+        except VlcError:
+            print "Streamer: play failed"
+            return -1
+        return 0
+
+    def pause(self):
+        "Pause this session"
+        print "Session " + self.id + " (" + self.fileName + "): pause"
+        self.state = 'ready'
+        try:
+            self.streamer.pause()
+        except VlcError:
+            print "Streamer: pause failed"
+            return -1
+        return 0
+
+    def stop(self):
+        "Stop this session"
+        print "Session " + self.id + " (" + self.fileName + "): stop"
+        try:
+            self.streamer.stop()
+        except VlcError:
+            print "Streamer: stop failed"
+            return -1
+        return 0
+
+
+
+class SessionList:
+    "Manages RTSP sessions"
+
+    list = {}
+    chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+    def __init__(self):
+        self.rand = random.Random(time.time())
+    
+    def newSessionId(self):
+        "Build a random session id"
+        id = ""
+        for x in range(12):
+            id += self.chars[self.rand.randrange(0, len(self.chars), 1)]
+        return id
+
+    def newSession(self, uri, dest):
+        "Create a new RTSP session"
+        id = self.newSessionId()
+        while self.list.has_key(id):
+            id = self.newSessionId()
+        try:
+            session = Session(id, uri, dest)
+        except VlcError:
+            print "Streamer: creation failed"
+            return None
+        self.list[id] = session
+        print "New session: " + id
+        return id
+        
+    def getSession(self, id):
+        "Get a session from its session id"
+        if self.list.has_key(id):
+            return self.list[id]
+        else:
+            return None
+
+    def delSession(self, id):
+        "Delete a session"
+        if self.list.has_key(id):
+            del self.list[id]
+            return 0
+        else:
+            return -1
+
+
diff --git a/python/vlrs/streamer.py b/python/vlrs/streamer.py
new file mode 100755 (executable)
index 0000000..23551a1
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/python -O
+#
+# VideoLAN RTSP Server
+#
+# Author: Cyril Deguet <asmax@via.ecp.fr>
+
+import cfg, vlc
+
+
+class VlcError(Exception):
+    "Exception class for libvlc calls"
+    pass
+
+
+
+class VlcStreamer:
+    "Manage a streamer with libvlc"
+    
+    def __init__(self, file, address):
+        "Create the streamer"
+        self.file = file
+        self.address = address
+        self.id = vlc.create()
+        if self.id < 0:        
+            raise VlcError
+        if vlc.init(self.id, self.file, self.address) < 0:
+            raise VlcError
+            
+    def play(self):
+        "Play the stream"
+        if vlc.play(self.id) < 0:
+            raise VlcError
+            
+    def stop(self):
+        "Stop the stream"
+        if vlc.stop(self.id) < 0:
+            raise VlcError
+            
+    def pause(self):
+        "Pause the stream"
+        if vlc.pause(self.id) < 0:
+            raise VlcError
+
+
diff --git a/python/vlrs/vlrs.py b/python/vlrs/vlrs.py
new file mode 100755 (executable)
index 0000000..7c8ab7c
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/python -O
+#
+# VideoLAN RTSP Server
+#
+# Author: Cyril Deguet <asmax@via.ecp.fr>
+
+
+import cfg, SocketServer, string, sys
+
+from playlist import PlayList
+from rtsp import RtspServerHandler
+from session import SessionList
+
+
+PORT = 1554
+
+if len(sys.argv) == 1:
+    print "usage: vlrs <playlist>\n"
+    sys.exit()
+
+cfg.playlist = PlayList()
+cfg.playlist.readConfig(sys.argv[1])
+cfg.sessionList = SessionList()
+
+rtspServer = SocketServer.TCPServer(('', PORT), RtspServerHandler)
+try:
+    rtspServer.serve_forever()
+except KeyboardInterrupt:
+    rtspServer.server_close()
+