示例#1
0
def validate_footer(footer, ssnd_bytes_written):
    """given a footer string as returned by aiff_header_footer()
    and PCM stream parameters, returns True if the footer is valid

    raises ValueError is the footer is invalid"""

    from io import BytesIO
    from audiotools.bitstream import BitstreamReader

    total_size = len(footer)
    aiff_file = BitstreamReader(BytesIO(footer), False)
    try:
        # ensure footer is padded properly if necessary
        # based on size of data bytes written
        if ssnd_bytes_written % 2:
            aiff_file.skip_bytes(1)
            total_size -= 1

        while total_size > 0:
            (chunk_id, chunk_size) = aiff_file.parse("4b 32u")
            if frozenset(chunk_id).issubset(AiffAudio.PRINTABLE_ASCII):
                total_size -= 8
            else:
                from audiotools.text import ERR_AIFF_INVALID_CHUNK
                raise ValueError(ERR_AIFF_INVALID_CHUNK)

            if chunk_id == b"COMM":
                # ensure no COMM chunks are found
                from audiotools.text import ERR_AIFF_MULTIPLE_COMM_CHUNKS
                raise ValueError(ERR_AIFF_MULTIPLE_COMM_CHUNKS)
            elif chunk_id == b"SSND":
                # ensure no SSND chunks are found
                from audiotools.text import ERR_AIFF_MULTIPLE_SSND_CHUNKS
                raise ValueError(ERR_AIFF_MULTIPLE_SSND_CHUNKS)
            else:
                # skip the full contents of non-audio chunks
                if chunk_size % 2:
                    aiff_file.skip_bytes(chunk_size + 1)
                    total_size -= (chunk_size + 1)
                else:
                    aiff_file.skip_bytes(chunk_size)
                    total_size -= chunk_size
        else:
            return True
    except IOError:
        from audiotools.text import ERR_AIFF_FOOTER_IOERROR
        raise ValueError(ERR_AIFF_FOOTER_IOERROR)
示例#2
0
def validate_footer(footer, ssnd_bytes_written):
    """given a footer string as returned by aiff_header_footer()
    and PCM stream parameters, returns True if the footer is valid

    raises ValueError is the footer is invalid"""

    from io import BytesIO
    from audiotools.bitstream import BitstreamReader

    total_size = len(footer)
    aiff_file = BitstreamReader(BytesIO(footer), False)
    try:
        # ensure footer is padded properly if necessary
        # based on size of data bytes written
        if ssnd_bytes_written % 2:
            aiff_file.skip_bytes(1)
            total_size -= 1

        while total_size > 0:
            (chunk_id, chunk_size) = aiff_file.parse("4b 32u")
            if frozenset(chunk_id).issubset(AiffAudio.PRINTABLE_ASCII):
                total_size -= 8
            else:
                from audiotools.text import ERR_AIFF_INVALID_CHUNK
                raise ValueError(ERR_AIFF_INVALID_CHUNK)

            if chunk_id == b"COMM":
                # ensure no COMM chunks are found
                from audiotools.text import ERR_AIFF_MULTIPLE_COMM_CHUNKS
                raise ValueError(ERR_AIFF_MULTIPLE_COMM_CHUNKS)
            elif chunk_id == b"SSND":
                # ensure no SSND chunks are found
                from audiotools.text import ERR_AIFF_MULTIPLE_SSND_CHUNKS
                raise ValueError(ERR_AIFF_MULTIPLE_SSND_CHUNKS)
            else:
                # skip the full contents of non-audio chunks
                if chunk_size % 2:
                    aiff_file.skip_bytes(chunk_size + 1)
                    total_size -= (chunk_size + 1)
                else:
                    aiff_file.skip_bytes(chunk_size)
                    total_size -= chunk_size
        else:
            return True
    except IOError:
        from audiotools.text import ERR_AIFF_FOOTER_IOERROR
        raise ValueError(ERR_AIFF_FOOTER_IOERROR)
示例#3
0
def validate_footer(footer, data_bytes_written):
    """given a footer string as returned by wave_header_footer()
    and PCM stream parameters, returns True if the footer is valid

    raises ValueError if the footer is invalid"""

    from io import BytesIO
    from audiotools.bitstream import BitstreamReader

    total_size = len(footer)
    wave_file = BitstreamReader(BytesIO(footer), True)
    try:
        # ensure footer is padded properly if necessary
        # based on size of data bytes written
        if data_bytes_written % 2:
            wave_file.skip_bytes(1)
            total_size -= 1

        while total_size > 0:
            (chunk_id, chunk_size) = wave_file.parse("4b 32u")
            if not frozenset(chunk_id).issubset(WaveAudio.PRINTABLE_ASCII):
                from audiotools.text import ERR_WAV_INVALID_CHUNK
                raise ValueError(ERR_WAV_INVALID_CHUNK)
            else:
                total_size -= 8

            if chunk_id == b"fmt ":
                # ensure no fmt chunks are found
                from audiotools.text import ERR_WAV_MULTIPLE_FMT
                raise ValueError(ERR_WAV_MULTIPLE_FMT)
            elif chunk_id == b"data":
                # ensure no data chunks are found
                from audiotools.text import ERR_WAV_MULTIPLE_DATA
                raise ValueError(ERR_WAV_MULTIPLE_DATA)
            else:
                # skip the full contents of non-audio chunks
                if chunk_size % 2:
                    wave_file.skip_bytes(chunk_size + 1)
                    total_size -= (chunk_size + 1)
                else:
                    wave_file.skip_bytes(chunk_size)
                    total_size -= chunk_size
        else:
            return True
    except IOError:
        from audiotools.text import ERR_WAV_FOOTER_IOERROR
        raise ValueError(ERR_WAV_FOOTER_IOERROR)
示例#4
0
def validate_header(header):
    """given header string as returned by aiff_header_footer()
    returns (total size, ssnd size)
    where total size is the size of the file in bytes
    and ssnd size is the size of the SSND chunk in bytes
    (including the 8 prefix bytes in the chunk
    but *not* including any padding byte at the end)

    the size of the SSND chunk and of the total file should be validated
    after the file has been completely written
    such that len(header) + len(SSND chunk) + len(footer) = total size

    raises ValueError if the header is invalid"""

    from io import BytesIO
    from audiotools.bitstream import BitstreamReader

    header_size = len(header)
    aiff_file = BitstreamReader(BytesIO(header), False)
    try:
        # ensure header starts with FORM<size>AIFF chunk
        (form, remaining_size, aiff) = aiff_file.parse("4b 32u 4b")
        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 = remaining_size + 8
            header_size -= 12

        comm_found = False

        while header_size > 0:
            # ensure each chunk header is valid
            (chunk_id, chunk_size) = aiff_file.parse("4b 32u")
            if frozenset(chunk_id).issubset(AiffAudio.PRINTABLE_ASCII):
                header_size -= 8
            else:
                from audiotools.text import ERR_AIFF_INVALID_CHUNK
                raise ValueError(ERR_AIFF_INVALID_CHUNK)

            if chunk_id == b"COMM":
                if not comm_found:
                    # skip COMM chunk when found
                    comm_found = True
                    if chunk_size % 2:
                        aiff_file.skip_bytes(chunk_size + 1)
                        header_size -= (chunk_size + 1)
                    else:
                        aiff_file.skip_bytes(chunk_size)
                        header_size -= chunk_size
                else:
                    # ensure only one COMM chunk is found
                    from audiotools.text import ERR_AIFF_MULTIPLE_COMM_CHUNKS
                    raise ValueError(ERR_AIFF_MULTIPLE_COMM_CHUNKS)
            elif chunk_id == b"SSND":
                if not comm_found:
                    # ensure at least one COMM chunk is found
                    from audiotools.text import ERR_AIFF_PREMATURE_SSND_CHUNK
                    raise ValueError(ERR_AIFF_PREMATURE_SSND_CHUNK)
                elif header_size > 8:
                    # ensure exactly 8 bytes remain after SSND chunk header
                    from audiotools.text import ERR_AIFF_HEADER_EXTRA_SSND
                    raise ValueError(ERR_AIFF_HEADER_EXTRA_SSND)
                elif header_size < 8:
                    from audiotools.text import ERR_AIFF_HEADER_MISSING_SSND
                    raise ValueError(ERR_AIFF_HEADER_MISSING_SSND)
                else:
                    return (total_size, chunk_size - 8)
            else:
                # skip the full contents of non-audio chunks
                if chunk_size % 2:
                    aiff_file.skip_bytes(chunk_size + 1)
                    header_size -= (chunk_size + 1)
                else:
                    aiff_file.skip_bytes(chunk_size)
                    header_size -= chunk_size
        else:
            # header parsed with no SSND chunks found
            from audiotools.text import ERR_AIFF_NO_SSND_CHUNK
            raise ValueError(ERR_AIFF_NO_SSND_CHUNK)
    except IOError:
        from audiotools.text import ERR_AIFF_HEADER_IOERROR
        raise ValueError(ERR_AIFF_HEADER_IOERROR)
