Skip to content

Instantly share code, notes, and snippets.

@davidblewett
Created November 6, 2013 17:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidblewett/7340369 to your computer and use it in GitHub Desktop.
Save davidblewett/7340369 to your computer and use it in GitHub Desktop.
Simple HTTP FLAC streamer
from hashlib import md5
from audiotools.bitstream import BitstreamReader
from audiotools.flac import FlacAudio
from audiotools.flac import InvalidFLAC
from audiotools.id3 import skip_id3v2_comment
from audiotools.py_decoders import FlacDecoder
from audiotools.text import ERR_FLAC_INVALID_BLOCK
import requests
class StreamingFlacDecoder(FlacDecoder):
def __init__(self, url, channel_mask):
response = requests.get(url, stream=True)
self.reader = BitstreamReader(response.raw, 0)
if (self.reader.read_bytes(4) != 'fLaC'):
raise ValueError("invalid FLAC file")
self.current_md5sum = md5()
#locate the STREAMINFO,
#which is sometimes needed to handle non-subset streams
for (block_id,
block_size,
block_reader) in self.metadata_blocks(self.reader):
logger.info('Block info: %s, %d', block_id, block_size)
if (block_id == 0):
#read STREAMINFO
self.minimum_block_size = block_reader.read(16)
self.maximum_block_size = block_reader.read(16)
self.minimum_frame_size = block_reader.read(24)
self.maximum_frame_size = block_reader.read(24)
self.sample_rate = block_reader.read(20)
self.channels = block_reader.read(3) + 1
self.channel_mask = channel_mask
self.bits_per_sample = block_reader.read(5) + 1
self.total_frames = block_reader.read64(36)
self.md5sum = block_reader.read_bytes(16)
#these are frame header lookup tables
#which vary slightly depending on STREAMINFO's values
self.BLOCK_SIZE = [self.maximum_block_size,
192, 576, 1152,
2304, 4608, None, None,
256, 512, 1024, 2048,
4096, 8192, 16384, 32768]
self.SAMPLE_RATE = [self.sample_rate,
88200, 176400, 192000,
8000, 16000, 22050, 24000,
32000, 44100, 48000, 96000,
None, None, None, None]
self.BITS_PER_SAMPLE = [self.bits_per_sample,
8, 12, None, 16, 20, 24, None]
class StreamingFlacAudio(FlacAudio):
def __init__(self, url):
"""url is a plain string"""
FlacAudio.__init__(self, url)
# FIXME: use metadata from API
self.__channels__ = 2
self.__samplerate__ = 44100
def __read_streaminfo__(self):
pass
def to_pcm(self):
"""returns a PCMReader object containing the track's PCM data"""
from audiotools import PCMReaderError
try:
return StreamingFlacDecoder(self.filename,
self.channel_mask())
except (IOError, ValueError), msg:
#The only time this is likely to occur is
#if the FLAC is modified between when FlacAudio
#is initialized and when to_pcm() is called.
return PCMReaderError(error_message=str(msg),
sample_rate=self.sample_rate(),
channels=self.channels(),
channel_mask=int(self.channel_mask()),
bits_per_sample=self.bits_per_sample())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment