|  | #!/usr/bin/env python
# Copyright 2009 John-Mark Gurney <jmg@funkthat.com>
'''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)
 |