def _parse_dac3(self, atom, fileobj): # ETSI TS 102 366 assert atom.name == b"dac3" ok, data = atom.read(fileobj) if not ok: raise ASEntryError("truncated %s atom" % atom.name) fileobj = cBytesIO(data) r = BitReader(fileobj) # sample_rate in AudioSampleEntry covers values in # fscod2 and not just fscod, so ignore fscod here. try: r.skip(2 + 5 + 3) # fscod, bsid, bsmod acmod = r.bits(3) lfeon = r.bits(1) bit_rate_code = r.bits(5) r.skip(5) # reserved except BitReaderError as e: raise ASEntryError(e) self.channels = [2, 1, 2, 3, 3, 4, 4, 5][acmod] + lfeon try: self.bitrate = [ 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640 ][bit_rate_code] * 1000 except IndexError: pass
def _parse_dac3(self, atom, fileobj): # ETSI TS 102 366 assert atom.name == b"dac3" ok, data = atom.read(fileobj) if not ok: raise ASEntryError("truncated %s atom" % atom.name) fileobj = cBytesIO(data) r = BitReader(fileobj) # sample_rate in AudioSampleEntry covers values in # fscod2 and not just fscod, so ignore fscod here. try: r.skip(2 + 5 + 3) # fscod, bsid, bsmod acmod = r.bits(3) lfeon = r.bits(1) bit_rate_code = r.bits(5) r.skip(5) # reserved except BitReaderError as e: raise ASEntryError(e) self.channels = [2, 1, 2, 3, 3, 4, 4, 5][acmod] + lfeon try: self.bitrate = [ 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640][bit_rate_code] * 1000 except IndexError: pass
def test_get_position(self): r = BitReader(cBytesIO(b"\xAB\xCD")) self.assertEqual(r.get_position(), 0) r.bits(3) self.assertEqual(r.get_position(), 3) r.skip(9) self.assertEqual(r.get_position(), 3 + 9) r.align() self.assertEqual(r.get_position(), 16)
def test_get_position(self): r = BitReader(BytesIO(b"\xAB\xCD")) self.assertEqual(r.get_position(), 0) r.bits(3) self.assertEqual(r.get_position(), 3) r.skip(9) self.assertEqual(r.get_position(), 3 + 9) r.align() self.assertEqual(r.get_position(), 16)
def test_is_aligned(self): r = BitReader(BytesIO(b"\xAB\xCD\xEF")) self.assertTrue(r.is_aligned()) r.skip(1) self.assertFalse(r.is_aligned()) r.skip(7) self.assertTrue(r.is_aligned()) r.bits(7) self.assertFalse(r.is_aligned()) r.bits(1) self.assertTrue(r.is_aligned())
def test_is_aligned(self): r = BitReader(cBytesIO(b"\xAB\xCD\xEF")) self.assertTrue(r.is_aligned()) r.skip(1) self.assertFalse(r.is_aligned()) r.skip(7) self.assertTrue(r.is_aligned()) r.bits(7) self.assertFalse(r.is_aligned()) r.bits(1) self.assertTrue(r.is_aligned())
def _parse_adif(self, fileobj): r = BitReader(fileobj) try: copyright_id_present = r.bits(1) if copyright_id_present: r.skip(72) # copyright_id r.skip(1 + 1) # original_copy, home bitstream_type = r.bits(1) self.bitrate = r.bits(23) npce = r.bits(4) if bitstream_type == 0: r.skip(20) # adif_buffer_fullness pce = ProgramConfigElement(r) try: self.sample_rate = _FREQS[pce.sampling_frequency_index] except IndexError: pass self.channels = pce.channels # other pces.. for i in range(npce): ProgramConfigElement(r) r.align() except BitReaderError as e: raise AACError(e) # use bitrate + data size to guess length start = fileobj.tell() fileobj.seek(0, 2) length = fileobj.tell() - start if self.bitrate != 0: self.length = (8.0 * length) / self.bitrate
def _parse_adif(self, fileobj): r = BitReader(fileobj) try: copyright_id_present = r.bits(1) if copyright_id_present: r.skip(72) # copyright_id r.skip(1 + 1) # original_copy, home bitstream_type = r.bits(1) self.bitrate = r.bits(23) npce = r.bits(4) if bitstream_type == 0: r.skip(20) # adif_buffer_fullness pce = ProgramConfigElement(r) try: self.sample_rate = _FREQS[pce.sampling_frequency_index] except IndexError: pass self.channels = pce.channels # other pces.. for i in xrange(npce): ProgramConfigElement(r) r.align() except BitReaderError as e: raise AACError(e) # use bitrate + data size to guess length start = fileobj.tell() fileobj.seek(0, 2) length = fileobj.tell() - start if self.bitrate != 0: self.length = (8.0 * length) / self.bitrate
def __init__(self, atom, fileobj): ok, data = atom.read(fileobj) if not ok: raise ASEntryError("too short %r atom" % atom.name) fileobj = cBytesIO(data) r = BitReader(fileobj) try: # SampleEntry r.skip(6 * 8) # reserved r.skip(2 * 8) # data_ref_index # AudioSampleEntry r.skip(8 * 8) # reserved self.channels = r.bits(16) self.sample_size = r.bits(16) r.skip(2 * 8) # pre_defined r.skip(2 * 8) # reserved self.sample_rate = r.bits(32) >> 16 except BitReaderError as e: raise ASEntryError(e) assert r.is_aligned() try: extra = Atom(fileobj) except AtomError as e: raise ASEntryError(e) self.codec = atom.name.decode("latin-1") self.codec_description = None if atom.name == b"mp4a" and extra.name == b"esds": self._parse_esds(extra, fileobj) elif atom.name == b"alac" and extra.name == b"alac": self._parse_alac(extra, fileobj) elif atom.name == b"ac-3" and extra.name == b"dac3": self._parse_dac3(extra, fileobj) if self.codec_description is None: self.codec_description = self.codec.upper()
def __init__(self, atom, fileobj): ok, data = atom.read(fileobj) if not ok: raise ASEntryError("too short %r atom" % atom.name) fileobj = cBytesIO(data) r = BitReader(fileobj) try: # SampleEntry r.skip(6 * 8) # reserved r.skip(2 * 8) # data_ref_index # AudioSampleEntry r.skip(8 * 8) # reserved self.channels = r.bits(16) self.sample_size = r.bits(16) r.skip(2 * 8) # pre_defined r.skip(2 * 8) # reserved self.sample_rate = r.bits(32) >> 16 except BitReaderError as e: raise ASEntryError(e) assert r.is_aligned() try: extra = Atom(fileobj) except AtomError as e: raise ASEntryError(e) self.codec = atom.name.decode("latin-1") self.codec_description = None if atom.name == b"mp4a" and extra.name == b"esds": self._parse_esds(extra, fileobj) elif atom.name == b"alac" and extra.name == b"alac": self._parse_alac(extra, fileobj) elif atom.name == b"ac-3" and extra.name == b"dac3": self._parse_dac3(extra, fileobj) if self.codec_description is None: self.codec_description = self.codec.upper()
def _parse_alac(self, atom, fileobj): # https://alac.macosforge.org/trac/browser/trunk/ # ALACMagicCookieDescription.txt assert atom.name == b"alac" ok, data = atom.read(fileobj) if not ok: raise ASEntryError("truncated %s atom" % atom.name) try: version, flags, data = parse_full_atom(data) except ValueError as e: raise ASEntryError(e) if version != 0: raise ASEntryError("Unsupported version %d" % version) fileobj = cBytesIO(data) r = BitReader(fileobj) try: # for some files the AudioSampleEntry values default to 44100/2chan # and the real info is in the alac cookie, so prefer it r.skip(32) # frameLength compatibleVersion = r.bits(8) if compatibleVersion != 0: return self.sample_size = r.bits(8) r.skip(8 + 8 + 8) self.channels = r.bits(8) r.skip(16 + 32) self.bitrate = r.bits(32) self.sample_rate = r.bits(32) except BitReaderError as e: raise ASEntryError(e)
def _parse_alac(self, atom, fileobj): # https://alac.macosforge.org/trac/browser/trunk/ # ALACMagicCookieDescription.txt assert atom.name == b"alac" ok, data = atom.read(fileobj) if not ok: raise ASEntryError("truncated %s atom" % atom.name) try: version, flags, data = parse_full_atom(data) except ValueError as e: raise ASEntryError(e) if version != 0: raise ASEntryError("Unsupported version %d" % version) fileobj = cBytesIO(data) r = BitReader(fileobj) try: # for some files the AudioSampleEntry values default to 44100/2chan # and the real info is in the alac cookie, so prefer it r.skip(32) # frameLength compatibleVersion = r.bits(8) if compatibleVersion != 0: return self.sample_size = r.bits(8) r.skip(8 + 8 + 8) self.channels = r.bits(8) r.skip(16 + 32) self.bitrate = r.bits(32) self.sample_rate = r.bits(32) except BitReaderError as e: raise ASEntryError(e)
def test_align(self): r = BitReader(cBytesIO(b"\xAB\xCD\xEF")) r.skip(3) self.assertEqual(r.align(), 5) self.assertEqual(r.get_position(), 8)
def test_bytes_unaligned(self): r = BitReader(cBytesIO(b"\xAB\xCD\xEF")) r.skip(4) self.assertEqual(r.bytes(2), b"\xBC\xDE")
def test_skip_too_much(self): r = BitReader(cBytesIO(b"\xAB\xCD")) # aligned skips don't fail, but the following read will r.skip(32 + 8) self.assertRaises(BitReaderError, r.bits, 1) self.assertRaises(BitReaderError, r.skip, 1)
def test_skip_more(self): r = BitReader(cBytesIO(b"\xAB\xCD")) self.assertEqual(r.bits(4), 0xa) r.skip(8) self.assertEqual(r.bits(4), 0xd) self.assertRaises(BitReaderError, r.bits, 1)
def test_skip_more(self): r = BitReader(BytesIO(b"\xAB\xCD")) self.assertEqual(r.bits(4), 0xa) r.skip(8) self.assertEqual(r.bits(4), 0xd) self.assertRaises(BitReaderError, r.bits, 1)
def __init__(self, fileobj): """Raises HeaderNotFoundError""" self.frame_offset = fileobj.tell() r = BitReader(fileobj) try: if r.bits(11) != 0x7ff: raise HeaderNotFoundError("invalid sync") version = r.bits(2) layer = r.bits(2) protection = r.bits(1) bitrate = r.bits(4) sample_rate = r.bits(2) padding = r.bits(1) r.skip(1) # private self.mode = r.bits(2) r.skip(6) except BitReaderError: raise HeaderNotFoundError("truncated header") assert r.get_position() == 32 and r.is_aligned() # try to be strict here to redice the chance of a false positive if version == 1 or layer == 0 or sample_rate == 0x3 or \ bitrate == 0xf or bitrate == 0: raise HeaderNotFoundError("invalid header") self.channels = 1 if self.mode == MONO else 2 self.version = [2.5, None, 2, 1][version] self.layer = 4 - layer self.protected = not protection self.padding = bool(padding) self.bitrate = self.__BITRATE[(self.version, self.layer)][bitrate] self.bitrate *= 1000 self.sample_rate = self.__RATES[self.version][sample_rate] if self.layer == 1: frame_size = 384 slot = 4 elif self.version >= 2 and self.layer == 3: frame_size = 576 slot = 1 else: frame_size = 1152 slot = 1 frame_length = ( ((frame_size // 8 * self.bitrate) // self.sample_rate) + padding) * slot self.sketchy = True # Try to find/parse the Xing header, which trumps the above length # and bitrate calculation. if self.layer == 3: self._parse_vbr_header(fileobj, self.frame_offset, frame_size, frame_length) fileobj.seek(self.frame_offset + frame_length, 0)
def test_bytes_unaligned(self): r = BitReader(BytesIO(b"\xAB\xCD\xEF")) r.skip(4) self.assertEqual(r.bytes(2), b"\xBC\xDE")
def test_skip_too_much(self): r = BitReader(BytesIO(b"\xAB\xCD")) # aligned skips don't fail, but the following read will r.skip(32 + 8) self.assertRaises(BitReaderError, r.bits, 1) self.assertRaises(BitReaderError, r.skip, 1)
def __init__(self, xing, fileobj): """Raises LAMEError if parsing fails""" payload = fileobj.read(27) if len(payload) != 27: raise LAMEError("Not enough data") # extended lame header r = BitReader(cBytesIO(payload)) revision = r.bits(4) if revision != 0: raise LAMEError("unsupported header revision %d" % revision) self.vbr_method = r.bits(4) self.lowpass_filter = r.bits(8) * 100 # these have a different meaning for lame; expose them again here self.quality = (100 - xing.vbr_scale) % 10 self.vbr_quality = (100 - xing.vbr_scale) // 10 track_peak_data = r.bytes(4) if track_peak_data == b"\x00\x00\x00\x00": self.track_peak = None else: # see PutLameVBR() in LAME's VbrTag.c self.track_peak = ( cdata.uint32_be(track_peak_data) - 0.5) / 2 ** 23 track_gain_type = r.bits(3) self.track_gain_origin = r.bits(3) sign = r.bits(1) gain_adj = r.bits(9) / 10.0 if sign: gain_adj *= -1 if track_gain_type == 1: self.track_gain_adjustment = gain_adj else: self.track_gain_adjustment = None assert r.is_aligned() album_gain_type = r.bits(3) self.album_gain_origin = r.bits(3) sign = r.bits(1) album_gain_adj = r.bits(9) / 10.0 if album_gain_type == 2: self.album_gain_adjustment = album_gain_adj else: self.album_gain_adjustment = None self.encoding_flags = r.bits(4) self.ath_type = r.bits(4) self.bitrate = r.bits(8) self.encoder_delay_start = r.bits(12) self.encoder_padding_end = r.bits(12) self.source_sample_frequency_enum = r.bits(2) self.unwise_setting_used = r.bits(1) self.stereo_mode = r.bits(3) self.noise_shaping = r.bits(2) sign = r.bits(1) mp3_gain = r.bits(7) if sign: mp3_gain *= -1 self.mp3_gain = mp3_gain r.skip(2) self.surround_info = r.bits(3) self.preset_used = r.bits(11) self.music_length = r.bits(32) self.music_crc = r.bits(16) self.header_crc = r.bits(16) assert r.is_aligned()
def test_skip(self): r = BitReader(cBytesIO(b"\xEF")) r.skip(4) self.assertEqual(r.bits(4), 0xf)
def test_align(self): r = BitReader(BytesIO(b"\xAB\xCD\xEF")) r.skip(3) self.assertEqual(r.align(), 5) self.assertEqual(r.get_position(), 8)
def test_skip(self): r = BitReader(BytesIO(b"\xEF")) r.skip(4) self.assertEqual(r.bits(4), 0xf)