示例#5
0
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
示例#6
0
    def __parse_info__(self):
        """generates a cache of sample_rate, bits-per-sample, etc"""

        import re
        import os.path
        from audiotools.bitstream import BitstreamReader

        if (len(self.tracks) == 0):
            return

        # Why is this post-init processing necessary?
        # DVDATrack references DVDATitle
        # so a DVDATitle must exist when DVDATrack is initialized.
        # But because reading this info requires knowing the sector
        # of the first track, we wind up with a circular dependency.
        # Doing a "post-process" pass fixes that problem.

        # find the AOB file of the title's first track
        track_sector = self[0].first_sector
        titleset = re.compile("ATS_%2.2d_\\d\\.AOB" % (self.titleset))
        for aob_path in sorted([self.dvdaudio.files[key] for key in
                                self.dvdaudio.files.keys()
                                if (titleset.match(key))]):
            aob_sectors = os.path.getsize(aob_path) // DVDAudio.SECTOR_SIZE
            if (track_sector > aob_sectors):
                track_sector -= aob_sectors
            else:
                break
        else:
            from audiotools.text import ERR_DVDA_NO_TRACK_SECTOR
            raise ValueError(ERR_DVDA_NO_TRACK_SECTOR)

        # open that AOB file and seek to that track's first sector
        aob_file = open(aob_path, 'rb')
        try:
            aob_file.seek(track_sector * DVDAudio.SECTOR_SIZE)
            aob_reader = BitstreamReader(aob_file, 0)

            # read and validate the pack header
            # (there's one pack header per sector, at the sector's start)
            (sync_bytes,
             marker1,
             current_pts_high,
             marker2,
             current_pts_mid,
             marker3,
             current_pts_low,
             marker4,
             scr_extension,
             marker5,
             bit_rate,
             marker6,
             stuffing_length) = aob_reader.parse(
                "32u 2u 3u 1u 15u 1u 15u 1u 9u 1u 22u 2u 5p 3u")
            aob_reader.skip_bytes(stuffing_length)
            if (sync_bytes != 0x1BA):
                from audiotools.text import ERR_DVDA_INVALID_AOB_SYNC
                raise InvalidDVDA(ERR_DVDA_INVALID_AOB_SYNC)
            if (((marker1 != 1) or (marker2 != 1) or (marker3 != 1) or
                 (marker4 != 1) or (marker5 != 1) or (marker6 != 3))):
                from audiotools.text import ERR_DVDA_INVALID_AOB_MARKER
                raise InvalidDVDA(ERR_DVDA_INVALID_AOB_MARKER)
            packet_pts = ((current_pts_high << 30) |
                          (current_pts_mid << 15) |
                          current_pts_low)

            # skip packets until one with a stream ID of 0xBD is found
            (start_code,
             stream_id,
             packet_length) = aob_reader.parse("24u 8u 16u")
            if (start_code != 1):
                from audiotools.text import ERR_DVDA_INVALID_AOB_START
                raise InvalidDVDA(ERR_DVDA_INVALID_AOB_START)
            while (stream_id != 0xBD):
                aob_reader.skip_bytes(packet_length)
                (start_code,
                 stream_id,
                 packet_length) = aob_reader.parse("24u 8u 16u")
                if (start_code != 1):
                    from audiotools.text import ERR_DVDA_INVALID_AOB_START
                    raise InvalidDVDA(ERR_DVDA_INVALID_AOB_START)

            # parse the PCM/MLP header in the packet data
            (pad1_size,) = aob_reader.parse("16p 8u")
            aob_reader.skip_bytes(pad1_size)
            (stream_id, crc) = aob_reader.parse("8u 8u 8p")
            if (stream_id == 0xA0):  # PCM
                # read a PCM reader
                (pad2_size,
                 first_audio_frame,
                 padding2,
                 group1_bps,
                 group2_bps,
                 group1_sample_rate,
                 group2_sample_rate,
                 padding3,
                 channel_assignment) = aob_reader.parse(
                    "8u 16u 8u 4u 4u 4u 4u 8u 8u")
            else:                    # MLP
                aob_reader.skip_bytes(aob_reader.read(8))  # skip pad2
                # read a total frame size + MLP major sync header
                (total_frame_size,
                 sync_words,
                 stream_type,
                 group1_bps,
                 group2_bps,
                 group1_sample_rate,
                 group2_sample_rate,
                 unknown1,
                 channel_assignment,
                 unknown2) = aob_reader.parse(
                    "4p 12u 16p 24u 8u 4u 4u 4u 4u 11u 5u 48u")

            # return the values indicated by the header
            self.sample_rate = DVDATrack.SAMPLE_RATE[group1_sample_rate]
            self.channels = DVDATrack.CHANNELS[channel_assignment]
            self.channel_mask = DVDATrack.CHANNEL_MASK[channel_assignment]
            self.bits_per_sample = DVDATrack.BITS_PER_SAMPLE[group1_bps]
            self.stream_id = stream_id

        finally:
            aob_file.close()
