Exemplo n.º 1
0
    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)]
Exemplo n.º 2
0
    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)
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
    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
Exemplo n.º 5
0
    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
Exemplo n.º 6
0
    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)
Exemplo n.º 7
0
    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)