|
|
@@ -12,7 +12,7 @@ __version__ = '$Change$' |
|
|
|
# to zero till we get one? |
|
|
|
|
|
|
|
import ConfigParser |
|
|
|
import StringIO |
|
|
|
import cStringIO as StringIO |
|
|
|
import os.path |
|
|
|
import random |
|
|
|
|
|
|
@@ -24,7 +24,7 @@ from DIDLLite import Container, MusicGenre, Item, AudioItem, Resource |
|
|
|
from FSStorage import registerklassfun |
|
|
|
|
|
|
|
from twisted.protocols import shoutcast |
|
|
|
from twisted.python import log, threadable |
|
|
|
from twisted.python import log, threadable, failure |
|
|
|
from twisted.internet import defer, protocol, reactor |
|
|
|
from twisted.web import error, http, resource, server |
|
|
|
from twisted.web.client import getPage, _parse |
|
|
@@ -180,16 +180,16 @@ class ShoutProxy(resource.Resource): |
|
|
|
self.urls = None |
|
|
|
self.fetchingurls = False |
|
|
|
|
|
|
|
def dump_exc(self): |
|
|
|
def dump_exc(self, failure, request): |
|
|
|
exc = StringIO.StringIO() |
|
|
|
traceback.print_exc(file=exc) |
|
|
|
failure.printBriefTraceback(file=exc) |
|
|
|
failure.printTraceback() |
|
|
|
exc.seek(0) |
|
|
|
self.request.setHeader('content-type', 'text/html') |
|
|
|
self.request.write(error.ErrorPage(http.INTERNAL_SERVER_ERROR, |
|
|
|
request.setHeader('content-type', 'text/html') |
|
|
|
request.write(error.ErrorPage(http.INTERNAL_SERVER_ERROR, |
|
|
|
http.RESPONSES[http.INTERNAL_SERVER_ERROR], |
|
|
|
'<pre>%s</pre>' % exc.read()).render(self.request)) |
|
|
|
self.request.finish() |
|
|
|
self.request = None |
|
|
|
'<pre>%s</pre>' % exc.read()).render(request)) |
|
|
|
request.finish() |
|
|
|
|
|
|
|
def startNextConnection(self, request): |
|
|
|
url = self.urls[self.urlpos] |
|
|
@@ -205,37 +205,32 @@ class ShoutProxy(resource.Resource): |
|
|
|
|
|
|
|
def gotPLS(self, page): |
|
|
|
self.fetchingurls = False |
|
|
|
try: |
|
|
|
pls = ConfigParser.SafeConfigParser() |
|
|
|
pls.readfp(StringIO.StringIO(page)) |
|
|
|
assert pls.getint(PLSsection, 'Version') == 2 |
|
|
|
assert pls.has_option(PLSsection, 'numberofentries') |
|
|
|
cnt = pls.getint(PLSsection, 'numberofentries') |
|
|
|
self.urls = [] |
|
|
|
for i in range(cnt): |
|
|
|
i += 1 # stupid one based arrays |
|
|
|
self.urls.append(pls.get(PLSsection, |
|
|
|
'File%d' % i)) |
|
|
|
#log.msg('pls urls:', self.urls) |
|
|
|
self.urlpos = random.randrange(len(self.urls)) |
|
|
|
except: |
|
|
|
self.dump_exc() |
|
|
|
self.urls = None |
|
|
|
self.triggerdefered(lambda x: x.errback(1)) |
|
|
|
return |
|
|
|
|
|
|
|
self.triggerdefered(lambda x: x.callback(1)) |
|
|
|
pls = ConfigParser.SafeConfigParser() |
|
|
|
pls.readfp(StringIO.StringIO(page)) |
|
|
|
# KCSM 91.1 doesn't provide a version |
|
|
|
#assert pls.getint(PLSsection, 'Version') == 2 |
|
|
|
assert pls.has_option(PLSsection, 'numberofentries') |
|
|
|
cnt = pls.getint(PLSsection, 'numberofentries') |
|
|
|
self.urls = [] |
|
|
|
for i in range(cnt): |
|
|
|
i += 1 # stupid one based arrays |
|
|
|
self.urls.append(pls.get(PLSsection, |
|
|
|
'File%d' % i)) |
|
|
|
#log.msg('pls urls:', self.urls) |
|
|
|
self.urlpos = random.randrange(len(self.urls)) |
|
|
|
|
|
|
|
self.triggerdefered(lambda x: x.callback(True)) |
|
|
|
|
|
|
|
def errPLS(self, failure): |
|
|
|
self.fetchingurls = False |
|
|
|
self.triggerdefered(lambda x: x.errback(1)) |
|
|
|
# XXX - retry? |
|
|
|
self.triggerdefered(lambda x: x.errback(failure)) |
|
|
|
|
|
|
|
def processRequest(self, ign, request): |
|
|
|
self.startNextConnection(request) |
|
|
|
|
|
|
|
def errRequest(self, failure, request): |
|
|
|
request.write(failure.render(self.request)) |
|
|
|
request.finish() |
|
|
|
self.dump_exc(failure, request) |
|
|
|
|
|
|
|
def render(self, request): |
|
|
|
request.setHeader('content-type', self.mt) |
|
|
@@ -258,9 +253,10 @@ class ShoutProxy(resource.Resource): |
|
|
|
self.fetchingurls = True |
|
|
|
# Not really sure if ascii is the correct one, |
|
|
|
# shouldn't getPage do proper escaping for me? |
|
|
|
getPage(self.shoutpls.encode('ascii')) \ |
|
|
|
.addCallbacks(self.gotPLS, self.errPLS) |
|
|
|
self.afterurls = [ defer.Deferred() ] |
|
|
|
d = getPage(self.shoutpls.encode('ascii')) |
|
|
|
d.addCallback(self.gotPLS) |
|
|
|
d.addErrback(self.errPLS) |
|
|
|
else: |
|
|
|
self.afterurls.append(defer.Deferred()) |
|
|
|
# Always add the callback if we don't have urls |
|
|
@@ -269,6 +265,7 @@ class ShoutProxy(resource.Resource): |
|
|
|
errbackArgs=(request, )) |
|
|
|
else: |
|
|
|
self.startNextConnection(request) |
|
|
|
|
|
|
|
# and make sure the connection doesn't get closed |
|
|
|
return server.NOT_DONE_YET |
|
|
|
|
|
|
@@ -276,20 +273,37 @@ class ShoutProxy(resource.Resource): |
|
|
|
'triggerdefered', ] |
|
|
|
threadable.synchronize(ShoutProxy) |
|
|
|
|
|
|
|
class ShoutStation(AudioItem): |
|
|
|
class ShoutURL(AudioItem): |
|
|
|
def __init__(self, *args, **kwargs): |
|
|
|
self.station = kwargs['station'] |
|
|
|
del kwargs['station'] |
|
|
|
url = kwargs.pop('url') |
|
|
|
mimetype = kwargs.pop('mimetype', 'audio/mpeg') |
|
|
|
bitrate = kwargs.pop('bitrate', None) |
|
|
|
|
|
|
|
kwargs['content'] = ShoutProxy(self.station['PLS_URL'], |
|
|
|
self.station['MimeType'].encode('ascii')) |
|
|
|
kwargs['content'] = ShoutProxy(url, mimetype) |
|
|
|
AudioItem.__init__(self, *args, **kwargs) |
|
|
|
self.url = '%s/%s' % (self.cd.urlbase, self.id) |
|
|
|
self.res = Resource(self.url, 'http-get:*:%s:*' % \ |
|
|
|
self.station['MimeType'].encode('ascii')) |
|
|
|
self.res = Resource(self.url, 'http-get:*:%s:*' % mimetype) |
|
|
|
#self.res = Resource(self.url + '/pcm', 'http-get:*:%s:*' % \ |
|
|
|
# 'audio/x-wav') |
|
|
|
self.bitrate = self.station['Bitrate'] * 128 # 1024k / 8bit |
|
|
|
if bitrate is not None: |
|
|
|
self.bitrate = bitrate |
|
|
|
|
|
|
|
class ShoutFile(ShoutURL): |
|
|
|
def __init__(self, *args, **kwargs): |
|
|
|
file = kwargs.pop('file') |
|
|
|
kwargs['url'] = open(file).read().strip() |
|
|
|
|
|
|
|
ShoutURL.__init__(self, *args, **kwargs) |
|
|
|
|
|
|
|
class ShoutStation(ShoutURL): |
|
|
|
def __init__(self, *args, **kwargs): |
|
|
|
self.station = kwargs.pop('station') |
|
|
|
|
|
|
|
kwargs['url'] = self.station['PLS_URL'] |
|
|
|
kwargs['mimetype'] = self.station['MimeType'].encode('ascii') |
|
|
|
kwargs['bitrate'] = self.station['Bitrate'] * 128 # 1024k / 8bit |
|
|
|
|
|
|
|
ShoutURL.__init__(*args, **kwargs) |
|
|
|
|
|
|
|
class ShoutGenre(MusicGenre): |
|
|
|
def __init__(self, *args, **kwargs): |
|
|
@@ -397,10 +411,15 @@ class ShoutCast(Container): |
|
|
|
if doupdate: |
|
|
|
Container.doUpdate(self) |
|
|
|
|
|
|
|
def detectshoutcastfile(path, fobj): |
|
|
|
path = os.path.basename(path) |
|
|
|
def detectshoutcastfile(origpath, fobj): |
|
|
|
path = os.path.basename(origpath) |
|
|
|
if path == 'SHOUTcast Radio': |
|
|
|
return ShoutCast, { } |
|
|
|
|
|
|
|
ext = os.path.splitext(path)[1] |
|
|
|
if ext == '.scst': |
|
|
|
return ShoutFile, { 'file': origpath } |
|
|
|
|
|
|
|
return None, None |
|
|
|
|
|
|
|
registerklassfun(detectshoutcastfile) |