示例#7
0
class ALACDecoder:
    def __init__(self, filename):
        self.reader = BitstreamReader(open(filename, "rb"), 0)

        self.reader.mark()
        try:
            #locate the "alac" atom
            #which is full of required decoding parameters
            try:
                stsd = self.find_sub_atom("moov", "trak", "mdia",
                                          "minf", "stbl", "stsd")
            except KeyError:
                raise ValueError("required stsd atom not found")

            (stsd_version, descriptions) = stsd.parse("8u 24p 32u")
            (alac1,
             alac2,
             self.samples_per_frame,
             self.bits_per_sample,
             self.history_multiplier,
             self.initial_history,
             self.maximum_k,
             self.channels,
             self.sample_rate) = stsd.parse(
                 #ignore much of the stuff in the "high" ALAC atom
                 "32p 4b 6P 16p 16p 16p 4P 16p 16p 16p 16p 4P" +
                 #and use the attributes in the "low" ALAC atom instead
                 "32p 4b 4P 32u 8p 8u 8u 8u 8u 8u 16p 32p 32p 32u")

            self.channel_mask = {1: 0x0004,
                                 2: 0x0003,
                                 3: 0x0007,
                                 4: 0x0107,
                                 5: 0x0037,
                                 6: 0x003F,
                                 7: 0x013F,
                                 8: 0x00FF}.get(self.channels, 0)

            if ((alac1 != 'alac') or (alac2 != 'alac')):
                raise ValueError("Invalid alac atom")

            #also locate the "mdhd" atom
            #which contains the stream's length in PCM frames
            self.reader.rewind()
            mdhd = self.find_sub_atom("moov", "trak", "mdia", "mdhd")
            (version, ) = mdhd.parse("8u 24p")
            if (version == 0):
                (self.total_pcm_frames,) = mdhd.parse(
                    "32p 32p 32p 32u 2P 16p")
            elif (version == 1):
                (self.total_pcm_frames,) = mdhd.parse(
                    "64p 64p 32p 64U 2P 16p")
            else:
                raise ValueError("invalid mdhd version")

            #finally, set our stream to the "mdat" atom
            self.reader.rewind()
            (atom_size, atom_name) = self.reader.parse("32u 4b")
            while (atom_name != "mdat"):
                self.reader.skip_bytes(atom_size - 8)
                (atom_size, atom_name) = self.reader.parse("32u 4b")

        finally:
            self.reader.unmark()

    def find_sub_atom(self, *atom_names):
        reader = self.reader

        for (last, next_atom) in iter_last(iter(atom_names)):
            try:
                (length, stream_atom) = reader.parse("32u 4b")
                while (stream_atom != next_atom):
                    reader.skip_bytes(length - 8)
                    (length, stream_atom) = reader.parse("32u 4b")
                if (last):
                    return reader.substream(length - 8)
                else:
                    reader = reader.substream(length - 8)
            except IOError:
                raise KeyError(next_atom)

    def read(self, pcm_frames):
        #if the stream is exhausted, return an empty pcm.FrameList object
        if (self.total_pcm_frames == 0):
            return from_list([], self.channels, self.bits_per_sample, True)

        #otherwise, read one ALAC frameset's worth of frame data
        frameset_data = []
        frame_channels = self.reader.read(3) + 1
        while (frame_channels != 0x8):
            frameset_data.extend(self.read_frame(frame_channels))
            frame_channels = self.reader.read(3) + 1
        self.reader.byte_align()

        #reorder the frameset to Wave order, depending on channel count
        if ((self.channels == 1) or (self.channels == 2)):
            pass
        elif (self.channels == 3):
            frameset_data = [frameset_data[1],
                             frameset_data[2],
                             frameset_data[0]]
        elif (self.channels == 4):
            frameset_data = [frameset_data[1],
                             frameset_data[2],
                             frameset_data[0],
                             frameset_data[3]]
        elif (self.channels == 5):
            frameset_data = [frameset_data[1],
                             frameset_data[2],
                             frameset_data[0],
                             frameset_data[3],
                             frameset_data[4]]
        elif (self.channels == 6):
            frameset_data = [frameset_data[1],
                             frameset_data[2],
                             frameset_data[0],
                             frameset_data[5],
                             frameset_data[3],
                             frameset_data[4]]
        elif (self.channels == 7):
            frameset_data = [frameset_data[1],
                             frameset_data[2],
                             frameset_data[0],
                             frameset_data[6],
                             frameset_data[3],
                             frameset_data[4],
                             frameset_data[5]]
        elif (self.channels == 8):
            frameset_data = [frameset_data[3],
                             frameset_data[4],
                             frameset_data[0],
                             frameset_data[7],
                             frameset_data[5],
                             frameset_data[6],
                             frameset_data[1],
                             frameset_data[2]]
        else:
            raise ValueError("unsupported channel count")

        framelist = from_channels([from_list(channel,
                                             1,
                                             self.bits_per_sample,
                                             True)
                                   for channel in frameset_data])

        #deduct PCM frames from remainder
        self.total_pcm_frames -= framelist.frames

        #return samples as a pcm.FrameList object
        return framelist

    def read_frame(self, channel_count):
        """returns a list of PCM sample lists, one per channel"""

        #read the ALAC frame header
        self.reader.skip(16)
        has_sample_count = self.reader.read(1)
        uncompressed_lsb_size = self.reader.read(2)
        uncompressed = self.reader.read(1)
        if (has_sample_count):
            sample_count = self.reader.read(32)
        else:
            sample_count = self.samples_per_frame

        if (uncompressed == 1):
            #if the frame is uncompressed,
            #read the raw, interlaced samples
            samples = [self.reader.read_signed(self.bits_per_sample)
                       for i in xrange(sample_count * channel_count)]
            return [samples[i::channel_count] for i in xrange(channel_count)]
        else:
            #if the frame is compressed,
            #read the interlacing parameters
            interlacing_shift = self.reader.read(8)
            interlacing_leftweight = self.reader.read(8)

            #subframe headers
            subframe_headers = [self.read_subframe_header()
                                for i in xrange(channel_count)]

            #optional uncompressed LSB values
            if (uncompressed_lsb_size > 0):
                uncompressed_lsbs = [
                    self.reader.read(uncompressed_lsb_size * 8)
                    for i in xrange(sample_count * channel_count)]
            else:
                uncompressed_lsbs = []

            sample_size = (self.bits_per_sample -
                           (uncompressed_lsb_size * 8) +
                           channel_count - 1)

            #and residual blocks
            residual_blocks = [self.read_residuals(sample_size,
                                                   sample_count)
                               for i in xrange(channel_count)]

            #calculate subframe samples based on
            #subframe header's QLP coefficients and QLP shift-needed
            decoded_subframes = [self.decode_subframe(header[0],
                                                      header[1],
                                                      sample_size,
                                                      residuals)
                                 for (header, residuals) in
                                 zip(subframe_headers,
                                     residual_blocks)]

            #decorrelate channels according interlacing shift and leftweight
            decorrelated_channels = self.decorrelate_channels(
                decoded_subframes,
                interlacing_shift,
                interlacing_leftweight)

            #if uncompressed LSB values are present,
            #prepend them to each sample of each channel
            if (uncompressed_lsb_size > 0):
                channels = []
                for (i, channel) in enumerate(decorrelated_channels):
                    assert(len(channel) ==
                           len(uncompressed_lsbs[i::channel_count]))
                    channels.append([s << (uncompressed_lsb_size * 8) | l
                                     for (s, l) in
                                     zip(channel,
                                         uncompressed_lsbs[i::channel_count])])
                return channels
            else:
                return decorrelated_channels

    def read_subframe_header(self):
        prediction_type = self.reader.read(4)
        qlp_shift_needed = self.reader.read(4)
        rice_modifier = self.reader.read(3)
        qlp_coefficients = [self.reader.read_signed(16)
                            for i in xrange(self.reader.read(5))]

        return (qlp_shift_needed, qlp_coefficients)

    def read_residuals(self, sample_size, sample_count):
        residuals = []
        history = self.initial_history
        sign_modifier = 0
        i = 0

        while (i < sample_count):
            #get an unsigned residual based on "history"
            #and on "sample_size" as a lst resort
            k = min(log2(history / (2 ** 9) + 3), self.maximum_k)

            unsigned = self.read_residual(k, sample_size) + sign_modifier

            #clear out old sign modifier, if any
            sign_modifier = 0

            #change unsigned residual to signed residual
            if (unsigned & 1):
                residuals.append(-((unsigned + 1) / 2))
            else:
                residuals.append(unsigned / 2)

            #update history based on unsigned residual
            if (unsigned <= 0xFFFF):
                history += ((unsigned * self.history_multiplier) -
                            ((history * self.history_multiplier) >> 9))
            else:
                history = 0xFFFF

            #if history gets too small, we may have a block of 0 samples
            #which can be compressed more efficiently
            if ((history < 128) and ((i + 1) < sample_count)):
                zeroes_k = min(7 -
                               log2(history) +
                               ((history + 16) / 64),
                               self.maximum_k)
                zero_residuals = self.read_residual(zeroes_k, 16)
                if (zero_residuals > 0):
                    residuals.extend([0] * zero_residuals)
                    i += zero_residuals

                history = 0

                if (zero_residuals <= 0xFFFF):
                    sign_modifier = 1

            i += 1

        return residuals

    def read_residual(self, k, sample_size):
        msb = self.reader.limited_unary(0, 9)
        if (msb is None):
            return self.reader.read(sample_size)
        elif (k == 0):
            return msb
        else:
            lsb = self.reader.read(k)
            if (lsb > 1):
                return msb * ((1 << k) - 1) + (lsb - 1)
            elif (lsb == 1):
                self.reader.unread(1)
                return msb * ((1 << k) - 1)
            else:
                self.reader.unread(0)
                return msb * ((1 << k) - 1)

    def decode_subframe(self, qlp_shift_needed, qlp_coefficients,
                        sample_size, residuals):
        #first sample is always copied verbatim
        samples = [residuals.pop(0)]

        if (len(qlp_coefficients) < 31):
            #the next "coefficient count" samples
            #are applied as differences to the previous
            for i in xrange(len(qlp_coefficients)):
                samples.append(truncate_bits(samples[-1] + residuals.pop(0),
                                             sample_size))

            #remaining samples are processed much like LPC
            for residual in residuals:
                base_sample = samples[-len(qlp_coefficients) - 1]
                lpc_sum = sum([(s - base_sample) * c for (s, c) in
                               zip(samples[-len(qlp_coefficients):],
                                   reversed(qlp_coefficients))])
                outval = (1 << (qlp_shift_needed - 1)) + lpc_sum
                outval >>= qlp_shift_needed
                samples.append(truncate_bits(outval + residual + base_sample,
                                             sample_size))

                buf = samples[-len(qlp_coefficients) - 2:-1]

                #error value then adjusts the coefficients table
                if (residual > 0):
                    predictor_num = len(qlp_coefficients) - 1

                    while ((predictor_num >= 0) and residual > 0):
                        val = (buf[0] -
                               buf[len(qlp_coefficients) - predictor_num])

                        sign = sign_only(val)

                        qlp_coefficients[predictor_num] -= sign

                        val *= sign

                        residual -= ((val >> qlp_shift_needed) *
                                     (len(qlp_coefficients) - predictor_num))

                        predictor_num -= 1

                elif (residual < 0):
                    #the same as above, but we break if residual goes positive
                    predictor_num = len(qlp_coefficients) - 1

                    while ((predictor_num >= 0) and residual < 0):
                        val = (buf[0] -
                               buf[len(qlp_coefficients) - predictor_num])

                        sign = -sign_only(val)

                        qlp_coefficients[predictor_num] -= sign

                        val *= sign

                        residual -= ((val >> qlp_shift_needed) *
                                     (len(qlp_coefficients) - predictor_num))

                        predictor_num -= 1
        else:
            #residuals are encoded as simple difference values
            for residual in residuals:
                samples.append(truncate_bits(samples[-1] + residual,
                                             sample_size))

        return samples

    def decorrelate_channels(self, channel_data,
                             interlacing_shift, interlacing_leftweight):
        if (len(channel_data) != 2):
            return channel_data
        elif (interlacing_leftweight == 0):
            return channel_data
        else:
            left = []
            right = []
            for (ch1, ch2) in zip(*channel_data):
                right.append(ch1 - ((ch2 * interlacing_leftweight) /
                                    (2 ** interlacing_shift)))
                left.append(ch2 + right[-1])
            return [left, right]

    def close(self):
        pass
