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\$

0

Your Answer

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

Browse other questions tagged or ask your own question.