#!/usr/bin/env python # Copyright 2009 John-Mark Gurney '''Audio Source''' __version__ = '$Change: 1366 $' # $Id: //depot/python/pymeds/main/shoutcast.py#24 $ import ossaudiodev import os.path from DIDLLite import Container, MusicGenre, AudioItem, Resource, ResourceList from FSStorage import registerklassfun from twisted.internet.abstract import FileDescriptor from twisted.internet import fdesc from twisted.python import log, threadable, failure from twisted.web import error, http, resource, server from zope.interface import implements mttobytes = { 'audio/l8': 1, 'audio/l16': 2, } def bytespersecmt(mt): tmp = [ x.strip() for x in mt.split(';') ] try: r = mttobytes[tmp[0].lower()] except KeyError: raise ValueError('invalid audio type: %s' % `tmp[0]`) v = set(('rate', 'channels')) for i in tmp[1:]: arg, value = [ x.strip() for x in i.split('=', 1) ] if arg in v: v.remove(arg) r *= int(value) else: raise ValueError('invalid audio parameter %s in %s' % (`arg`, `mt`)) return r class AudioPlayer(FileDescriptor): def __init__(self, consumer, dev, mode, params): self._dev = ossaudiodev.open(dev, mode) # Set some sub-functions self.fileno = self._dev.fileno self.setparameters = self._dev.setparameters res = self.setparameters(*params) self._dev.nonblock() FileDescriptor.__init__(self) self.connected = True self.attached = consumer self.writefun = self.attached.write consumer.registerProducer(self, True) self.dobuffer = False self.buffer = None self.startReading() # Drop our useless write connection self._writeDisconnected = True def writeSomeData(self, data): print 'wsd:', len(data) return fdesc.writeToFD(self.fileno(), data) def doRead(self): return fdesc.readFromFD(self.fileno(), self.writefun) def connectionLost(self, reason): FileDescriptor.connectionLost(self, reason) print 'AP, connectionLost' self.fileno = lambda: -1 self.setparameters = None if self._dev is not None: self._dev.close() self._dev = None self.attached = None def stopProducing(self): print 'AP, sp' self.writefun = lambda x: None FileDescriptor.stopProducing(self) def pauseProducing(self): if not self.dobuffer: self.buffer = [] self.dobuffer = True self.writefun = self.buffer.append #FileDescriptor.pauseProducing(self) def resumeProducing(self): if self.dobuffer: self.attached.write(''.join(self.buffer)) self.dobuffer = False self.buffer = None self.writefun = self.attached.write #FileDescriptor.resumeProducing(self) def __repr__(self): return '' % (self.fileno(), self.connected, self.disconnecting, self._writeDisconnected) class AudioResource(resource.Resource): isLeaf = True mtformat = { ossaudiodev.AFMT_S16_BE: 'audio/L16', ossaudiodev.AFMT_U8: 'audio/L8', } producerFactory = AudioPlayer def __init__(self, dev, default): resource.Resource.__init__(self) self.dev = dev self.default = default @staticmethod def getfmt(fmt): return getattr(ossaudiodev, 'AFMT_%s' % fmt) def getmimetype(self, *args): if len(args) == 0: args = self.default elif len(args) != 3: raise TypeError('getmimetype() takes exactly 0 or 3 aruments (%d given)' % len(args)) fmt, nchan, rate = args origfmt = fmt try: fmt = getattr(ossaudiodev, 'AFMT_%s' % fmt) nchan = int(nchan) rate = int(rate) except AttributeError: raise ValueError('Invalid audio format: %s' % `origfmt`) try: mt = self.mtformat[fmt] except KeyError: raise KeyError('No mime-type for audio format: %s.' % `origfmt`) return '%s;rate=%d;channels=%d' % (mt, rate, nchan) def render(self, request): default = self.default if request.postpath: default = request.postpath fmt, nchan, rate = default nchan = int(nchan) rate = int(rate) try: request.setHeader('content-type', self.getmimetype(fmt, nchan, rate)) except (ValueError, AttributeError, KeyError), x: return error.ErrorPage(http.UNSUPPORTED_MEDIA_TYPE, 'Unsupported Media Type', str(x)).render(request) #except AttributeError: # return error.NoResource('Unknown audio format.').render(request) #except ValueError: # return error.NoResource('Unknown channels (%s) or rate (%s).' % (`nchan`, `rate`)).render(request) #except KeyError: # return error.ErrorPage(http.UNSUPPORTED_MEDIA_TYPE, # 'Unsupported Media Type', # 'No mime-type for audio format: %s.' % # `fmt`).render(request) if request.method == 'HEAD': return '' self.producerFactory(request, self.dev, 'r', (self.getfmt(fmt), nchan, rate, True)) # and make sure the connection doesn't get closed return server.NOT_DONE_YET synchronized = [ 'render' ] threadable.synchronize(AudioResource) class ossaudiodev_fmts: pass for i in (k for k in dir(ossaudiodev) if k[:5] == 'AFMT_' and \ isinstance(getattr(ossaudiodev, k), (int, long))): setattr(ossaudiodev_fmts, i, getattr(ossaudiodev, i)) class AudioSource(AudioItem): def __init__(self, *args, **kwargs): file = kwargs.pop('file') fargs = eval(open(file).read().strip(), { '__builtins__': {}, }) # 'ossaudiodev': ossaudiodev_fmts }) self.dev = fargs.pop('dev') default = fargs.pop('default') kwargs['content'] = AudioResource(self.dev, default) AudioItem.__init__(self, *args, **kwargs) if False: self.bitrate = bitrate self.url = '%s/%s' % (self.cd.urlbase, self.id) self.res = ResourceList() self.res.append(Resource(self.url, 'http-get:*:%s:*' % kwargs['content'].getmimetype())) # XXX - add other non-default formats def getfmtstrings(f): r = [] for i in ( x for x in dir(ossaudiodev) if x[:5] == 'AFMT_' ): val = getattr(ossaudiodev, i) if val & f: f &= ~val r.append(i) while f: print f, f & -f r.append(f & -f) f ^= f & -f return r def detectaudiosource(origpath, fobj): path = os.path.basename(origpath) ext = os.path.splitext(path)[1] if ext == '.asrc': return AudioSource, { 'file': origpath } return None, None registerklassfun(detectaudiosource) from zope.interface import implements from twisted.internet.interfaces import IConsumer class FileConsumer: implements(IConsumer) def __init__(self, fp): self.fp = open(fp, 'w') self.producer = None def registerProducer(self, producer, streaming): if self.producer is not None: raise RuntimeError('already have a producer') self.streaming = streaming self.producer = producer producer.resumeProducing() def unregisterProducer(self): if self.producer is None: raise RuntimeError('none registered') self.producer = None def write(self, data): self.fp.write(data) if __name__ == '__main__': if False: i = ossaudiodev.open('/dev/dsp2', 'r') print getfmtstrings(i.getfmts()) i.setparameters(ossaudiodev.AFMT_S16_BE, 2, 44100, True) print `i.read(16)` else: aplr = AudioPlayer('/dev/dsp2', 'r', (ossaudiodev.AFMT_S16_BE, 2, 44100, True)) file = FileConsumer('test.output') file.registerProducer(aplr, True) from twisted.internet import reactor reactor.run()