示例#8
0
    def __init__(self, filename):
        """filename is a plain string"""

        AudioFile.__init__(self, filename)

        from audiotools.bitstream import parse

        try:
            mp3file = open(filename, "rb")
        except IOError as msg:
            raise InvalidMP3(str(msg))

        try:
            try:
                header_bytes = MP3Audio.__find_next_mp3_frame__(mp3file)
            except IOError:
                from audiotools.text import ERR_MP3_FRAME_NOT_FOUND
                raise InvalidMP3(ERR_MP3_FRAME_NOT_FOUND)

            (frame_sync, mpeg_id, layer, bit_rate, sample_rate, pad,
             channels) = parse("11u 2u 2u 1p 4u 2u 1u 1p 2u 6p", False,
                               mp3file.read(4))

            self.__samplerate__ = self.SAMPLE_RATE[mpeg_id][sample_rate]
            if self.__samplerate__ is None:
                from audiotools.text import ERR_MP3_INVALID_SAMPLE_RATE
                raise InvalidMP3(ERR_MP3_INVALID_SAMPLE_RATE)
            if channels in (0, 1, 2):
                self.__channels__ = 2
            else:
                self.__channels__ = 1

            first_frame = mp3file.read(
                self.frame_length(mpeg_id, layer, bit_rate, sample_rate, pad) -
                4)

            if ((b"Xing" in first_frame) and
                (len(first_frame[first_frame.
                                 index(b"Xing"):first_frame.index(b"Xing") +
                                 160]) == 160)):
                # pull length from Xing header, if present
                self.__pcm_frames__ = (parse(
                    "32p 32p 32u 32p 832p", 0, first_frame[first_frame.index(
                        b"Xing"):first_frame.index(b"Xing") + 160])[0] *
                                       self.PCM_FRAMES_PER_MPEG_FRAME[layer])
            else:
                # otherwise, bounce through file frames
                from audiotools.bitstream import BitstreamReader

                reader = BitstreamReader(mp3file, False)
                self.__pcm_frames__ = 0

                try:
                    (frame_sync, mpeg_id, layer, bit_rate, sample_rate,
                     pad) = reader.parse("11u 2u 2u 1p 4u 2u 1u 9p")

                    while frame_sync == 0x7FF:
                        self.__pcm_frames__ += \
                            self.PCM_FRAMES_PER_MPEG_FRAME[layer]

                        reader.skip_bytes(
                            self.frame_length(mpeg_id, layer, bit_rate,
                                              sample_rate, pad) - 4)

                        (frame_sync, mpeg_id, layer, bit_rate, sample_rate,
                         pad) = reader.parse("11u 2u 2u 1p 4u 2u 1u 9p")
                except IOError:
                    pass
                except ValueError as err:
                    raise InvalidMP3(err)
        finally:
            mp3file.close()
示例#9
0
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
示例#10
0
    def wave_header_footer(self):
        """returns a pair of data strings before and after PCM data

        the first contains all data before the PCM content of the data chunk
        the second containing all data after the data chunk
        for example:

        >>> w = audiotools.open("input.wav")
        >>> (head, tail) = w.wave_header_footer()
        >>> f = open("output.wav", "wb")
        >>> f.write(head)
        >>> audiotools.transfer_framelist_data(w.to_pcm(), f.write)
        >>> f.write(tail)
        >>> f.close()

        should result in "output.wav" being identical to "input.wav"
        """

        from audiotools.bitstream import BitstreamReader
        from audiotools.bitstream import BitstreamRecorder

        head = BitstreamRecorder(1)
        tail = BitstreamRecorder(1)
        current_block = head
        fmt_found = False

        wave_file = BitstreamReader(open(self.filename, 'rb'), 1)
        try:
            # transfer the 12-byte "RIFFsizeWAVE" header to head
            (riff, size, wave) = wave_file.parse("4b 32u 4b")
            if (riff != 'RIFF'):
                from audiotools.text import ERR_WAV_NOT_WAVE
                raise ValueError(ERR_WAV_NOT_WAVE)
            elif (wave != 'WAVE'):
                from audiotools.text import ERR_WAV_INVALID_WAVE
                raise ValueError(ERR_WAV_INVALID_WAVE)
            else:
                current_block.build("4b 32u 4b", (riff, size, wave))
                total_size = size - 4

            while (total_size > 0):
                # transfer each chunk header
                (chunk_id, chunk_size) = wave_file.parse("4b 32u")
                if (not frozenset(chunk_id).issubset(self.PRINTABLE_ASCII)):
                    from audiotools.text import ERR_WAV_INVALID_CHUNK
                    raise ValueError(ERR_WAV_INVALID_CHUNK)
                else:
                    current_block.build("4b 32u", (chunk_id, chunk_size))
                    total_size -= 8

                # and transfer the full content of non-audio chunks
                if (chunk_id != "data"):
                    if (chunk_id == "fmt "):
                        if (not fmt_found):
                            fmt_found = True
                        else:
                            from audiotools.text import ERR_WAV_MULTIPLE_FMT
                            raise ValueError(ERR_WAV_MULTIPLE_FMT)

                    if (chunk_size % 2):
                        current_block.write_bytes(
                            wave_file.read_bytes(chunk_size + 1))
                        total_size -= (chunk_size + 1)
                    else:
                        current_block.write_bytes(
                            wave_file.read_bytes(chunk_size))
                        total_size -= chunk_size
                else:
                    wave_file.skip_bytes(chunk_size)
                    current_block = tail

                    if (chunk_size % 2):
                        current_block.write_bytes(wave_file.read_bytes(1))
                        total_size -= (chunk_size + 1)
                    else:
                        total_size -= chunk_size

            if (fmt_found):
                return (head.data(), tail.data())
            else:
                from audiotools.text import ERR_WAV_NO_FMT_CHUNK
                return ValueError(ERR_WAV_NO_FMT_CHUNK)
        finally:
            wave_file.close()
