Logo Search packages:      
Sourcecode: lastfmproxy version File versions  Download package

main.py

#!/usr/bin/python

import sys
import time
import socket
import md5
import threading
import cgi
import os
import string

import config
import lastfm

True = 1
False = 0

class proxy:

    def __init__(self, username, password):
        self.username = username
        self.password = password
        self.lastfm = None
        self.checktime = 0
        self.bookmarks = []
        self.lasttracks = []
        self.basedir = "."
        self.quit = False
        self.version = "1.1"

        self.extraheaders = string.join([
                "Server: LastFMProxy/" + self.version + "\r\n",
                "Pragma: no-cache\r\n",
                "Cache-Control: max-cache=0\r\n"
                ], "");

    def checkmetadata(self):
        # See if it's time to update the metadata ("now playing")
        if time.time() < self.checktime:
            return
        self.checktime = time.time() + 60 # try to avoid races

        if not self.lastfm.getmetadata():
            self.checktime = time.time() + 2
        else:
            tmp = self.lastfm.metadata
            if tmp["streaming"] == "true":
                dur = int(tmp["trackduration"])
                remaining = dur - (time.time() - self.lastfm.progress)
                if remaining > 60:
                    remaining = 60
                elif remaining < 2:
                    remaining = 2
                self.checktime = time.time() + remaining
            else:
                self.checktime = time.time() + 10

    def metadataloop(self):
        while not self.quit:
            self.checkmetadata()
            time.sleep(1)

    def storebookmark(self, station):
        newbookmarks = []
        newbookmarks.append(station)
        for s in self.bookmarks:
            if s != station:
                newbookmarks.append(s)

        self.bookmarks = newbookmarks
        if len(self.bookmarks) > 10:
            self.bookmarks = self.bookmarks[:10]

        f = open(os.path.join(self.basedir, "bookmarks.txt"), "w")
        for s in self.bookmarks:
            f.write(s + "\n")
        f.close()

    def storetrack(self, track):
        if len(self.lasttracks) > 0 and track == self.lasttracks[0]:
            return
        self.lasttracks = ([ track ] + self.lasttracks)[:6]

    def gotconnection(self, clientsock):
        metaint = 16000
        sendmetadata = False

        req = ""
        while True:
            data = clientsock.recv(1)
            if data == "":
                break
            req = req + data
            if req[-4:] == "\r\n\r\n":
                break
        req = string.split(req, "\n")
        http = {}
        for line in req:
            tmp = string.split(line, ":", 1)
            #print tmp
            if len(tmp) == 2:
                http[tmp[0]] = string.strip(tmp[1])
                if string.lower(tmp[0]) == "icy-metadata":
                    sendmetadata = int(tmp[1])

        # Make sure http "Host" variable is fully qualified
        if not http.has_key("Host"):
            http["Host"] = "localhost:" + str(config.listenport)
        elif string.find(http["Host"], ":") < 0:
            http["Host"] = http["Host"] + ":" + str(config.listenport)

        # Check request method
        req[0] = string.split(req[0], " ")
        if len(req[0]) != 3 or req[0][0] != "GET":
            print "Weird method:", req[0]
            clientsock.close()
            return

        # Check if metadata is up-to-date
        #self.checkmetadata()

        tmp = string.split(req[0][1], "?", 1)
        station = tmp[0]
        if len(tmp) > 1:
            args = cgi.parse_qs(tmp[1])
        else:
            args = None

        if station == "/frames":
            cont = "<frameset rows=\"220, *\"><FRAME src=\"/\"><frame src=\"http://www.last.fm/\"></frameset>\n"
            try:
                clientsock.send("HTTP/1.0 200 OK\r\n")
                clientsock.send(self.extraheaders)
                clientsock.send("Content-Type: text/html\r\n")
                clientsock.send("Content-Length: " + str(len(cont)) + "\r\n")
                clientsock.send("\r\n")
                clientsock.send(cont)
                clientsock.close()
            except socket.error:
                clientsock.close()
            return

        if station == "/quit":
            self.quit = True
            cont = "Bye!\n"
            try:
                clientsock.send("HTTP/1.0 200 OK\r\n")
                clientsock.send(self.extraheaders)
                clientsock.send("Content-Type: text/html\r\n")
                clientsock.send("Content-Length: " + str(len(cont)) + "\r\n")
                clientsock.send("\r\n")
                clientsock.send(cont)
                clientsock.close()
            except socket.error:
                clientsock.close()
            return

        if station[:9] == "/settheme":
            theme = station[10:]
            try:
                tmp = os.stat(os.path.join(self.basedir, "data", theme + ".css"))
                tmp = os.stat(os.path.join(self.basedir, "data", theme + ".html"))
                config.theme = theme
                url = "http://" + http["Host"] + "/"
            except OSError:
                url = "http://" + http["Host"] + "/?msg=Theme%20" + theme + "%20not%20found!"
                
            cont = "Moved to <a href=\"" + url + "\">here</a>."

            try:
                clientsock.send("HTTP/1.0 307 Temporary Redirect\r\n")
                clientsock.send(self.extraheaders)
                clientsock.send("Location: " + url + "\r\n")
                clientsock.send("Content-Type: text/html\r\n")
                clientsock.send("Content-Length: " + str(len(cont)) + "\r\n")
                clientsock.send("\r\n")
                clientsock.send(cont)
                clientsock.close()
            except socket.error:
                clientsock.close()
            return

        if station == "/skip" or station == "/love" or station == "/ban" or station[:8] == "/lastfm:" or station == "/rtp" or station == "/nortp" or station == "/discovery/on" or station == "/discovery/off":

            redirect = 0

            if station[:8] == "/lastfm:":
                res = self.lastfm.changestation(station[1:])
                if res.has_key("response") and res["response"] == "OK":
                    self.storebookmark(station[1:])
                self.checktime = time.time() + 2
                redirect = 1
            if station[:22] == "/changestation/lastfm:":
                res = self.lastfm.changestation(station[15:])
                if res.has_key("response") and res["response"] == "OK":
                    self.storebookmark(station[15:])
                self.checktime = time.time() + 2
            elif station[:10] == "/discovery":
                res = self.lastfm.changestation("lastfm://settings" + station)
                self.checktime = time.time() + 2
            else:
                res = self.lastfm.command(station[1:])
                self.checktime = time.time() + 2

            if res.has_key("response"):
                res = res["response"]
            else:
                print "hmm?", repr(res)
                res = "INTERNALERROR"

            cont = "result = '" + res + "';\n"

            try:
                if redirect:
                    clientsock.send("HTTP/1.0 307 Temporary Redirect\r\n")
                    clientsock.send("Location: http://" + http["Host"] + "/\r\n")
                else:
                    clientsock.send("HTTP/1.0 200 OK\r\n")
                clientsock.send(self.extraheaders)
                clientsock.send("Content-Type: text/plain\r\n")
                clientsock.send("Content-Length: " + str(len(cont)) + "\r\n")
                clientsock.send("\r\n")
                clientsock.send(cont)
                clientsock.close()
            except socket.error:
                clientsock.close()
            return

        if station == "/popup":
            cont = "<script>\nfunction popup(url,w,h) {\nwindow.open(url, \"LastFMProxy\", 'width='+w+',height='+h+'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes');\n}\n</script>\n"
            cont = cont + "<div><a href=\"javascript:popup('/',700,300);\">Click here to open popup-window</a></div>\n"
            try:
                clientsock.send("HTTP/1.0 200 OK\r\n")
                clientsock.send(self.extraheaders)
                clientsock.send("Content-Type: text/html\r\n")
                clientsock.send("Content-Length: " + str(len(cont)) + "\r\n")
                clientsock.send("\r\n")
                clientsock.send(cont)
                clientsock.close()
            except socket.error:
                clientsock.close()
            return

        if station == "/np":
            m = self.lastfm.metadata

            if m and m.has_key("track_url") and m.has_key("artist") and m.has_key("track"):
                self.storetrack("<a href=\"" + m["track_url"] + "\">" + m["artist"] + " - " + m["track"] + "</a>")

            cont = "np_metadata_age = " + str(int(time.time() - self.lastfm.metadatatime)) + ";\n"
            if m:
                for tmp in m.keys():
                    cont = cont + "np_" + tmp + " = '" + string.replace(m[tmp], "'", "\\'") + "';\n"

            cont = cont + "np_trackprogress = " + str(int(time.time() - self.lastfm.progress)) + ";\n"

            cont = cont + "np_lasttracks = " + str(len(self.lasttracks)) + ";\n"
            cont = cont + "np_lasttrack = new Array(" + str(len(self.lasttracks)) + ");\n"
            for i in range(0,len(self.lasttracks)):
                cont = cont + "np_lasttrack[" + str(i) + "] = '" + string.replace(self.lasttracks[i], "'", "\\'") + "';\n"

            cont = cont + "np_bookmarks = " + str(len(self.bookmarks)) + ";\n"
            cont = cont + "np_bookmark = new Array(" + str(len(self.bookmarks)) + ");\n"
            for i in range(0,len(self.bookmarks)):
                cont = cont + "np_bookmark[" + str(i) + "] = '" + string.replace(self.bookmarks[i], "'", "\\'") + "';\n"

            try:
                clientsock.send("HTTP/1.0 200 OK\r\n")
                clientsock.send(self.extraheaders)
                clientsock.send("Content-Type: text/plain\r\n")
                clientsock.send("Content-Length: " + str(len(cont)) + "\r\n")
                clientsock.send("\r\n")
                clientsock.send(cont)
                clientsock.close()
            except socket.error:
                clientsock.close()
            return

        if station == "/info":
            # Dump current metadata struct
            cont = "<p>metadata age:" + str(int(time.time() - self.lastfm.metadatatime)) + "</p>"
            cont = cont + "<p><pre>"
            for tmp in self.lastfm.info.keys():
                cont = cont + tmp + ": " + self.lastfm.info[tmp] + "\n"
            cont = cont + "</pre></p>\n"
            try:
                clientsock.send("HTTP/1.0 200 OK\r\n")
                clientsock.send(self.extraheaders)
                clientsock.send("Content-Type: text/html\r\n")
                clientsock.send("Content-Length: " + str(len(cont)) + "\r\n")
                clientsock.send("\r\n")
                clientsock.send(cont)
                clientsock.close()
            except socket.error:
                clientsock.close()
            return

        if station == "/dump":
            # Dump current metadata struct
            cont = "<p>metadata age:" + str(int(time.time() - self.lastfm.metadatatime)) + "</p>"
            cont = cont + "<p><pre>"
            for tmp in self.lastfm.metadata.keys():
                cont = cont + tmp + ": " + self.lastfm.metadata[tmp] + "\n"
            cont = cont + "</pre></p>\n"
            try:
                clientsock.send("HTTP/1.0 200 OK\r\n")
                clientsock.send(self.extraheaders)
                clientsock.send("Content-Type: text/html\r\n")
                clientsock.send("Content-Length: " + str(len(cont)) + "\r\n")
                clientsock.send("\r\n")
                clientsock.send(cont)
                clientsock.close()
            except socket.error:
                clientsock.close()
            return

        if station == "/":
            title = "LastFMProxy v" + self.version
            cont = "<html><head><title>" + title + "</title>\n"
            cont = cont + "<link rel=\"shortcut icon\" href=\"/data/favicon.ico\" />\n"
            cont = cont + "<link rel=\"icon\" href=\"/data/favicon.ico\" />\n"
            cont = cont + "<link rel=\"icon\" type=\"image/png\" href=\"/data/nice_favicon.png\" />\n"
            cont = cont + "<link rel=\"stylesheet\" type=\"text/css\" media=\"screen\" href=\"data/" + config.theme + ".css\" />\n"
            cont = cont + "<script>\n"
            cont = cont + "var host = 'http://" + http["Host"] + "';\n"
            f = open(os.path.join(self.basedir, "data", "main.js"), "r")
            cont = cont + f.read()
            f.close()
            cont = cont + "</script>\n"

            cont = cont + "</head><body>\n"
            cont = cont + "<form action=\"/\" name=\"lfmpform\" method=\"get\">"

            f = open(os.path.join(self.basedir, "data", config.theme + ".html"), "r")
            gui = f.read()
            f.close()

            tmp = "<a href=\"http://vidar.gimp.org/lastfmproxy/\" target=\"_blank\">LastFMProxy v" + self.version + "</a> - &copy; 2005 Vidar Madsen"
            gui = string.replace(gui, "VERSION", tmp)

            gui = string.replace(gui, "STATION", "<span id=\"lfmp-dyn-station\">&nbsp;</span>")

            tmp = "<span id=\"lfmp-dyn-lasttracks-list\"></span>"
            gui = string.replace(gui, "LASTTRACKS", tmp)

            cover = "data/noalbum_medium.gif"
            gui = string.replace(gui, "COVER", "<span id=\"lfmp-dyn-cover\"><img width=130 height=130 src=\"" + cover + "\"></span>")
            gui = string.replace(gui, "SIMILAR", "<span id=\"lfmp-dyn-similarlink\">&nbsp;</span>")
            gui = string.replace(gui, "FANS", "<span id=\"lfmp-dyn-fanslink\">&nbsp;</span>")
            gui = string.replace(gui, "ARTIST", "<span id=\"lfmp-dyn-artist\">&nbsp;</span>")
            gui = string.replace(gui, "ALBUM", "<span id=\"lfmp-dyn-album\">&nbsp;</span>")
            gui = string.replace(gui, "TRACK", "<span id=\"lfmp-dyn-track\">&nbsp;</span>")
            gui = string.replace(gui, "DURATION", "<span id=\"lfmp-dyn-dur\">&nbsp;</span>")

            tmp = "<select name=\"stationselect\" onChange=\"selectstation();\"><option value=\"\">...</option></select>"
            gui = string.replace(gui, "BOOKMARKS", tmp)

            tmp = "<a href=\"/\" target=\"_self\" title=\"Refresh (R)\">Refresh</a> &middot; "

            tmp = tmp + "<span id=\"lfmp-buttons1\" style=\"display: none;\">"
            tmp = tmp + "<a href=\"javascript:skip();\" title=\"Skip track (space)\">Skip</a> &middot; "
            tmp = tmp + "<a href=\"javascript:love();\" title=\"Love this track (enter)\">Love</a> &middot; "
            tmp = tmp + "<a href=\"javascript:ban();\" title=\"Ban this track (backspace)\">Ban</a> &middot; "
            tmp = tmp + "<input type=\"checkbox\" onChange=\"togglertp();\" name=\"rtp\" id=\"lfmp-rtp\"> <label for=\"lfmp-rtp\">R<span class=\"lfmp-shortcut\">e</span>cord to profile</label> &middot; "
            tmp = tmp + "<input type=\"checkbox\" onChange=\"toggledisc();\" name=\"discovery\" id=\"lfmp-discovery\"> <label for=\"lfmp-discovery\"><span class=\"lfmp-shortcut\">D</span>iscovery mode</label>"
            tmp = tmp + "</span>"

            tmp = tmp + "<span id=\"lfmp-buttons2\" style=\"display: none;\">"
            tmp = tmp + "<a href=\"http://" + http["Host"] + "/lastfm.m3u\">Start radio</a>\n"
            tmp = tmp + "</span>"

            gui = string.replace(gui, "BUTTONS", tmp)

            cont = cont + gui

            cont = cont + "</form>"
            cont = cont + "<script>\ntick();\n</script>\n"
            cont = cont + "</body></html>\n"

            try:
                clientsock.send("HTTP/1.0 200 OK\r\n")
                clientsock.send(self.extraheaders)
                clientsock.send("Content-Type: text/html; charset=utf-8\r\n")
                clientsock.send("Content-Length: " + str(len(cont)) + "\r\n")
                clientsock.send("\r\n")
                clientsock.send(cont)
                clientsock.close()
            except socket.error:
                clientsock.close()
            return

        if station[:6] == "/data/":
            if station[-4:] == ".css":
                type = "text/css"
            elif station[-4:] == ".png":
                type = "image/png"
            elif station[-4:] == ".jpg":
                type = "image/jpeg"
            elif station[-4:] == ".gif":
                type = "image/gif"
            else:
                type = "application/octet-stream"

            try:
                f = open(os.path.join(self.basedir, "data", station[6:]), "rb")
            except IOError:
                cont = "File \"" + station + "\" not found\n"
                try:
                    clientsock.send("HTTP/1.0 404 Not Found\r\n")
                    clientsock.send(self.extraheaders)
                    clientsock.send("Content-Type: text/plain\r\n")
                    clientsock.send("Content-Length: " + str(len(cont)) + "\r\n")
                    clientsock.send("\r\n")
                    clientsock.send(cont)
                    clientsock.close()
                except socket.error:
                    clientsock.close()
                return

            cont = f.read()
            f.close()
            try:
                # No extraheaders here, since we want to cache these files.
                clientsock.send("HTTP/1.0 200 OK\r\n")
                clientsock.send("Server: LastFMProxy/" + self.version + "\r\n")
                clientsock.send("Content-Type: " + type + "\r\n")
                clientsock.send("Content-Length: " + str(len(cont)) + "\r\n")
                clientsock.send("\r\n")
                clientsock.send(cont)
                clientsock.close()
            except socket.error:
                clientsock.close()
            return

        if station[-4:] == ".m3u":
            cont = "http://" + http["Host"] + string.replace(station, ".m3u", ".mp3") + "\n"
            try:
                clientsock.send("HTTP/1.0 200 OK\r\n")
                clientsock.send(self.extraheaders)
                clientsock.send("Content-Type: audio/x-mpegurl\r\n")
                clientsock.send("Content-Length: " + str(len(cont)) + "\r\n")
                clientsock.send("\r\n")
                clientsock.send(cont)
                clientsock.close()
            except socket.error:
                clientsock.close()
            return

        if station[-4:] != ".mp3":
            cont = "File \"" + station + "\" not found\n"
            try:
                clientsock.send("HTTP/1.0 404 Not Found\r\n")
                clientsock.send(self.extraheaders)
                clientsock.send("Content-Type: text/plain\r\n")
                clientsock.send("Content-Length: " + str(len(cont)) + "\r\n")
                clientsock.send("\r\n")
                clientsock.send(cont)
                clientsock.close()
            except socket.error:
                clientsock.close()
            return

        url = string.split(self.lastfm.info["stream_url"], "/", 3)
        host = string.split(url[2], ":")
        if len(host) != 2:
            host = [ host[0], 80 ]
        else:
            host[1] = int(host[1])

        # Connect to actual server and request stream
        streamsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        if config.useproxy:
            streamsock.connect((config.proxyhost, config.proxyport))
            streamsock.send("GET http://" + host[0] + ":" + str(host[1]) + "/" + url[3] + " HTTP/1.0\r\n")
        else:
            streamsock.connect((host[0], host[1]))
            streamsock.send("GET /" + url[3] + " HTTP/1.0\r\n")
        streamsock.send("Host: " + host[0] + "\r\n")
        streamsock.send("\r\n")

        # Check if station is "last.mp3", and if not, send a station change
        if station != "/lastfm.mp3":
            station = "lastfm:/" + string.replace(station, ".mp3", "")
            #print "station:", station
            res = self.lastfm.changestation(station)
            if res.has_key("response") and res["response"] == "OK":
                self.storebookmark(station)
            #print repr(res)

        # Pass HTTP headers on to client
        while True:
            line = ""
            while True:
                c = streamsock.recv(1)
                line = line + c
                if c == '\n':
                    break

            if line == "\r\n" and sendmetadata:
                for tmp in [ "icy-name:last.fm", 
                             "icy-pub:1", 
                             "icy-url:http://www.last.fm/",
                             "icy-metaint:" + str(metaint) ]:
                    clientsock.send(tmp + "\r\n")

            try:
                clientsock.send(line)
            except socket.error:
                break
            if line == "\r\n":
                break

        # Stream data
        count = 0
        while not self.quit:
            l = metaint - count
            try:
                data = streamsock.recv(l)
            except socket.error:
                break
            if data == "":
                break

            if string.find(data, "SYNC") >= 0:
                self.lastfm.progress = time.time()

            count = count + len(data)
            if count == metaint:
                count = 0

            try:
                clientsock.send(data)
            except socket.error:
                break

            #self.checkmetadata()

            # Inject Shoutcast tags
            if sendmetadata and count == 0:

                tmp = self.lastfm.metadata
                
                # Metadata might not be updated yet, so be paranoid
                if tmp and tmp.has_key("artist") and tmp.has_key("track"):
                    trackname = tmp["artist"] + " - " + tmp["track"]
                else:
                    trackname = "Wait..."

                icytag = "StreamTitle='" + trackname + "';StreamUrl='';" + chr(0) * 16
                blocks = len(icytag) / 16
                icytag = chr(blocks) + icytag[:blocks*16]
                try:
                    clientsock.send(icytag)
                except socket.error:
                    break

        #print "Stopping..."

        streamsock.close()
        clientsock.close()
        self.checktime = time.time() + 2

    def run(self, port):
        print "Starting LastFMProxy " + self.version + "..."

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # Set socket options to allow binding to the same address
        # when the proxy has been closed and then restarted:
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s.bind(("0.0.0.0", port))
        s.listen(5)

        print "Connecting to last.fm server..."
        self.lastfm = lastfm.lastfm();
        self.lastfm.connect(self.username, self.password)
        #print self.lastfm.info

        if not self.lastfm.info.has_key("session"):
            print "Handshake failed."
            s.close()
            return

        if self.lastfm.info["session"] == "FAILED":
            print "Handshake failed. Bad login info, perhaps?"
            s.close()
            return

        self.bookmarks = []
        try:
            f = open(os.path.join(self.basedir, "bookmarks.txt"), "r")
            for line in f.readlines():
                self.bookmarks.append(string.rstrip(line))
            f.close()
        except IOError:
            try:
                f = open(os.path.join(self.basedir, "stations.txt"), "r")
                for line in f.readlines():
                    self.bookmarks.append(string.rstrip(line))
                f.close()
            except IOError:
                pass

        print "To tune in, point your browser to:"
        print "  http://localhost:" + str(port) + "/"

        runningthreads = []

        t = threading.Thread(target=self.metadataloop)
        t.start()
        runningthreads.append(t)
        #print repr(runningthreads)

        try:
            while not self.quit:
                (clientsocket, address) = s.accept()
                #print "\nGot connection from", address
                t = threading.Thread(target=self.gotconnection,args=(clientsocket,))
                t.start()
                runningthreads.append(t)
                #print repr(runningthreads)
                #self.gotconnection(clientsocket)
        except KeyboardInterrupt:
            self.quit = 1

        #print "Waiting for threads to end..."
        for t in runningthreads:
            t.join()
        #print "Done! Bye!"

        s.close()
        return

def hexify(s):
    result = ""
    for c in s:
        result = result + ("%02x" % ord(c))
    return result

p = proxy(config.username, hexify(md5.md5(config.password).digest()))
p.basedir = os.path.dirname(sys.argv[0])
p.run(config.listenport)


Generated by  Doxygen 1.6.0   Back to index