#!/usr/bin/env python # Copyright 2009 John-Mark Gurney '''Audio Raw Converter''' from DIDLLite import AudioItem, Resource, ResourceList from FSStorage import registerklassfun from twisted.web import resource, server from twisted.internet.interfaces import IPullProducer from zope.interface import implements decoders = {} try: import flac decoders['flac'] = flac.FLACDec except ImportError: pass mtformat = { 16: 'audio/l16', # BE signed # 8: 'audio/l8', unsigned } def makemtfromdec(dec): return '%s;rate=%d;channels=%d' % (mtformat[dec.bitspersample], dec.samplerate, dec.channels) class DecoderProducer: implements(IPullProducer) def __init__(self, consumer, decoder, tbytes, skipbytes): self.decoder = decoder self.consumer = consumer self.tbytes = tbytes self.skipbytes = skipbytes consumer.registerProducer(self, False) def resumeProducing(self): r = self.decoder.read(8*1024) if r: #print 'DPrP:', len(r) if self.skipbytes: cnt = min(self.skipbytes, len(r)) r = r[cnt:] self.skipbytes -= cnt send = min(len(r), self.tbytes) r = r[:send] self.tbytes -= len(r) self.consumer.write(r) if self.tbytes: return print 'DPurP' self.consumer.unregisterProducer() def stopProducing(self): print 'DPsP' self.decoder.close() self.decoder = None class AudioResource(resource.Resource): isLeaf = True def __init__(self, f, dec): resource.Resource.__init__(self) self.f = f self.dec = dec def calcrange(self, rng, l): rng = rng.strip() unit, rangeset = rng.split('=') assert unit == 'bytes', `unit` start, end = rangeset.split('-') start = int(start) if end: end = int(end) else: end = l return start, end - start + 1 def render(self, request): decoder = self.dec(self.f) request.setHeader('content-type', makemtfromdec(decoder)) bytespersample = decoder.channels * decoder.bitspersample / 8 tbytes = decoder.totalsamples * bytespersample skipbytes = 0 request.setHeader('content-length', tbytes) request.setHeader('accept-ranges', 'bytes') if request.requestHeaders.hasHeader('range'): print 'range req:', `request.requestHeaders.getRawHeaders('range')` start, cnt = self.calcrange( request.requestHeaders.getRawHeaders('range')[0], tbytes) skipbytes = start % bytespersample print 'going:', start / bytespersample decoder.goto(start / bytespersample) print 'there' request.setHeader('content-length', cnt) request.setHeader('content-range', 'bytes %s-%s/%s' % (start, start + cnt - 1, tbytes)) tbytes = cnt if request.method == 'HEAD': return '' DecoderProducer(request, decoder, tbytes, skipbytes) print 'producing render' # and make sure the connection doesn't get closed return server.NOT_DONE_YET class AudioRaw(AudioItem): def __init__(self, *args, **kwargs): self.file = kwargs.pop('file') nchan = kwargs.pop('channels') samprate = kwargs.pop('samplerate') bitsps = kwargs.pop('bitspersample') samples = kwargs.pop('samples') self.totalbytes = nchan * samples * bitsps / 8 try: mt = mtformat[bitsps] except KeyError: raise KeyError('No mime-type for audio format: %s.' % `origfmt`) self.mt = '%s;rate=%d;channels=%d' % (mt, samprate, nchan) kwargs['content'] = AudioResource(self.file, kwargs.pop('decoder')) AudioItem.__init__(self, *args, **kwargs) self.url = '%s/%s' % (self.cd.urlbase, self.id) self.res = ResourceList() r = Resource(self.url, 'http-get:*:%s:*' % self.mt) r.size = self.totalbytes r.duration = float(samples) / samprate r.bitrate = nchan * samprate * bitsps / 8 r.sampleFrequency = samprate r.bitsPerSample = bitsps r.nrAudioChannels = nchan self.res.append(r) def detectaudioraw(origpath, fobj): for i in decoders.itervalues(): try: obj = i(origpath) # XXX - don't support down sampling yet if obj.bitspersample not in (8, 16): continue return AudioRaw, { 'decoder': i, 'file': origpath, 'channels': obj.channels, 'samplerate': obj.samplerate, 'bitspersample': obj.bitspersample, 'samples': obj.totalsamples, } except: #import traceback #traceback.print_exc() pass return None, None registerklassfun(detectaudioraw, True)