示例#11
0
    def aiff_header_footer(self):
        """returns (header, footer) tuple of strings
        containing all data before and after the PCM stream

        if self.has_foreign_aiff_chunks() is False,
        may raise ValueError if the file has no header and footer
        for any reason"""

        from audiotools.bitstream import BitstreamReader
        from audiotools.bitstream import BitstreamRecorder
        from audiotools.text import (ERR_AIFF_NOT_AIFF,
                                     ERR_AIFF_INVALID_AIFF,
                                     ERR_AIFF_INVALID_CHUNK_ID)

        head = BitstreamRecorder(0)
        tail = BitstreamRecorder(0)
        current_block = head

        aiff_file = BitstreamReader(open(self.filename, 'rb'), 0)
        try:
            # transfer the 12-byte "RIFFsizeWAVE" header to head
            (form, size, aiff) = aiff_file.parse("4b 32u 4b")
            if (form != 'FORM'):
                raise InvalidAIFF(ERR_AIFF_NOT_AIFF)
            elif (aiff != 'AIFF'):
                raise InvalidAIFF(ERR_AIFF_INVALID_AIFF)
            else:
                current_block.build("4b 32u 4b", (form, size, aiff))
                total_size = size - 4

            while (total_size > 0):
                # transfer each chunk header
                (chunk_id, chunk_size) = aiff_file.parse("4b 32u")
                if (not frozenset(chunk_id).issubset(self.PRINTABLE_ASCII)):
                    raise InvalidAIFF(ERR_AIFF_INVALID_CHUNK_ID)
                else:
                    current_block.build("4b 32u", (chunk_id, chunk_size))
                    total_size -= 8

                # and transfer the full content of non-audio chunks
                if (chunk_id != "SSND"):
                    if (chunk_size % 2):
                        current_block.write_bytes(
                            aiff_file.read_bytes(chunk_size + 1))
                        total_size -= (chunk_size + 1)
                    else:
                        current_block.write_bytes(
                            aiff_file.read_bytes(chunk_size))
                        total_size -= chunk_size
                else:
                    # transfer alignment as part of SSND's chunk header
                    align = aiff_file.parse("32u 32u")
                    current_block.build("32u 32u", align)
                    aiff_file.skip_bytes(chunk_size - 8)
                    current_block = tail

                    if (chunk_size % 2):
                        current_block.write_bytes(aiff_file.read_bytes(1))
                        total_size -= (chunk_size + 1)
                    else:
                        total_size -= chunk_size

            return (head.data(), tail.data())
        finally:
            aiff_file.close()
示例#12
0
    def wave_header_footer(self):
        """returns a pair of data strings before and after PCM data

        the first contains all data before the PCM content of the data chunk
        the second containing all data after the data chunk
        for example:

        >>> w = audiotools.open("input.wav")
        >>> (head, tail) = w.wave_header_footer()
        >>> f = open("output.wav", "wb")
        >>> f.write(head)
        >>> audiotools.transfer_framelist_data(w.to_pcm(), f.write)
        >>> f.write(tail)
        >>> f.close()

        should result in "output.wav" being identical to "input.wav"
        """

        from audiotools.bitstream import BitstreamReader
        from audiotools.bitstream import BitstreamRecorder

        head = BitstreamRecorder(1)
        tail = BitstreamRecorder(1)
        current_block = head
        fmt_found = False

        wave_file = BitstreamReader(open(self.filename, 'rb'), 1)
        try:
            # transfer the 12-byte "RIFFsizeWAVE" header to head
            (riff, size, wave) = wave_file.parse("4b 32u 4b")
            if (riff != 'RIFF'):
                from audiotools.text import ERR_WAV_NOT_WAVE
                raise ValueError(ERR_WAV_NOT_WAVE)
            elif (wave != 'WAVE'):
                from audiotools.text import ERR_WAV_INVALID_WAVE
                raise ValueError(ERR_WAV_INVALID_WAVE)
            else:
                current_block.build("4b 32u 4b", (riff, size, wave))
                total_size = size - 4

            while (total_size > 0):
                # transfer each chunk header
                (chunk_id, chunk_size) = wave_file.parse("4b 32u")
                if (not frozenset(chunk_id).issubset(self.PRINTABLE_ASCII)):
                    from audiotools.text import ERR_WAV_INVALID_CHUNK
                    raise ValueError(ERR_WAV_INVALID_CHUNK)
                else:
                    current_block.build("4b 32u", (chunk_id, chunk_size))
                    total_size -= 8

                # and transfer the full content of non-audio chunks
                if (chunk_id != "data"):
                    if (chunk_id == "fmt "):
                        if (not fmt_found):
                            fmt_found = True
                        else:
                            from audiotools.text import ERR_WAV_MULTIPLE_FMT
                            raise ValueError(ERR_WAV_MULTIPLE_FMT)

                    if (chunk_size % 2):
                        current_block.write_bytes(
                            wave_file.read_bytes(chunk_size + 1))
                        total_size -= (chunk_size + 1)
                    else:
                        current_block.write_bytes(
                            wave_file.read_bytes(chunk_size))
                        total_size -= chunk_size
                else:
                    wave_file.skip_bytes(chunk_size)
                    current_block = tail

                    if (chunk_size % 2):
                        current_block.write_bytes(wave_file.read_bytes(1))
                        total_size -= (chunk_size + 1)
                    else:
                        total_size -= chunk_size

            if (fmt_found):
                return (head.data(), tail.data())
            else:
                from audiotools.text import ERR_WAV_NO_FMT_CHUNK
                return ValueError(ERR_WAV_NO_FMT_CHUNK)
        finally:
            wave_file.close()
示例#13
0
    def __init__(self, filename):
        """filename is a plain string"""

        AudioFile.__init__(self, filename)

        from audiotools.bitstream import parse

        try:
            mp3file = open(filename, "rb")
        except IOError as msg:
            raise InvalidMP3(str(msg))

        try:
            try:
                header_bytes = MP3Audio.__find_next_mp3_frame__(mp3file)
            except IOError:
                from audiotools.text import ERR_MP3_FRAME_NOT_FOUND
                raise InvalidMP3(ERR_MP3_FRAME_NOT_FOUND)

            (frame_sync,
             mpeg_id,
             layer,
             bit_rate,
             sample_rate,
             pad,
             channels) = parse("11u 2u 2u 1p 4u 2u 1u 1p 2u 6p",
                               False,
                               mp3file.read(4))

            self.__samplerate__ = self.SAMPLE_RATE[mpeg_id][sample_rate]
            if self.__samplerate__ is None:
                from audiotools.text import ERR_MP3_INVALID_SAMPLE_RATE
                raise InvalidMP3(ERR_MP3_INVALID_SAMPLE_RATE)
            if channels in (0, 1, 2):
                self.__channels__ = 2
            else:
                self.__channels__ = 1

            first_frame = mp3file.read(self.frame_length(mpeg_id,
                                                         layer,
                                                         bit_rate,
                                                         sample_rate,
                                                         pad) - 4)

            if ((b"Xing" in first_frame) and
                (len(first_frame[first_frame.index(b"Xing"):
                                 first_frame.index(b"Xing") + 160]) == 160)):
                # pull length from Xing header, if present
                self.__pcm_frames__ = (
                    parse("32p 32p 32u 32p 832p",
                          0,
                          first_frame[first_frame.index(b"Xing"):
                                      first_frame.index(b"Xing") + 160])[0] *
                    self.PCM_FRAMES_PER_MPEG_FRAME[layer])
            else:
                # otherwise, bounce through file frames
                from audiotools.bitstream import BitstreamReader

                reader = BitstreamReader(mp3file, False)
                self.__pcm_frames__ = 0

                try:
                    (frame_sync,
                     mpeg_id,
                     layer,
                     bit_rate,
                     sample_rate,
                     pad) = reader.parse("11u 2u 2u 1p 4u 2u 1u 9p")

                    while frame_sync == 0x7FF:
                        self.__pcm_frames__ += \
                            self.PCM_FRAMES_PER_MPEG_FRAME[layer]

                        reader.skip_bytes(self.frame_length(mpeg_id,
                                                            layer,
                                                            bit_rate,
                                                            sample_rate,
                                                            pad) - 4)

                        (frame_sync,
                         mpeg_id,
                         layer,
                         bit_rate,
                         sample_rate,
                         pad) = reader.parse("11u 2u 2u 1p 4u 2u 1u 9p")
                except IOError:
                    pass
                except ValueError as err:
                    raise InvalidMP3(err)
        finally:
            mp3file.close()
