4
\$\begingroup\$

I am new to Python and not very familiar with advanced Python data structures.

I have written a function to receive data from a socket in Python and perform string manipulations on it. The basic purpose of the code is to get the metadata from an Icecast radio stream based on suggestions I found elsewhere.

The function seems to work but it would be great it someone can help me optimize the string operation.

def radioPoller():
    msg1="GET / HTTP/1.1"+'\r'+'\n'+"Host: sc.buddharadio.com"+'\r'+'\n'+"User-Agent: VLC/2.0.5 LibVLC/2.0.5"+'\r'+'\n'+"Range: bytes=0-"+'\r'+'\n'+"Connection: close"+'\r'+'\n'+"Icy-MetaData: 1"+'\r'+'\n'+'\r'+'\n'


    if os.path.exists("/media/hdd1/data.txt"):
        os.unlink("/media/hdd1/data.txt")

    HOST = 'sc.buddharadio.com'                      # The remote host
    PORT = 80                                # The same port as used by the server
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))


    f=open("/media/hdd1/data.txt","w")

    s.send(msg1)                                 #sending http request
    data=""
    while len(data)<1024:
        data=s.recv(1024)               #recieving a few characters

    t=data.find("metaint")                       #finding the metaint response header which contains interval after which metadata will be visible in data stream eg:-icy-metaint:32768 
    data=data[t+8:]                      #jumping 8 characters to the end of the string

    splitter="\r\n"                         # to split the contents and find when header ends and                              
    data1=data.split(splitter)                      #data1 stores the splitted data. First list has length and last has data 

    metaInt=int(data1[0])                      #find the metadata interval byte length
    #print metaInt  

    string12=data1[len(data1)-1]                #contains only the data part

    lengthTotal= len(string12)                  #length of data we have

    print "total data is", lengthTotal,"\n"

    print "TOtal bytes we should get is", (metaInt+4080),"\n"

    while lengthTotal<(metaInt+4080):
        data = s.recv(8192)
        lengthTotal=lengthTotal+len(data)
        print "Total length now is", lengthTotal 
        string12=string12+data
        #The first character after MetaInterval number of characters define the length of actual metadata/16
    metaLen=ord(string12[metaInt])
    print "This is multiplier", metaLen, "No of characters to read ", (metaLen*16)

    metaString=string12[metaInt+1:metaInt+1+(metaLen*16)]+'\n'
    print "Extracted String is \n", metaString

    f.write(str(metaString))
    s.close
    f.close

The code sends a request header to the radio stream and then receives a response like this:

ICY 200 OK icy-notice1:
This stream requires Winamp
icy-notice2:SHOUTcast Distributed Network Audio Server/Linux v1.9.8
icy-name:Buddha
icy-genre:
icy-url:http://www.buddharadio.com
content-type:audio/aac
icy-pub:0
icy-metaint:32768
icy-br:32

The icy-metaint defines the data interval after the metadata appears in the data stream.

I use a while loop to read in the Meta Data Interval+Max Meta Data Length of data and then slice the metadata and save it in a file.

\$\endgroup\$
1
  • \$\begingroup\$ Repeated string concatenations are a bad idea - instead build a list and then use "".join(). \$\endgroup\$
    – Latty
    Mar 2, 2013 at 19:53

1 Answer 1

5
\$\begingroup\$
def radioPoller():

Python convention says that function names should be lowercase_with_undescores. Also, they should be verb, so: poll_radio would be better

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))

I recommend not using s as its not obvious what that means.

    finalData=[]

Python convention, local names should be lowercase_with_underscores.

    f=open("/media/hdd1/data.txt","a")

Again, don't use single letter variable names.

    s.send(msg1)                                 #sending http request

Instead of doing HTTP requests yourself, it'd make more sense to use urllib2 to do it for you. That way it'd take care of parsing etc. Where is msg1 coming from? Also, you don't know that this sent all the data. To be sure all data was sent, use s.sendall

    data=""
    while len(data)<1024:
        data=s.recv(1024)               #recieving a few characters

Shouldn't you be using +=?

    t=data.find("metaint")                       #finding the metaint response header
    data=data[t+8:]                      #jumping 8 characters to the end of the string

    splitter="\r\n"                         # to split the contents and find when header ends and                              
    data1=data.split(splitter)                      #data1 stores the splitted data. First list has length and last has data 

Why assign splitter to a local variable? just pass the constant.

    metaInt=int(data1[0])                      #find the metadata interval byte length
    #print metaInt  

Rather then mucking with this use, use python's tool to parse the headers for you.

    finalData.append(data1[len(data1)-1])           #contains only the data part

You can use data1[-1] for the same result.

    lengthTotal= len(finalData[0])                  #length of data we have

    #print "total data is", lengthTotal,"\n"

    #print "TOtal bytes we should get is", (metaInt+4080),"\n"

Don't leave dead code in comments

    while lengthTotal<(metaInt+4080):
        data = s.recv(8192)

        lengthTotal=lengthTotal+len(data)
        #print "Total length now is", lengthTotal 
        finalData.append(data)

In this case I suggest using StringIO rather than a list.

    print finalData
    string12=''.join(finalData)

Don't use meaningless names

    metaLen=ord(string12[metaInt])
    #print "This is multiplier", metaLen, "No of characters to read ", (metaLen*16)

    metaString=string12[metaInt+1:metaInt+1+(metaLen*16)]
    metaString=metaString+"\r\n"

    #print "Extracted String is \n", metaString

    f.write(str(metaString))

It's already a string, don't pass it to str

    s.close
    f.close

These last two don't do anything.

Here's my reworking of your code:

def parse_headers(response):
    headers = {}
    while True:
        line = response.readline()
        if line == '\r\n':
            break # end of headers
        if ':' in line:
            key, value = line.split(':', 1)
            headers[key] = value
    return headers

def poll_radio():
    request = urllib2.Request("http://sc.buddharadio.com:80/", headers = {
        'User-Agent' : 'User-Agent: VLC/2.0.5 LibVLC/2.0.5',
        'Icy-MetaData' : '1',
        'Range' : 'bytes=0-',
    })
    # the connection will be close on exit from with block
    with contextlib.closing(urllib2.urlopen(request)) as response:

        headers = parse_headers(response)

        meta_interval = int(headers['icy-metaint'])
        response.read(meta_interval) # throw away the data until the meta interval

        length = ord(response.read(1)) * 16 # length is encoded in the stream
        metadata = response.read(length)
        print metadata
\$\endgroup\$
2
  • \$\begingroup\$ This is amazingly neat.My code was no way near good compared to this.However could you explain me the usage of contextlib.closing in the code and difference it makes without it.Thanks \$\endgroup\$
    – SteveIrwin
    Mar 3, 2013 at 0:03
  • 1
    \$\begingroup\$ @SteveIrwin, a with block automatically closes its object when exiting the with. (Yes, that way over-simplifid, as it can do other things depending on the object). However, the object returned by urlopen doesn't support the with statement. The contextlib.closing takes care of that, causing it to call the .close() method upon exiting the block. \$\endgroup\$ Mar 3, 2013 at 0:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.