|
- #!/usr/bin/env python
- # Copyright 2009 John-Mark Gurney <jmg@funkthat.com>
- '''Audio Source'''
-
- __version__ = '$Change$'
- # $Id$
-
- 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' % repr(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' %
- (repr(arg), repr(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 '<AudioPlayer: fileno: %d, connected: %s, self.disconnecting: %s, _writeDisconnected: %s>' % (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' % repr(origfmt))
-
- try:
- mt = self.mtformat[fmt]
- except KeyError:
- raise KeyError('No mime-type for audio format: %s.' %
- repr(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) as 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)):
- 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(repr(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()
|