示例#14
0
    def __parse_info__(self):
        """generates a cache of sample_rate, bits-per-sample, etc"""

        import re
        import os.path
        from audiotools.bitstream import BitstreamReader

        if (len(self.tracks) == 0):
            return

        # Why is this post-init processing necessary?
        # DVDATrack references DVDATitle
        # so a DVDATitle must exist when DVDATrack is initialized.
        # But because reading this info requires knowing the sector
        # of the first track, we wind up with a circular dependency.
        # Doing a "post-process" pass fixes that problem.

        # find the AOB file of the title's first track
        track_sector = self[0].first_sector
        titleset = re.compile("ATS_%2.2d_\\d\\.AOB" % (self.titleset))
        for aob_path in sorted([
                self.dvdaudio.files[key] for key in self.dvdaudio.files.keys()
                if (titleset.match(key))
        ]):
            aob_sectors = os.path.getsize(aob_path) // DVDAudio.SECTOR_SIZE
            if (track_sector > aob_sectors):
                track_sector -= aob_sectors
            else:
                break
        else:
            from audiotools.text import ERR_DVDA_NO_TRACK_SECTOR
            raise ValueError(ERR_DVDA_NO_TRACK_SECTOR)

        # open that AOB file and seek to that track's first sector
        aob_file = open(aob_path, 'rb')
        try:
            aob_file.seek(track_sector * DVDAudio.SECTOR_SIZE)
            aob_reader = BitstreamReader(aob_file, 0)

            # read and validate the pack header
            # (there's one pack header per sector, at the sector's start)
            (sync_bytes, marker1, current_pts_high, marker2, current_pts_mid,
             marker3, current_pts_low, marker4, scr_extension, marker5,
             bit_rate, marker6, stuffing_length) = aob_reader.parse(
                 "32u 2u 3u 1u 15u 1u 15u 1u 9u 1u 22u 2u 5p 3u")
            aob_reader.skip_bytes(stuffing_length)
            if (sync_bytes != 0x1BA):
                from audiotools.text import ERR_DVDA_INVALID_AOB_SYNC
                raise InvalidDVDA(ERR_DVDA_INVALID_AOB_SYNC)
            if (((marker1 != 1) or (marker2 != 1) or (marker3 != 1)
                 or (marker4 != 1) or (marker5 != 1) or (marker6 != 3))):
                from audiotools.text import ERR_DVDA_INVALID_AOB_MARKER
                raise InvalidDVDA(ERR_DVDA_INVALID_AOB_MARKER)
            packet_pts = ((current_pts_high << 30) | (current_pts_mid << 15)
                          | current_pts_low)

            # skip packets until one with a stream ID of 0xBD is found
            (start_code, stream_id,
             packet_length) = aob_reader.parse("24u 8u 16u")
            if (start_code != 1):
                from audiotools.text import ERR_DVDA_INVALID_AOB_START
                raise InvalidDVDA(ERR_DVDA_INVALID_AOB_START)
            while (stream_id != 0xBD):
                aob_reader.skip_bytes(packet_length)
                (start_code, stream_id,
                 packet_length) = aob_reader.parse("24u 8u 16u")
                if (start_code != 1):
                    from audiotools.text import ERR_DVDA_INVALID_AOB_START
                    raise InvalidDVDA(ERR_DVDA_INVALID_AOB_START)

            # parse the PCM/MLP header in the packet data
            (pad1_size, ) = aob_reader.parse("16p 8u")
            aob_reader.skip_bytes(pad1_size)
            (stream_id, crc) = aob_reader.parse("8u 8u 8p")
            if (stream_id == 0xA0):  # PCM
                # read a PCM reader
                (pad2_size, first_audio_frame, padding2, group1_bps,
                 group2_bps, group1_sample_rate, group2_sample_rate, padding3,
                 channel_assignment
                 ) = aob_reader.parse("8u 16u 8u 4u 4u 4u 4u 8u 8u")
            else:  # MLP
                aob_reader.skip_bytes(aob_reader.read(8))  # skip pad2
                # read a total frame size + MLP major sync header
                (total_frame_size, sync_words, stream_type, group1_bps,
                 group2_bps, group1_sample_rate, group2_sample_rate, unknown1,
                 channel_assignment, unknown2) = aob_reader.parse(
                     "4p 12u 16p 24u 8u 4u 4u 4u 4u 11u 5u 48u")

            # return the values indicated by the header
            self.sample_rate = DVDATrack.SAMPLE_RATE[group1_sample_rate]
            self.channels = DVDATrack.CHANNELS[channel_assignment]
            self.channel_mask = DVDATrack.CHANNEL_MASK[channel_assignment]
            self.bits_per_sample = DVDATrack.BITS_PER_SAMPLE[group1_bps]
            self.stream_id = stream_id

        finally:
            aob_file.close()
示例#15
0
def validate_header(header):
    """given header string as returned by wave_header_footer(),
    returns (total size, data size)
    where total size is the size of the file in bytes
    and data size is the size of the data chunk in bytes
    (not including any padding byte)

    the size of the data chunk and of the total file should be validated
    after the file has been completely written
    such that len(header) + len(data chunk) + len(footer) = total size

    raises ValueError if the header is invalid
    """

    from io import BytesIO
    from audiotools.bitstream import BitstreamReader

    header_size = len(header)
    wave_file = BitstreamReader(BytesIO(header), True)
    try:
        # ensure header starts with RIFF<size>WAVE chunk
        (riff, remaining_size, wave) = wave_file.parse("4b 32u 4b")
        if riff != b"RIFF":
            from audiotools.text import ERR_WAV_NOT_WAVE
            raise ValueError(ERR_WAV_NOT_WAVE)
        elif wave != b"WAVE":
            from audiotools.text import ERR_WAV_INVALID_WAVE
            raise ValueError(ERR_WAV_INVALID_WAVE)
        else:
            total_size = remaining_size + 8
            header_size -= 12

        fmt_found = False

        while header_size > 0:
            # ensure each chunk header is valid
            (chunk_id, chunk_size) = wave_file.parse("4b 32u")
            if not frozenset(chunk_id).issubset(WaveAudio.PRINTABLE_ASCII):
                from audiotools.text import ERR_WAV_INVALID_CHUNK
                raise ValueError(ERR_WAV_INVALID_CHUNK)
            else:
                header_size -= 8

            if chunk_id == b"fmt ":
                if not fmt_found:
                    # skip fmt chunk data when found
                    fmt_found = True
                    if chunk_size % 2:
                        wave_file.skip_bytes(chunk_size + 1)
                        header_size -= (chunk_size + 1)
                    else:
                        wave_file.skip_bytes(chunk_size)
                        header_size -= chunk_size
                else:
                    # ensure only one fmt chunk is found
                    from audiotools.text import ERR_WAV_MULTIPLE_FMT
                    raise ValueError(ERR_WAV_MULTIPLE_FMT)
            elif chunk_id == b"data":
                if not fmt_found:
                    # ensure at least one fmt chunk is found
                    from audiotools.text import ERR_WAV_PREMATURE_DATA
                    raise ValueError(ERR_WAV_PREMATURE_DATA)
                elif header_size > 0:
                    # ensure no data remains after data chunk header
                    from audiotools.text import ERR_WAV_HEADER_EXTRA_DATA
                    raise ValueError(
                        ERR_WAV_HEADER_EXTRA_DATA.format(header_size))
                else:
                    return (total_size, chunk_size)
            else:
                # skip the full contents of non-audio chunks
                if chunk_size % 2:
                    wave_file.skip_bytes(chunk_size + 1)
                    header_size -= (chunk_size + 1)
                else:
                    wave_file.skip_bytes(chunk_size)
                    header_size -= chunk_size
        else:
            # header parsed with no data chunks found
            from audiotools.text import ERR_WAV_NO_DATA_CHUNK
            raise ValueError(ERR_WAV_NO_DATA_CHUNK)
    except IOError:
        from audiotools.text import ERR_WAV_HEADER_IOERROR
        raise ValueError(ERR_WAV_HEADER_IOERROR)
