3
\$\begingroup\$

I've posted a repo on a live radio streamer much like shoutcast/icecast without admin panels / user panels or custom mounts. I was wondering if anyone could review it and tell me what I can do better or add to it.

Repo

Server

from twisted.python import log
from twisted.internet import reactor,ssl, protocol
import config
import uuid
import json
import header

class cl:
    clients = list()
    source = None
    sourceID = None
    sent_header = list()
    id3_headers = {
        "title": "None",
        "length": "0",
    }

class RadioServer(protocol.Protocol):

    def connectionMade(self):
        if config.PyCasterMaxListeners != len(cl.clients):
            self.id = str(uuid.uuid4())
            self.peer = str(self.transport.getPeer())
            cl.clients.append(self)
            print("Client-Connected-IP: "+self.peer)
            print("Client-Connected-ID: " + self.id)
        else:
            self.transport.abortConnection()

    def connectionLost(self, reason):
        if self.id == cl.sourceID:
            print("Source-Closed: "+ str(reason))
            cl.sourceID = None
            cl.source = None
        else:
            self.removeClient(self.id)
            print("Client-Closed-Reason: " + str(reason))
            print("Client-Closed-IP: " + self.peer)
            print("Client-Closed-ID: " + self.id)

    def dataReceived(self, data):
        try: dct = json.loads(data)
        except: dct = {}
        if dct.has_key("PyCasterAuth"):
            if not cl.sourceID:
                auth = dct['PyCasterAuth']
                if auth == config.PyCasterAuth:
                    cl.source = self
                    cl.sourceID = self.id
                    self.transport.write("ok")
                    print("Source-Registered")
                    print("Source-ID: " + self.id)
                    self.removeClient(self.id)
                else:
                    cl.source.transport.write("denied")
                    print("Source-Login-Denied-IP: " + self.peer)
                    print("Source-Login-Denied-ID: " + self.id)
            else:
                print("Source-Exists-IP: " + self.peer)
                print("Source-Exists-ID: " + self.id)
                self.closeCl(self.id)

        elif dct.has_key("info"):
            cl.id3_headers = dct["info"]

        elif dct.has_key("buffer"):
            buffer = dct['buffer'].decode('base64')
            self.sendClients(buffer, bin=True)

    def removeClient(self, id):
        for client in cl.clients:
            if client.id == id:
                cl.clients.remove(client)
        for client in cl.sent_header:
            if client.id == id:
                cl.sent_header.remove(client)

    def closeCl(self, id):
        for client in cl.clients:
            if client.id == id:
                self.removeClient(id)
                client.transport.abortConnection()
                print("Server-Closed-Client: (%s, %s)" % (id, client.peer))


    def sendClients(self, msg, bin=False):
        for client in cl.clients:
            if client.id == cl.sourceID:
                self.removeClient(client.id)
            if bin:
                if client not in cl.sent_header:
                    head = header.header
                    head = head\
                        .replace("$title", cl.id3_headers['title'])\
                        .replace("$length", str(cl.id3_headers['length']))\
                        .replace("$listen", str(len(cl.clients)))\
                        .replace("$max", str(config.PyCasterMaxListeners))
                    client.transport.write("HTTP/1.1 200 OK\r\n"+bytes(head))
                    cl.sent_header.append(client)
            client.transport.write(msg)
            if config.PyCasterSendLogging:
                print("SENT %i bytes TO %s" % (len(msg), client.id))


if __name__=="__main__":
    import sys
    key = config.PyCasterSSLKey
    cert = config.PyCasterSSLCert
    factory = protocol.Factory()
    log.startLogging(config.PyCasterLogFile)
    factory.protocol = RadioServer
    if config.PyCasterSSL:
        reactor.listenSSL(config.PyCasterPort, factory,  ssl.DefaultOpenSSLContextFactory(key, cert))
        reactor.run()
    else:
        reactor.listenTCP(config.PyCasterPort, factory)
        reactor.run()

PyCasterClient

import config
import json
import PyCasterError
import SSLSocket
import socket
import mutagen

class Pycaster:
    def __init__(self):
        self.ok = False
        self._info = {}
        self._has_sent = 0
        try:
            if config.PyCasterSSL:
                self._socket = SSLSocket.SSLSocket().socket()
                self._socket.connect((config.PyCasterHost, config.PyCasterPort))
            else:
                self._socket = socket.socket()
                self._socket.connect((config.PyCasterHost, config.PyCasterPort))
        except Exception as e:
            raise PyCasterError.PyCasterConnect(e.message)

    def file(self, file):
        _file = open(file, "rb")
        mp3 = mutagen.File(file)
        try:
            title = mp3['title']
        except:
            title = "None"
        dct = {
            'info': {
                "title": title,
                "length": mp3.info.length,
                "bitrate": mp3.info.bitrate
            }
        }
        self._has_sent = 0
        self._info = dct
        return _file


    def send(self, data, buffer=True):
        if self.ok:
            if not self._has_sent:
                self._socket.send(json.dumps(self._info))
                self._has_sent = 1
            if buffer: data = json.dumps({"buffer": data.encode('base64')})
            try:
                self._socket.send(data)
            except:
                raise PyCasterError.PyCasterConnectionLost

    def events(self, **kw):
        return json.dumps(kw)

    def _messageEvent(self, data):
        if data == "ok":
            self.ok = True
        elif data == "source-exists":
            raise PyCasterError.PyCasterAlreadyLoggedIn
        elif data == "denied":
            raise PyCasterError.PyCasterInvalidAuth

    def login_as_source(self):
        data = self.events(PyCasterAuth=config.PyCasterAuth)
        self._socket.send(data)

    def loopme(self):
        if not self.ok:
            try: self._messageEvent(self._socket.recv(13))
            except:
                raise PyCasterError.PyCasterConnectionLost

PyCaster

import glob
import random
import time
import log
import PyCasterClient
import config


class PyCaster:
    def __init__(self):
        self._client = PyCasterClient.Pycaster()
        self._client.login_as_source()
        self.count = 0

    def getSongs(self):
        if config.directory:
            print(log.log("Using directory: "+ config.directory))
            songs = glob.glob(config.directory)
            if config.shuffle:
                random.shuffle(songs)
            print(log.log("Directory-Shuffle: %s,  with %i songs" % (config.shuffle, len(songs))))
            return songs

        elif config.playlist:
            try:
                print(log.log("Using playlist: " + config.playlist))
                songs = list()
                f = open(config.playlist, "r")
                for entry in f.readlines():
                    songs.append(entry.strip())
                f.close()
                if config.shuffle:
                    random.shuffle(songs)
                print(log.log("Playlist-Shuffle: %s,  with %i songs" % (config.shuffle, len(songs))))
                return songs
            except:
                print(log.log("Playlist not found", "err"))
                raise Exception(config.playlist + ' not found..')


    def _next(self):
        songs = self.getSongs()
        if config.loop:
            if len(songs) >= self.count+1:
                self.count = 0
            else:
                self.count += 1
            self.mainLoop()
        else:
            self.count += 1 # error will stop loop
            self.mainLoop()

    def mainLoop(self):
        song = self._client.file(self.getSongs()[self.count])
        print(log.log("Currently Playing: (" + song.name + ") with loop %s" % config.loop))
        buf = song.read(4096)
        while True:
            nbuf = buf
            buf = song.read(4096)
            self._client.loopme()
            if len(nbuf) == 0:
                log.log(song.name + " has ended")
                break
            self._client.send(nbuf)
            time.sleep(0.1)
        self._next()

if __name__=="__main__":
    pyc = PyCaster()
    pyc.mainLoop()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.