class AuReader(object): def __init__(self, au_filename): from audiotools.bitstream import BitstreamReader from audiotools.text import (ERR_AU_INVALID_HEADER, ERR_AU_UNSUPPORTED_FORMAT) self.stream = BitstreamReader(open(au_filename, "rb"), False) (magic_number, self.data_offset, data_size, encoding_format, self.sample_rate, self.channels) = self.stream.parse("4b 5* 32u") if magic_number != b'.snd': self.stream.close() raise ValueError(ERR_AU_INVALID_HEADER) try: self.bits_per_sample = {2: 8, 3: 16, 4: 24}[encoding_format] except KeyError: self.stream.close() raise ValueError(ERR_AU_UNSUPPORTED_FORMAT) self.channel_mask = {1: 0x4, 2: 0x3}.get(self.channels, 0) self.bytes_per_pcm_frame = ((self.bits_per_sample // 8) * self.channels) self.total_pcm_frames = (data_size // self.bytes_per_pcm_frame) self.remaining_pcm_frames = self.total_pcm_frames def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def read(self, pcm_frames): # try to read requested PCM frames or remaining frames requested_pcm_frames = min(max(pcm_frames, 1), self.remaining_pcm_frames) requested_bytes = (self.bytes_per_pcm_frame * requested_pcm_frames) pcm_data = self.stream.read_bytes(requested_bytes) # raise exception if data block exhausted early if len(pcm_data) < requested_bytes: from audiotools.text import ERR_AU_TRUNCATED_DATA raise IOError(ERR_AU_TRUNCATED_DATA) else: self.remaining_pcm_frames -= requested_pcm_frames # return parsed chunk return FrameList(pcm_data, self.channels, self.bits_per_sample, True, True) def read_closed(self, pcm_frames): raise ValueError("cannot read closed stream") def seek(self, pcm_frame_offset): if pcm_frame_offset < 0: from audiotools.text import ERR_NEGATIVE_SEEK raise ValueError(ERR_NEGATIVE_SEEK) # ensure one doesn't walk off the end of the file pcm_frame_offset = min(pcm_frame_offset, self.total_pcm_frames) # position file in data block self.stream.seek(self.data_offset + (pcm_frame_offset * self.bytes_per_pcm_frame), 0) self.remaining_pcm_frames = (self.total_pcm_frames - pcm_frame_offset) return pcm_frame_offset def seek_closed(self, pcm_frame_offset): raise ValueError("cannot seek closed stream") def close(self): self.stream.close() self.read = self.read_closed self.seek = self.seek_closed
class WaveReader(object): """a PCMReader object for reading wave file contents""" def __init__(self, wave_filename): """wave_filename is a string""" from audiotools.bitstream import BitstreamReader self.stream = BitstreamReader(open(wave_filename, "rb"), True) # ensure RIFF<size>WAVE header is ok try: (riff, total_size, wave) = self.stream.parse("4b 32u 4b") except struct.error: from audiotools.text import ERR_WAV_INVALID_WAVE self.stream.close() raise ValueError(ERR_WAV_INVALID_WAVE) if riff != b'RIFF': from audiotools.text import ERR_WAV_NOT_WAVE self.stream.close() raise ValueError(ERR_WAV_NOT_WAVE) elif wave != b'WAVE': from audiotools.text import ERR_WAV_INVALID_WAVE self.stream.close() raise ValueError(ERR_WAV_INVALID_WAVE) else: total_size -= 4 fmt_chunk_read = False # walk through chunks until "data" chunk encountered while total_size > 0: try: (chunk_id, chunk_size) = self.stream.parse("4b 32u") except struct.error: from audiotools.text import ERR_WAV_INVALID_WAVE self.stream.close() raise ValueError(ERR_WAV_INVALID_WAVE) if not frozenset(chunk_id).issubset(WaveAudio.PRINTABLE_ASCII): from audiotools.text import ERR_WAV_INVALID_CHUNK self.stream.close() raise ValueError(ERR_WAV_INVALID_CHUNK) else: total_size -= 8 if chunk_id == b"fmt ": # when "fmt " chunk encountered, # use it to populate PCMReader attributes (self.channels, self.sample_rate, self.bits_per_sample, channel_mask) = parse_fmt(self.stream) self.channel_mask = int(channel_mask) self.bytes_per_pcm_frame = ((self.bits_per_sample // 8) * self.channels) fmt_chunk_read = True elif chunk_id == b"data": # when "data" chunk encountered, # use its size to determine total PCM frames # and ready PCMReader for reading if not fmt_chunk_read: from audiotools.text import ERR_WAV_PREMATURE_DATA self.stream.close() raise ValueError(ERR_WAV_PREMATURE_DATA) else: self.total_pcm_frames = (chunk_size // self.bytes_per_pcm_frame) self.remaining_pcm_frames = self.total_pcm_frames self.data_start = self.stream.getpos() return else: # all other chunks are ignored self.stream.skip_bytes(chunk_size) if chunk_size % 2: if len(self.stream.read_bytes(1)) < 1: from audiotools.text import ERR_WAV_INVALID_CHUNK self.stream.close() raise ValueError(ERR_WAV_INVALID_CHUNK) total_size -= (chunk_size + 1) else: total_size -= chunk_size else: # raise an error if no "data" chunk is encountered from audiotools.text import ERR_WAV_NO_DATA_CHUNK self.stream.close() raise ValueError(ERR_WAV_NO_DATA_CHUNK) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def read(self, pcm_frames): """try to read a pcm.FrameList with the given number of PCM frames""" # try to read requested PCM frames or remaining frames requested_pcm_frames = min(max(pcm_frames, 1), self.remaining_pcm_frames) requested_bytes = (self.bytes_per_pcm_frame * requested_pcm_frames) pcm_data = self.stream.read_bytes(requested_bytes) # raise exception if "data" chunk exhausted early if len(pcm_data) < requested_bytes: from audiotools.text import ERR_WAV_TRUNCATED_DATA_CHUNK raise IOError(ERR_WAV_TRUNCATED_DATA_CHUNK) else: self.remaining_pcm_frames -= requested_pcm_frames # return parsed chunk return FrameList(pcm_data, self.channels, self.bits_per_sample, False, self.bits_per_sample != 8) def read_closed(self, pcm_frames): raise ValueError("cannot read closed stream") def seek(self, pcm_frame_offset): """tries to seek to the given PCM frame offset returns the total amount of frames actually seeked over""" if pcm_frame_offset < 0: from audiotools.text import ERR_NEGATIVE_SEEK raise ValueError(ERR_NEGATIVE_SEEK) # ensure one doesn't walk off the end of the file pcm_frame_offset = min(pcm_frame_offset, self.total_pcm_frames) # position file in "data" chunk self.stream.setpos(self.data_start) self.stream.seek(pcm_frame_offset * self.bytes_per_pcm_frame, 1) self.remaining_pcm_frames = (self.total_pcm_frames - pcm_frame_offset) return pcm_frame_offset def seek_closed(self, pcm_frame_offset): raise ValueError("cannot seek closed stream") def close(self): """closes the stream for reading""" self.stream.close() self.read = self.read_closed self.seek = self.seek_closed
class AiffReader(object): """a PCMReader object for reading AIFF file contents""" def __init__(self, aiff_filename): """aiff_filename is a string""" from audiotools.bitstream import BitstreamReader self.stream = BitstreamReader(open(aiff_filename, "rb"), False) # ensure FORM<size>AIFF header is ok try: (form, total_size, aiff) = self.stream.parse("4b 32u 4b") except struct.error: from audiotools.text import ERR_AIFF_INVALID_AIFF raise InvalidAIFF(ERR_AIFF_INVALID_AIFF) if form != b'FORM': from audiotools.text import ERR_AIFF_NOT_AIFF raise ValueError(ERR_AIFF_NOT_AIFF) elif aiff != b'AIFF': from audiotools.text import ERR_AIFF_INVALID_AIFF raise ValueError(ERR_AIFF_INVALID_AIFF) else: total_size -= 4 comm_chunk_read = False # walk through chunks until "SSND" chunk encountered while total_size > 0: try: (chunk_id, chunk_size) = self.stream.parse("4b 32u") except struct.error: from audiotools.text import ERR_AIFF_INVALID_AIFF raise ValueError(ERR_AIFF_INVALID_AIFF) if not frozenset(chunk_id).issubset(AiffAudio.PRINTABLE_ASCII): from audiotools.text import ERR_AIFF_INVALID_CHUNK_ID raise ValueError(ERR_AIFF_INVALID_CHUNK_ID) else: total_size -= 8 if chunk_id == b"COMM": # when "COMM" chunk encountered, # use it to populate PCMReader attributes (self.channels, self.total_pcm_frames, self.bits_per_sample, self.sample_rate, channel_mask) = parse_comm(self.stream) self.channel_mask = int(channel_mask) self.bytes_per_pcm_frame = ((self.bits_per_sample // 8) * self.channels) self.remaining_pcm_frames = self.total_pcm_frames comm_chunk_read = True elif chunk_id == b"SSND": # when "SSND" chunk encountered, # strip off the "offset" and "block_size" attributes # and ready PCMReader for reading if not comm_chunk_read: from audiotools.text import ERR_AIFF_PREMATURE_SSND_CHUNK raise ValueError(ERR_AIFF_PREMATURE_SSND_CHUNK) else: self.stream.skip_bytes(8) self.ssnd_start = self.stream.getpos() return else: # all other chunks are ignored self.stream.skip_bytes(chunk_size) if chunk_size % 2: if len(self.stream.read_bytes(1)) < 1: from audiotools.text import ERR_AIFF_INVALID_CHUNK raise ValueError(ERR_AIFF_INVALID_CHUNK) total_size -= (chunk_size + 1) else: total_size -= chunk_size else: # raise an error if no "SSND" chunk is encountered from audiotools.text import ERR_AIFF_NO_SSND_CHUNK raise ValueError(ERR_AIFF_NO_SSND_CHUNK) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def read(self, pcm_frames): """try to read a pcm.FrameList with the given number of PCM frames""" # try to read requested PCM frames or remaining frames requested_pcm_frames = min(max(pcm_frames, 1), self.remaining_pcm_frames) requested_bytes = (self.bytes_per_pcm_frame * requested_pcm_frames) pcm_data = self.stream.read_bytes(requested_bytes) # raise exception if "SSND" chunk exhausted early if len(pcm_data) < requested_bytes: from audiotools.text import ERR_AIFF_TRUNCATED_SSND_CHUNK raise IOError(ERR_AIFF_TRUNCATED_SSND_CHUNK) else: self.remaining_pcm_frames -= requested_pcm_frames # return parsed chunk return FrameList(pcm_data, self.channels, self.bits_per_sample, True, True) def read_closed(self, pcm_frames): raise ValueError("cannot read closed stream") def seek(self, pcm_frame_offset): """tries to seek to the given PCM frame offset returns the total amount of frames actually seeked over""" if pcm_frame_offset < 0: from audiotools.text import ERR_NEGATIVE_SEEK raise ValueError(ERR_NEGATIVE_SEEK) # ensure one doesn't walk off the end of the file pcm_frame_offset = min(pcm_frame_offset, self.total_pcm_frames) # position file in "SSND" chunk self.stream.setpos(self.ssnd_start) self.stream.seek((pcm_frame_offset * self.bytes_per_pcm_frame), 1) self.remaining_pcm_frames = (self.total_pcm_frames - pcm_frame_offset) return pcm_frame_offset def seek_closed(self, pcm_frame_offset): raise ValueError("cannot seek closed stream") def close(self): """closes the stream for reading""" self.stream.close() self.read = self.read_closed self.seek = self.seek_closed
class AuReader(object): def __init__(self, au_filename): from audiotools.bitstream import BitstreamReader from audiotools.text import (ERR_AU_INVALID_HEADER, ERR_AU_UNSUPPORTED_FORMAT) self.stream = BitstreamReader(open(au_filename, "rb"), False) (magic_number, self.data_offset, data_size, encoding_format, self.sample_rate, self.channels) = self.stream.parse("4b 5* 32u") if (magic_number != b'.snd'): self.stream.close() raise ValueError(ERR_AU_INVALID_HEADER) try: self.bits_per_sample = {2: 8, 3: 16, 4: 24}[encoding_format] except KeyError: self.stream.close() raise ValueError(ERR_AU_UNSUPPORTED_FORMAT) self.channel_mask = {1: 0x4, 2: 0x3}.get(self.channels, 0) self.bytes_per_pcm_frame = ((self.bits_per_sample // 8) * self.channels) self.total_pcm_frames = (data_size // self.bytes_per_pcm_frame) self.remaining_pcm_frames = self.total_pcm_frames def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def read(self, pcm_frames): # try to read requested PCM frames or remaining frames requested_pcm_frames = min(max(pcm_frames, 1), self.remaining_pcm_frames) requested_bytes = (self.bytes_per_pcm_frame * requested_pcm_frames) pcm_data = self.stream.read_bytes(requested_bytes) # raise exception if data block exhausted early if (len(pcm_data) < requested_bytes): from audiotools.text import ERR_AU_TRUNCATED_DATA raise IOError(ERR_AU_TRUNCATED_DATA) else: self.remaining_pcm_frames -= requested_pcm_frames # return parsed chunk return FrameList(pcm_data, self.channels, self.bits_per_sample, True, True) def read_closed(self, pcm_frames): raise ValueError("cannot read closed stream") def seek(self, pcm_frame_offset): if (pcm_frame_offset < 0): from audiotools.text import ERR_NEGATIVE_SEEK raise ValueError(ERR_NEGATIVE_SEEK) # ensure one doesn't walk off the end of the file pcm_frame_offset = min(pcm_frame_offset, self.total_pcm_frames) # position file in data block self.stream.seek( self.data_offset + (pcm_frame_offset * self.bytes_per_pcm_frame), 0) self.remaining_pcm_frames = (self.total_pcm_frames - pcm_frame_offset) return pcm_frame_offset def seek_closed(self, pcm_frame_offset): raise ValueError("cannot seek closed stream") def close(self): self.stream.close() self.read = self.read_closed self.seek = self.seek_closed
class AiffReader(object): """a PCMReader object for reading AIFF file contents""" def __init__(self, aiff_filename): """aiff_filename is a string""" from audiotools.bitstream import BitstreamReader self.stream = BitstreamReader(open(aiff_filename, "rb"), False) # ensure FORM<size>AIFF header is ok try: (form, total_size, aiff) = self.stream.parse("4b 32u 4b") except struct.error: from audiotools.text import ERR_AIFF_INVALID_AIFF raise InvalidAIFF(ERR_AIFF_INVALID_AIFF) if form != b'FORM': from audiotools.text import ERR_AIFF_NOT_AIFF raise ValueError(ERR_AIFF_NOT_AIFF) elif aiff != b'AIFF': from audiotools.text import ERR_AIFF_INVALID_AIFF raise ValueError(ERR_AIFF_INVALID_AIFF) else: total_size -= 4 comm_chunk_read = False # walk through chunks until "SSND" chunk encountered while total_size > 0: try: (chunk_id, chunk_size) = self.stream.parse("4b 32u") except struct.error: from audiotools.text import ERR_AIFF_INVALID_AIFF raise ValueError(ERR_AIFF_INVALID_AIFF) if not frozenset(chunk_id).issubset(AiffAudio.PRINTABLE_ASCII): from audiotools.text import ERR_AIFF_INVALID_CHUNK_ID raise ValueError(ERR_AIFF_INVALID_CHUNK_ID) else: total_size -= 8 if chunk_id == b"COMM": # when "COMM" chunk encountered, # use it to populate PCMReader attributes (self.channels, self.total_pcm_frames, self.bits_per_sample, self.sample_rate, channel_mask) = parse_comm(self.stream) self.channel_mask = int(channel_mask) self.bytes_per_pcm_frame = ((self.bits_per_sample // 8) * self.channels) self.remaining_pcm_frames = self.total_pcm_frames comm_chunk_read = True elif chunk_id == b"SSND": # when "SSND" chunk encountered, # strip off the "offset" and "block_size" attributes # and ready PCMReader for reading if not comm_chunk_read: from audiotools.text import ERR_AIFF_PREMATURE_SSND_CHUNK raise ValueError(ERR_AIFF_PREMATURE_SSND_CHUNK) else: self.stream.skip_bytes(8) self.ssnd_start = self.stream.getpos() return else: # all other chunks are ignored self.stream.skip_bytes(chunk_size) if chunk_size % 2: if len(self.stream.read_bytes(1)) < 1: from audiotools.text import ERR_AIFF_INVALID_CHUNK raise ValueError(ERR_AIFF_INVALID_CHUNK) total_size -= (chunk_size + 1) else: total_size -= chunk_size else: # raise an error if no "SSND" chunk is encountered from audiotools.text import ERR_AIFF_NO_SSND_CHUNK raise ValueError(ERR_AIFF_NO_SSND_CHUNK) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def read(self, pcm_frames): """try to read a pcm.FrameList with the given number of PCM frames""" # try to read requested PCM frames or remaining frames requested_pcm_frames = min(max(pcm_frames, 1), self.remaining_pcm_frames) requested_bytes = (self.bytes_per_pcm_frame * requested_pcm_frames) pcm_data = self.stream.read_bytes(requested_bytes) # raise exception if "SSND" chunk exhausted early if len(pcm_data) < requested_bytes: from audiotools.text import ERR_AIFF_TRUNCATED_SSND_CHUNK raise IOError(ERR_AIFF_TRUNCATED_SSND_CHUNK) else: self.remaining_pcm_frames -= requested_pcm_frames # return parsed chunk return FrameList(pcm_data, self.channels, self.bits_per_sample, True, True) def read_closed(self, pcm_frames): raise ValueError("cannot read closed stream") def seek(self, pcm_frame_offset): """tries to seek to the given PCM frame offset returns the total amount of frames actually seeked over""" if pcm_frame_offset < 0: from audiotools.text import ERR_NEGATIVE_SEEK raise ValueError(ERR_NEGATIVE_SEEK) # ensure one doesn't walk off the end of the file pcm_frame_offset = min(pcm_frame_offset, self.total_pcm_frames) # position file in "SSND" chunk self.stream.setpos(self.ssnd_start) self.stream.seek((pcm_frame_offset * self.bytes_per_pcm_frame), 1) self.remaining_pcm_frames = (self.total_pcm_frames - pcm_frame_offset) return pcm_frame_offset def seek_closed(self, pcm_frame_offset): raise ValueError("cannot seek closed stream") def close(self): """closes the stream for reading""" self.stream.close() self.read = self.read_closed self.seek = self.seek_closed