示例#16
0
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
示例#17
0
def validate_header(header):
    """given header string as returned by aiff_header_footer()
    returns (total size, ssnd size)
    where total size is the size of the file in bytes
    and ssnd size is the size of the SSND chunk in bytes
    (including the 8 prefix bytes in the chunk
    but *not* including any padding byte at the end)

    the size of the SSND chunk and of the total file should be validated
    after the file has been completely written
    such that len(header) + len(SSND chunk) + len(footer) = total size

    raises ValueError if the header is invalid"""

    from io import BytesIO
    from audiotools.bitstream import BitstreamReader

    header_size = len(header)
    aiff_file = BitstreamReader(BytesIO(header), False)
    try:
        # ensure header starts with FORM<size>AIFF chunk
        (form, remaining_size, aiff) = aiff_file.parse("4b 32u 4b")
        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 = remaining_size + 8
            header_size -= 12

        comm_found = False

        while header_size > 0:
            # ensure each chunk header is valid
            (chunk_id, chunk_size) = aiff_file.parse("4b 32u")
            if frozenset(chunk_id).issubset(AiffAudio.PRINTABLE_ASCII):
                header_size -= 8
            else:
                from audiotools.text import ERR_AIFF_INVALID_CHUNK
                raise ValueError(ERR_AIFF_INVALID_CHUNK)

            if chunk_id == b"COMM":
                if not comm_found:
                    # skip COMM chunk when found
                    comm_found = True
                    if chunk_size % 2:
                        aiff_file.skip_bytes(chunk_size + 1)
                        header_size -= (chunk_size + 1)
                    else:
                        aiff_file.skip_bytes(chunk_size)
                        header_size -= chunk_size
                else:
                    # ensure only one COMM chunk is found
                    from audiotools.text import ERR_AIFF_MULTIPLE_COMM_CHUNKS
                    raise ValueError(ERR_AIFF_MULTIPLE_COMM_CHUNKS)
            elif chunk_id == b"SSND":
                if not comm_found:
                    # ensure at least one COMM chunk is found
                    from audiotools.text import ERR_AIFF_PREMATURE_SSND_CHUNK
                    raise ValueError(ERR_AIFF_PREMATURE_SSND_CHUNK)
                elif header_size > 8:
                    # ensure exactly 8 bytes remain after SSND chunk header
                    from audiotools.text import ERR_AIFF_HEADER_EXTRA_SSND
                    raise ValueError(ERR_AIFF_HEADER_EXTRA_SSND)
                elif header_size < 8:
                    from audiotools.text import ERR_AIFF_HEADER_MISSING_SSND
                    raise ValueError(ERR_AIFF_HEADER_MISSING_SSND)
                else:
                    return (total_size, chunk_size - 8)
            else:
                # skip the full contents of non-audio chunks
                if chunk_size % 2:
                    aiff_file.skip_bytes(chunk_size + 1)
                    header_size -= (chunk_size + 1)
                else:
                    aiff_file.skip_bytes(chunk_size)
                    header_size -= chunk_size
        else:
            # header parsed with no SSND chunks found
            from audiotools.text import ERR_AIFF_NO_SSND_CHUNK
            raise ValueError(ERR_AIFF_NO_SSND_CHUNK)
    except IOError:
        from audiotools.text import ERR_AIFF_HEADER_IOERROR
        raise ValueError(ERR_AIFF_HEADER_IOERROR)
