HTTP Stream Convert Proxy

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
joni
User
Beiträge: 2
Registriert: Donnerstag 24. Januar 2008, 16:45

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"
Zuletzt geändert von joni am Sonntag 27. April 2008, 21:47, insgesamt 1-mal geändert.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

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).
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

`BaseHTTPServer` bzw. `SimpleHTTPServer` wären auch einen Blick wert.
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

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.
Antworten