def __init__(self, fileobj): """Reads the VBRI header or raises VBRIHeaderError. The file position is undefined after this returns """ data = fileobj.read(26) if len(data) != 26 or not data.startswith(b"VBRI"): raise VBRIHeaderError("Not a VBRI header") offset = 4 self.version, offset = cdata.uint16_be_from(data, offset) if self.version != 1: raise VBRIHeaderError( "Unsupported header version: %r" % self.version) offset += 2 # float16.. can't do self.quality, offset = cdata.uint16_be_from(data, offset) self.bytes, offset = cdata.uint32_be_from(data, offset) self.frames, offset = cdata.uint32_be_from(data, offset) toc_num_entries, offset = cdata.uint16_be_from(data, offset) self.toc_scale_factor, offset = cdata.uint16_be_from(data, offset) toc_entry_size, offset = cdata.uint16_be_from(data, offset) self.toc_frames, offset = cdata.uint16_be_from(data, offset) toc_size = toc_entry_size * toc_num_entries toc_data = fileobj.read(toc_size) if len(toc_data) != toc_size: raise VBRIHeaderError("VBRI header truncated") self.toc = [] if toc_entry_size == 2: unpack = partial(cdata.uint16_be_from, toc_data) elif toc_entry_size == 4: unpack = partial(cdata.uint32_be_from, toc_data) else: raise VBRIHeaderError("Invalid TOC entry size") self.toc = [unpack(i)[0] for i in xrange(0, toc_size, toc_entry_size)]
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 ValueError("truncated %s atom" % atom.name) version, flags, data = _parse_full_atom(data) if version != 0: # unsupported version, ignore return # for some files the AudioSampleEntry values default to 44100/2chan # and the real info is in the alac cookie, so prefer it try: self.channels, off = cdata.uint8_from(data, 9) off += 6 # skip some stuff self.bitrate, off = cdata.uint32_be_from(data, off) self.sample_rate, off = cdata.uint32_be_from(data, off) except cdata.error as e: raise ValueError(e)
def _parse_stsd(self, atom, fileobj): """Sets channels, bits_per_sample, sample_rate and optionally bitrate. Can raise MP4StreamInfoError. """ assert atom.name == b"stsd" ok, data = atom.read(fileobj) if not ok: raise MP4StreamInfoError("Invalid stsd") try: version, flags, data = parse_full_atom(data) except ValueError as e: raise MP4StreamInfoError(e) if version != 0: raise MP4StreamInfoError("Unsupported stsd version") try: num_entries, offset = cdata.uint32_be_from(data, 0) except cdata.error as e: raise MP4StreamInfoError(e) if num_entries == 0: return # look at the first entry if there is one entry_fileobj = cBytesIO(data[offset:]) try: entry_atom = Atom(entry_fileobj) except AtomError as e: raise MP4StreamInfoError(e) try: entry = AudioSampleEntry(entry_atom, entry_fileobj) except ASEntryError as e: raise MP4StreamInfoError(e) else: self.channels = entry.channels self.bits_per_sample = entry.sample_size self.sample_rate = entry.sample_rate self.bitrate = entry.bitrate self.codec = entry.codec self.codec_description = entry.codec_description
def __init__(self, fileobj): """Parses the Xing header or raises XingHeaderError. The file position after this returns is undefined. """ data = fileobj.read(8) if len(data) != 8 or data[:4] not in (b"Xing", b"Info"): raise XingHeaderError("Not a Xing header") self.is_info = (data[:4] == b"Info") flags = cdata.uint32_be_from(data, 4)[0] if flags & XingHeaderFlags.FRAMES: data = fileobj.read(4) if len(data) != 4: raise XingHeaderError("Xing header truncated") self.frames = cdata.uint32_be(data) if flags & XingHeaderFlags.BYTES: data = fileobj.read(4) if len(data) != 4: raise XingHeaderError("Xing header truncated") self.bytes = cdata.uint32_be(data) if flags & XingHeaderFlags.TOC: data = fileobj.read(100) if len(data) != 100: raise XingHeaderError("Xing header truncated") self.toc = list(bytearray(data)) if flags & XingHeaderFlags.VBR_SCALE: data = fileobj.read(4) if len(data) != 4: raise XingHeaderError("Xing header truncated") self.vbr_scale = cdata.uint32_be(data) try: self.lame_version, self.lame_version_desc, has_header = \ LAMEHeader.parse_version(fileobj) if has_header: self.lame_header = LAMEHeader(self, fileobj) except LAMEError: pass
def _parse_esds(self, esds, fileobj): assert esds.name == b"esds" ok, data = esds.read(fileobj) if not ok: raise ValueError("truncated %s atom" % esds.name) version, flags, data = _parse_full_atom(data) if version != 0: # unsupported version, ignore return try: tag, off = cdata.uint8_from(data, 0) ES_DescrTag = 0x03 if tag != ES_DescrTag: raise ValueError("unexpected descriptor: %d" % tag) base_size, off = _parse_desc_length(data, off) es_id, off = cdata.uint16_be_from(data, off) es_flags, off = cdata.uint8_from(data, off) streamDependenceFlag = cdata.test_bit(es_flags, 7) URL_Flag = cdata.test_bit(es_flags, 6) OCRstreamFlag = cdata.test_bit(es_flags, 5) # streamPriority = es_flags & 0x1f if streamDependenceFlag: off += 2 # dependsOn_ES_ID if URL_Flag: url_len, off = cdata.uint8_from(data, off) off += url_len # URLstring if OCRstreamFlag: off += 2 # OCR_ES_Id DecoderConfigDescrTag = 4 tag, off = cdata.uint8_from(data, off) if tag != DecoderConfigDescrTag: raise ValueError("unexpected DecoderConfigDescrTag %d" % tag) dec_conf_size, off = _parse_desc_length(data, off) off += 9 # skip some stuff # average bitrate self.bitrate, off = cdata.uint32_be_from(data, off) except cdata.error as e: raise ValueError(e)
def __init__(self, atom, fileobj): if atom.name not in (b"mp4a", b"alac"): raise ValueError("Unsupported coding name %s" % atom.name) ok, data = atom.read(fileobj) if not ok or len(data) < 28: raise ValueError("too short %s atom" % atom.name) # SampleEntry off = 6 # reserved off += 2 # data_ref_index # AudioSampleEntry off += 8 # reserved self.channels, off = cdata.uint16_be_from(data, off) self.sample_size, off = cdata.uint16_be_from(data, off) off += 2 # pre_defined off += 2 # reserved sample_rate, off = cdata.uint32_be_from(data, off) # defined as Q16.16, but the fraction part seems unused.. # self.sample_rate = sample_rate * 2 ** (-16) self.sample_rate = sample_rate >> 16 assert off == 28 fileobj = cBytesIO(data[off:]) try: extra = Atom(fileobj) except MP4MetadataError as e: raise ValueError(e) # esds only in mp4a atoms 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)