示例#18
0
class ALACDecoder(object):
    def __init__(self, filename):
        self.reader = BitstreamReader(open(filename, "rb"), False)

        self.reader.mark()
        try:
            # locate the "alac" atom
            # which is full of required decoding parameters
            try:
                stsd = self.find_sub_atom(b"moov", b"trak", b"mdia", b"minf",
                                          b"stbl", b"stsd")
            except KeyError:
                raise ValueError("required stsd atom not found")

            (stsd_version, descriptions) = stsd.parse("8u 24p 32u")
            (alac1, alac2, self.samples_per_frame, self.bits_per_sample,
             self.history_multiplier, self.initial_history, self.maximum_k,
             self.channels, self.sample_rate) = stsd.parse(
                 # ignore much of the stuff in the "high" ALAC atom
                 "32p 4b 6P 16p 16p 16p 4P 16p 16p 16p 16p 4P" +
                 # and use the attributes in the "low" ALAC atom instead
                 "32p 4b 4P 32u 8p 8u 8u 8u 8u 8u 16p 32p 32p 32u")

            self.channel_mask = {
                1: 0x0004,
                2: 0x0003,
                3: 0x0007,
                4: 0x0107,
                5: 0x0037,
                6: 0x003F,
                7: 0x013F,
                8: 0x00FF
            }.get(self.channels, 0)

            if ((alac1 != b'alac') or (alac2 != b'alac')):
                raise ValueError("Invalid alac atom")

            # also locate the "mdhd" atom
            # which contains the stream's length in PCM frames
            self.reader.rewind()
            mdhd = self.find_sub_atom(b"moov", b"trak", b"mdia", b"mdhd")
            (version, ) = mdhd.parse("8u 24p")
            if (version == 0):
                (self.total_pcm_frames,
                 ) = mdhd.parse("32p 32p 32p 32u 2P 16p")
            elif (version == 1):
                (self.total_pcm_frames,
                 ) = mdhd.parse("64p 64p 32p 64U 2P 16p")
            else:
                raise ValueError("invalid mdhd version")

            # finally, set our stream to the "mdat" atom
            self.reader.rewind()
            (atom_size, atom_name) = self.reader.parse("32u 4b")
            while (atom_name != b"mdat"):
                self.reader.skip_bytes(atom_size - 8)
                (atom_size, atom_name) = self.reader.parse("32u 4b")

        finally:
            self.reader.unmark()

    def find_sub_atom(self, *atom_names):
        reader = self.reader

        for (last, next_atom) in iter_last(iter(atom_names)):
            try:
                (length, stream_atom) = reader.parse("32u 4b")
                while (stream_atom != next_atom):
                    reader.skip_bytes(length - 8)
                    (length, stream_atom) = reader.parse("32u 4b")
                if (last):
                    return reader.substream(length - 8)
                else:
                    reader = reader.substream(length - 8)
            except IOError:
                raise KeyError(next_atom)

    def read(self, pcm_frames):
        # if the stream is exhausted, return an empty pcm.FrameList object
        if (self.total_pcm_frames == 0):
            return empty_framelist(self.channels, self.bits_per_sample)

        # otherwise, read one ALAC frameset's worth of frame data
        frameset_data = []
        frame_channels = self.reader.read(3) + 1
        while (frame_channels != 0x8):
            frameset_data.extend(self.read_frame(frame_channels))
            frame_channels = self.reader.read(3) + 1
        self.reader.byte_align()

        # reorder the frameset to Wave order, depending on channel count
        if ((self.channels == 1) or (self.channels == 2)):
            pass
        elif (self.channels == 3):
            frameset_data = [
                frameset_data[1], frameset_data[2], frameset_data[0]
            ]
        elif (self.channels == 4):
            frameset_data = [
                frameset_data[1], frameset_data[2], frameset_data[0],
                frameset_data[3]
            ]
        elif (self.channels == 5):
            frameset_data = [
                frameset_data[1], frameset_data[2], frameset_data[0],
                frameset_data[3], frameset_data[4]
            ]
        elif (self.channels == 6):
            frameset_data = [
                frameset_data[1], frameset_data[2], frameset_data[0],
                frameset_data[5], frameset_data[3], frameset_data[4]
            ]
        elif (self.channels == 7):
            frameset_data = [
                frameset_data[1], frameset_data[2], frameset_data[0],
                frameset_data[6], frameset_data[3], frameset_data[4],
                frameset_data[5]
            ]
        elif (self.channels == 8):
            frameset_data = [
                frameset_data[3], frameset_data[4], frameset_data[0],
                frameset_data[7], frameset_data[5], frameset_data[6],
                frameset_data[1], frameset_data[2]
            ]
        else:
            raise ValueError("unsupported channel count")

        framelist = from_channels([
            from_list(channel, 1, self.bits_per_sample, True)
            for channel in frameset_data
        ])

        # deduct PCM frames from remainder
        self.total_pcm_frames -= framelist.frames

        # return samples as a pcm.FrameList object
        return framelist

    def read_frame(self, channel_count):
        """returns a list of PCM sample lists, one per channel"""

        # read the ALAC frame header
        self.reader.skip(16)
        has_sample_count = self.reader.read(1)
        uncompressed_lsb_size = self.reader.read(2)
        uncompressed = self.reader.read(1)
        if (has_sample_count):
            sample_count = self.reader.read(32)
        else:
            sample_count = self.samples_per_frame

        if (uncompressed == 1):
            # if the frame is uncompressed,
            # read the raw, interlaced samples
            samples = [
                self.reader.read_signed(self.bits_per_sample)
                for i in range(sample_count * channel_count)
            ]
            return [samples[i::channel_count] for i in range(channel_count)]
        else:
            # if the frame is compressed,
            # read the interlacing parameters
            interlacing_shift = self.reader.read(8)
            interlacing_leftweight = self.reader.read(8)

            # subframe headers
            subframe_headers = [
                self.read_subframe_header() for i in range(channel_count)
            ]

            # optional uncompressed LSB values
            if (uncompressed_lsb_size > 0):
                uncompressed_lsbs = [
                    self.reader.read(uncompressed_lsb_size * 8)
                    for i in range(sample_count * channel_count)
                ]
            else:
                uncompressed_lsbs = []

            sample_size = (self.bits_per_sample - (uncompressed_lsb_size * 8) +
                           channel_count - 1)

            # and residual blocks
            residual_blocks = [
                self.read_residuals(sample_size, sample_count)
                for i in range(channel_count)
            ]

            # calculate subframe samples based on
            # subframe header's QLP coefficients and QLP shift-needed
            decoded_subframes = [
                self.decode_subframe(header[0], header[1], sample_size,
                                     residuals)
                for (header,
                     residuals) in zip(subframe_headers, residual_blocks)
            ]

            # decorrelate channels according interlacing shift and leftweight
            decorrelated_channels = self.decorrelate_channels(
                decoded_subframes, interlacing_shift, interlacing_leftweight)

            # if uncompressed LSB values are present,
            # prepend them to each sample of each channel
            if (uncompressed_lsb_size > 0):
                channels = []
                for (i, channel) in enumerate(decorrelated_channels):
                    assert (len(channel) == len(
                        uncompressed_lsbs[i::channel_count]))
                    channels.append([
                        s << (uncompressed_lsb_size * 8) | l for (s, l) in zip(
                            channel, uncompressed_lsbs[i::channel_count])
                    ])
                return channels
            else:
                return decorrelated_channels

    def read_subframe_header(self):
        prediction_type = self.reader.read(4)
        qlp_shift_needed = self.reader.read(4)
        rice_modifier = self.reader.read(3)
        qlp_coefficients = [
            self.reader.read_signed(16) for i in range(self.reader.read(5))
        ]

        return (qlp_shift_needed, qlp_coefficients)

    def read_residuals(self, sample_size, sample_count):
        residuals = []
        history = self.initial_history
        sign_modifier = 0
        i = 0

        while (i < sample_count):
            # get an unsigned residual based on "history"
            # and on "sample_size" as a lst resort
            k = min(log2(history // (2**9) + 3), self.maximum_k)

            unsigned = self.read_residual(k, sample_size) + sign_modifier

            # clear out old sign modifier, if any
            sign_modifier = 0

            # change unsigned residual to signed residual
            if (unsigned & 1):
                residuals.append(-((unsigned + 1) // 2))
            else:
                residuals.append(unsigned // 2)

            # update history based on unsigned residual
            if (unsigned <= 0xFFFF):
                history += ((unsigned * self.history_multiplier) -
                            ((history * self.history_multiplier) >> 9))
            else:
                history = 0xFFFF

            # if history gets too small, we may have a block of 0 samples
            # which can be compressed more efficiently
            if ((history < 128) and ((i + 1) < sample_count)):
                zeroes_k = min(7 - log2(history) + ((history + 16) // 64),
                               self.maximum_k)
                zero_residuals = self.read_residual(zeroes_k, 16)
                if (zero_residuals > 0):
                    residuals.extend([0] * zero_residuals)
                    i += zero_residuals

                history = 0

                if (zero_residuals <= 0xFFFF):
                    sign_modifier = 1

            i += 1

        return residuals

    def read_residual(self, k, sample_size):
        msb = self.reader.read_huffman_code(RESIDUAL)
        if (msb == -1):
            return self.reader.read(sample_size)
        elif (k == 0):
            return msb
        else:
            lsb = self.reader.read(k)
            if (lsb > 1):
                return msb * ((1 << k) - 1) + (lsb - 1)
            elif (lsb == 1):
                self.reader.unread(1)
                return msb * ((1 << k) - 1)
            else:
                self.reader.unread(0)
                return msb * ((1 << k) - 1)

    def decode_subframe(self, qlp_shift_needed, qlp_coefficients, sample_size,
                        residuals):
        # first sample is always copied verbatim
        samples = [residuals.pop(0)]

        if (len(qlp_coefficients) < 31):
            # the next "coefficient count" samples
            # are applied as differences to the previous
            for i in range(len(qlp_coefficients)):
                samples.append(
                    truncate_bits(samples[-1] + residuals.pop(0), sample_size))

            # remaining samples are processed much like LPC
            for residual in residuals:
                base_sample = samples[-len(qlp_coefficients) - 1]
                lpc_sum = sum([(s - base_sample) * c
                               for (s,
                                    c) in zip(samples[-len(qlp_coefficients):],
                                              reversed(qlp_coefficients))])
                outval = (1 << (qlp_shift_needed - 1)) + lpc_sum
                outval >>= qlp_shift_needed
                samples.append(
                    truncate_bits(outval + residual + base_sample,
                                  sample_size))

                buf = samples[-len(qlp_coefficients) - 2:-1]

                # error value then adjusts the coefficients table
                if (residual > 0):
                    predictor_num = len(qlp_coefficients) - 1

                    while ((predictor_num >= 0) and residual > 0):
                        val = (buf[0] -
                               buf[len(qlp_coefficients) - predictor_num])

                        sign = sign_only(val)

                        qlp_coefficients[predictor_num] -= sign

                        val *= sign

                        residual -= ((val >> qlp_shift_needed) *
                                     (len(qlp_coefficients) - predictor_num))

                        predictor_num -= 1

                elif (residual < 0):
                    # the same as above, but we break if residual goes positive
                    predictor_num = len(qlp_coefficients) - 1

                    while ((predictor_num >= 0) and residual < 0):
                        val = (buf[0] -
                               buf[len(qlp_coefficients) - predictor_num])

                        sign = -sign_only(val)

                        qlp_coefficients[predictor_num] -= sign

                        val *= sign

                        residual -= ((val >> qlp_shift_needed) *
                                     (len(qlp_coefficients) - predictor_num))

                        predictor_num -= 1
        else:
            # residuals are encoded as simple difference values
            for residual in residuals:
                samples.append(
                    truncate_bits(samples[-1] + residual, sample_size))

        return samples

    def decorrelate_channels(self, channel_data, interlacing_shift,
                             interlacing_leftweight):
        if (len(channel_data) != 2):
            return channel_data
        elif (interlacing_leftweight == 0):
            return channel_data
        else:
            left = []
            right = []
            for (ch1, ch2) in zip(*channel_data):
                right.append(ch1 - ((ch2 * interlacing_leftweight) //
                                    (2**interlacing_shift)))
                left.append(ch2 + right[-1])
            return [left, right]

    def close(self):
        self.reader.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()
示例#19
0
    def aiff_header_footer(self):
        """returns (header, footer) tuple of strings
        containing all data before and after the PCM stream

        if self.has_foreign_aiff_chunks() is False,
        may raise ValueError if the file has no header and footer
        for any reason"""

        from audiotools.bitstream import BitstreamReader
        from audiotools.bitstream import BitstreamRecorder
        from audiotools.text import (ERR_AIFF_NOT_AIFF, ERR_AIFF_INVALID_AIFF,
                                     ERR_AIFF_INVALID_CHUNK_ID)

        head = BitstreamRecorder(0)
        tail = BitstreamRecorder(0)
        current_block = head

        aiff_file = BitstreamReader(open(self.filename, 'rb'), 0)
        try:
            # transfer the 12-byte "RIFFsizeWAVE" header to head
            (form, size, aiff) = aiff_file.parse("4b 32u 4b")
            if (form != 'FORM'):
                raise InvalidAIFF(ERR_AIFF_NOT_AIFF)
            elif (aiff != 'AIFF'):
                raise InvalidAIFF(ERR_AIFF_INVALID_AIFF)
            else:
                current_block.build("4b 32u 4b", (form, size, aiff))
                total_size = size - 4

            while (total_size > 0):
                # transfer each chunk header
                (chunk_id, chunk_size) = aiff_file.parse("4b 32u")
                if (not frozenset(chunk_id).issubset(self.PRINTABLE_ASCII)):
                    raise InvalidAIFF(ERR_AIFF_INVALID_CHUNK_ID)
                else:
                    current_block.build("4b 32u", (chunk_id, chunk_size))
                    total_size -= 8

                # and transfer the full content of non-audio chunks
                if (chunk_id != "SSND"):
                    if (chunk_size % 2):
                        current_block.write_bytes(
                            aiff_file.read_bytes(chunk_size + 1))
                        total_size -= (chunk_size + 1)
                    else:
                        current_block.write_bytes(
                            aiff_file.read_bytes(chunk_size))
                        total_size -= chunk_size
                else:
                    # transfer alignment as part of SSND's chunk header
                    align = aiff_file.parse("32u 32u")
                    current_block.build("32u 32u", align)
                    aiff_file.skip_bytes(chunk_size - 8)
                    current_block = tail

                    if (chunk_size % 2):
                        current_block.write_bytes(aiff_file.read_bytes(1))
                        total_size -= (chunk_size + 1)
                    else:
                        total_size -= chunk_size

            return (head.data(), tail.data())
        finally:
            aiff_file.close()