Seite 1 von 1

HTTP Stream Convert Proxy

Verfasst: Sonntag 27. April 2008, 15:21
von joni
hallo,

ich möchte mal wieder ein kleines programm vorstellen, das sich als nützlich erwiesen hat. das problem war: ich hätte gerne mms:// streaming support in mpd um einige radiosender die ich sehr mag zu integrieren. das direkt in mpd zu implementieren wurde mir etwas zu komplex (libavcodec > me),

also habe ich einen kleinen http server geschrieben, der auf anfrage beginnt einen stream über gstreamer zu laden und zu convertieren und über den webserver auszugeben. quasi ein streaming-convert-proxy.

derzeit untertützt er nur mms quellen und mp3 ausgaben, aber das kann man mit ein wenig gstreamer kenntnis leicht erweitern wenn nötig.

benutzung eignetlich ganz einfach, einfach PORT und STATIONS nach wunsch anpassen und starten.

dannach kann man unter

http://servername:port/stationname.encodierung

jeweils den stream finden, mit den voreingestellten werte local z.b.

http://localhost:3333/fm4.mp3

wenn eine ungültige station gewählt wird (oder einfach gar nichts) wird eine m3u playlist mit allen möglichen streams zurück gegeben.

das ganze ist absolut nicht multiclient fähig.

Code: Alles auswählen

import socket
import thread
import threading

import gst
import gobject

PORT=3333
STATIONS={'fm4':'mms://stream1.orf.at/fm4_live',
          'bayern2':'mms://gffstream-w3a.wm.llnwd.net/gffstream_w3a'}
SOURCES={'mms://': 'mmssrc location={X}'}
ENCODERS={'mp3': { 'gst': 'lame', 'mime': 'audio/mpeg'}}


def close_pipe():
    global gst_pipe
    if gst_pipe:
        gst_pipe.set_state(gst.STATE_NULL)
        gst_pipe=None

        
def create_pipe(request, sock):
    global gst_pipe
    #request looks like
    #/stationame.encoder, currently only encoders with 3 letters are supported
    close_pipe()
    source=None
    encoder=None
    # find station souce
    if request[1:-4] in STATIONS:
        for src_prefix in SOURCES:
            if STATIONS[request[1:-4]][0:len(src_prefix)] == src_prefix:
                source = SOURCES[src_prefix].replace('{X}',
                                                    STATIONS[request[1:-4]])
                break
    # find encoder
    if request[-3:] in ENCODERS:
        encoder=ENCODERS[request[-3:]]['gst']
    if encoder and source:
        gst_pipe = gst.parse_launch(source + " ! decodebin ! " +
                                    encoder + " ! fdsink name=fd_sink")
        gst_pipe.get_by_name('fd_sink').set_property('fd',
                                                sock.makefile('wb').fileno())
        bus = gst_pipe.get_bus()
        bus.add_signal_watch()
        bus.connect('message', gstreamer_message)
        return ENCODERS[request[-3:]]['mime']


def handle_request(client_sock):
    global gst_pipe, transfer_event
    req = ""
    while True:
        data = client_sock.recv(1)
        if data == "":
            break
        req = req + data
        if req[-4:] == "\r\n\r\n":
            break
    req = req.split('\r\n')
    mime = create_pipe(req[0].split()[1], client_sock)
    if mime:
        client_sock.sendall("HTTP/1.1 200 OK\r\n");
        client_sock.sendall("Content-Type: "+ mime +"\r\n")
        client_sock.sendall("Cache-Control: no-cache, must-revalidate\r\n");
        client_sock.sendall("\r\n\r\n")
        #start playing till eos or client close
        gst_pipe.set_state(gst.STATE_PLAYING)
        transfer_event.clear()
        transfer_event.wait()
        close_pipe()    
    else:
        #create playlist
        # Try to find host
        host = None
        for line in req:
            if line[0:4].lower() == 'host':
                host = line[6:]
        if not host:
            host = 'localhost:' + PORT
        playlist = ''
        for station in STATIONS:
            for encoder in ENCODERS:
                playlist += 'http://' + host + '/' + station + '.' + encoder + \
                            '\r\n'
        client_sock.sendall("HTTP/1.1 200 OK\r\n");
        client_sock.sendall("Content-Type: audio/x-mpegurl\r\n")
        client_sock.sendall("Content-Length: " + str(len(playlist)) + "\r\n")
        client_sock.sendall("Content-Disposition: attachment; filename=\"play"
                            +"list.m3u\"\r\n")
        client_sock.sendall("Cache-Control: no-cache, must-revalidate\r\n");
        client_sock.sendall("\r\n\r\n")                
        client_sock.sendall(playlist)
        

def gstreamer_message(bus, message):
    global transfer_event
    if message.type == gst.MESSAGE_ERROR or message.type == gst.MESSAGE_EOS:
        transfer_event.set()
    
    
def run_webserver():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   
    sock.bind(('', PORT)) 
    sock.listen(1)
    try:
        while 1:
            ( clientsock, client ) = sock.accept()
            handle_request ( clientsock )
            clientsock.close()
    finally:
        sock.close()


if __name__=='__main__':
    gobject.threads_init()
    gst_pipe=None
    transfer_event=threading.Event()
    ws = thread.start_new_thread(run_webserver, ())
    try:
        gobject.MainLoop().run()
    except KeyboardInterrupt:
        close_pipe()
        print "bye"

Verfasst: Sonntag 27. April 2008, 20:40
von Leonidas
Von der Idee her nett, aber: globals -> übel. Nicht PEP8 -> auch übel.

Ich höre ja lieber den FM4 Stream als Ogg Vorbis über den Icecast-Server, das funktioniert mit jedem Player (bis auf Rhythmbox vielleicht, aber dessen Streaming-Support ist einfach nur broken).

Verfasst: Sonntag 27. April 2008, 20:48
von Trundle
`BaseHTTPServer` bzw. `SimpleHTTPServer` wären auch einen Blick wert.

Verfasst: Montag 28. April 2008, 11:42
von Y0Gi
Nett.

Zum Code: Statt der vielen Aufrufe von `client_sock.sendall()` würde ich wohl die zu sendenden Zeilen, ggf. aus einer lokalen Funktion, `yield`en und `client_sock.sendall()` über `(i)map()` ansprechen.