Beispiel #1
0
    def get_metadata(self):
        """returns a MetaData object, or None

        raises IOError if unable to read the file"""

        from io import BytesIO
        from audiotools.bitstream import BitstreamReader
        from audiotools.ogg import PacketReader, PageReader

        reader = PacketReader(PageReader(open(self.filename, "rb")))

        identification = reader.read_packet()
        comment = BitstreamReader(BytesIO(reader.read_packet()), True)

        if (comment.read_bytes(8) == "OpusTags"):
            vendor_string = \
                comment.read_bytes(comment.read(32)).decode('utf-8')
            comment_strings = [
                comment.read_bytes(comment.read(32)).decode('utf-8')
                for i in range(comment.read(32))
            ]

            return VorbisComment(comment_strings, vendor_string)
        else:
            return None
Beispiel #2
0
    def get_metadata(self):
        """returns a MetaData object, or None

        raises IOError if unable to read the file"""

        from cStringIO import StringIO
        from audiotools.bitstream import BitstreamReader
        from audiotools.ogg import PacketReader, PageReader
        from audiotools.vorbiscomment import VorbisComment

        reader = PacketReader(PageReader(open(self.filename, "rb")))

        identification = reader.read_packet()
        comment = BitstreamReader(StringIO(reader.read_packet()), True)

        (packet_type, packet_header) = comment.parse("8u 6b")
        if ((packet_type == 3) and (packet_header == 'vorbis')):
            vendor_string = \
                comment.read_bytes(comment.read(32)).decode('utf-8')
            comment_strings = [
                comment.read_bytes(comment.read(32)).decode('utf-8')
                for i in xrange(comment.read(32))]
            if (comment.read(1) == 1):   # framing bit
                return VorbisComment(comment_strings, vendor_string)
            else:
                return None
        else:
            return None
Beispiel #3
0
    def get_metadata(self):
        """returns a MetaData object, or None

        raises IOError if unable to read the file"""

        from audiotools.ogg import PacketReader,PageReader
        from cStringIO import StringIO
        from audiotools.bitstream import BitstreamReader

        packets = PacketReader(PageReader(open(self.filename, "rb")))

        identification = packets.read_packet()
        comment = BitstreamReader(StringIO(packets.read_packet()), True)

        if (comment.read_bytes(8) == "OpusTags"):
            vendor_string = \
                comment.read_bytes(comment.read(32)).decode('utf-8')
            comment_strings = [
                comment.read_bytes(comment.read(32)).decode('utf-8')
                for i in xrange(comment.read(32))]
            return VorbisComment(comment_strings, vendor_string)
        else:
            return None
    def get_metadata(self):
        """returns a MetaData object, or None

        raises IOError if unable to read the file"""

        from io import BytesIO
        from audiotools.bitstream import BitstreamReader
        from audiotools.ogg import PacketReader, PageReader
        from audiotools.vorbiscomment import VorbisComment

        with PacketReader(PageReader(open(self.filename, "rb"))) as reader:
            identification = reader.read_packet()

            comment = BitstreamReader(BytesIO(reader.read_packet()), True)

            vendor_string = \
                comment.read_bytes(comment.read(32)).decode('utf-8')

            comment_strings = [
                comment.read_bytes(comment.read(32)).decode('utf-8')
                for i in range(comment.read(32))]

            return VorbisComment(comment_strings, vendor_string)
Beispiel #5
0
    def __init__(self, filename):
        """filename is a plain string"""

        from audiotools.bitstream import BitstreamReader
        from audiotools import ChannelMask
        from io import BytesIO

        WaveContainer.__init__(self, filename)
        try:
            f = open(filename, 'rb')
        except IOError as msg:
            raise InvalidShorten(str(msg))

        reader = BitstreamReader(f, 0)
        try:
            if (reader.parse("4b 8u") != ["ajkg", 2]):
                raise InvalidShorten("invalid Shorten header")
        except IOError:
            raise InvalidShorten("invalid Shorten header")

        def read_unsigned(r, c):
            MSB = r.unary(1)
            LSB = r.read(c)
            return MSB * 2 ** c + LSB

        def read_long(r):
            return read_unsigned(r, read_unsigned(r, 2))

        # populate channels and bits_per_sample from Shorten header
        (file_type,
         self.__channels__,
         block_length,
         max_LPC,
         number_of_means,
         bytes_to_skip) = [read_long(reader) for i in range(6)]

        if ((1 <= file_type) and (file_type <= 2)):
            self.__bits_per_sample__ = 8
        elif ((3 <= file_type) and (file_type <= 6)):
            self.__bits_per_sample__ = 16
        else:
            # FIXME
            raise InvalidShorten("unsupported Shorten file type")

        # setup some default dummy metadata
        self.__sample_rate__ = 44100
        if (self.__channels__ == 1):
            self.__channel_mask__ = ChannelMask(0x4)
        elif (self.__channels__ == 2):
            self.__channel_mask__ = ChannelMask(0x3)
        else:
            self.__channel_mask__ = ChannelMask(0)
        self.__total_frames__ = 0

        # populate sample_rate and total_frames from first VERBATIM command
        command = read_unsigned(reader, 2)
        if (command == 9):
            verbatim_bytes = "".join([chr(read_unsigned(reader, 8) & 0xFF)
                                      for i in range(read_unsigned(reader,
                                                                   5))])
            try:
                wave = BitstreamReader(BytesIO(verbatim_bytes), 1)
                header = wave.read_bytes(12)
                if (header.startswith("RIFF") and header.endswith("WAVE")):
                    # got RIFF/WAVE header, so parse wave blocks as needed
                    total_size = len(verbatim_bytes) - 12
                    while (total_size >= 8):
                        (chunk_id, chunk_size) = wave.parse("4b 32u")
                        total_size -= 8
                        if (chunk_id == 'fmt '):
                            from audiotools.wav import parse_fmt

                            (channels,
                             self.__sample_rate__,
                             bits_per_sample,
                             self.__channel_mask__) = parse_fmt(
                                wave.substream(chunk_size))
                        elif (chunk_id == 'data'):
                            self.__total_frames__ = \
                                (chunk_size //
                                 (self.__channels__ *
                                  (self.__bits_per_sample__ // 8)))
                        else:
                            if (chunk_size % 2):
                                wave.read_bytes(chunk_size + 1)
                                total_size -= (chunk_size + 1)
                            else:
                                wave.read_bytes(chunk_size)
                                total_size -= chunk_size
            except (IOError, ValueError):
                pass

            try:
                aiff = BitstreamReader(BytesIO(verbatim_bytes), 0)
                header = aiff.read_bytes(12)
                if (header.startswith("FORM") and header.endswith("AIFF")):
                    # got FORM/AIFF header, so parse aiff blocks as needed
                    total_size = len(verbatim_bytes) - 12
                    while (total_size >= 8):
                        (chunk_id, chunk_size) = aiff.parse("4b 32u")
                        total_size -= 8
                        if (chunk_id == 'COMM'):
                            from audiotools.aiff import parse_comm

                            (channels,
                             total_sample_frames,
                             bits_per_sample,
                             self.__sample_rate__,
                             self.__channel_mask__) = parse_comm(
                                aiff.substream(chunk_size))
                        elif (chunk_id == 'SSND'):
                            # subtract 8 bytes for "offset" and "block size"
                            self.__total_frames__ = \
                                ((chunk_size - 8) //
                                 (self.__channels__ *
                                  (self.__bits_per_sample__ // 8)))
                        else:
                            if (chunk_size % 2):
                                aiff.read_bytes(chunk_size + 1)
                                total_size -= (chunk_size + 1)
                            else:
                                aiff.read_bytes(chunk_size)
                                total_size -= chunk_size
            except IOError:
                pass
Beispiel #6
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
Beispiel #7
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
    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()
    def read_metadata(self):
        command = self.unsigned(2)
        if (command == 9):
            # got verbatim, so read data
            verbatim_bytes = "".join([chr(self.unsigned(8) & 0xFF)
                                      for i in range(self.unsigned(5))])

            try:
                wave = BitstreamReader(cStringIO.StringIO(verbatim_bytes), 1)
                header = wave.read_bytes(12)
                if (header.startswith("RIFF") and header.endswith("WAVE")):
                    # got RIFF/WAVE header, so parse wave blocks as needed
                    total_size = len(verbatim_bytes) - 12
                    while (total_size > 0):
                        (chunk_id, chunk_size) = wave.parse("4b 32u")
                        total_size -= 8
                        if (chunk_id == 'fmt '):
                            (channels,
                             self.sample_rate,
                             bits_per_sample,
                             channel_mask) = parse_fmt(
                                wave.substream(chunk_size))
                            self.channel_mask = int(channel_mask)
                            return
                        else:
                            if (chunk_size % 2):
                                wave.read_bytes(chunk_size + 1)
                                total_size -= (chunk_size + 1)
                            else:
                                wave.read_bytes(chunk_size)
                                total_size -= chunk_size
                    else:
                        # no fmt chunk, so use default metadata
                        pass
            except (IOError, ValueError):
                pass

            try:
                aiff = BitstreamReader(cStringIO.StringIO(verbatim_bytes), 0)
                header = aiff.read_bytes(12)
                if (header.startswith("FORM") and header.endswith("AIFF")):
                    # got FORM/AIFF header, so parse aiff blocks as needed
                    total_size = len(verbatim_bytes) - 12
                    while (total_size > 0):
                        (chunk_id, chunk_size) = aiff.parse("4b 32u")
                        total_size -= 8
                        if (chunk_id == 'COMM'):
                            (channels,
                             total_sample_frames,
                             bits_per_sample,
                             self.sample_rate,
                             channel_mask) = parse_comm(
                                aiff.substream(chunk_size))
                            self.channel_mask = int(channel_mask)
                            return
                        else:
                            if (chunk_size % 2):
                                aiff.read_bytes(chunk_size + 1)
                                total_size -= (chunk_size + 1)
                            else:
                                aiff.read_bytes(chunk_size)
                                total_size -= chunk_size
                    else:
                        # no COMM chunk, so use default metadata
                        pass
            except IOError:
                pass

        # got something else, so invent some PCM parameters
        self.sample_rate = 44100
        if (self.channels == 1):
            self.channel_mask = 0x4
        elif (self.channels == 2):
            self.channel_mask = 0x3
        else:
            self.channel_mask = 0
Beispiel #10
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
Beispiel #11
0
    def __init__(self, filename):
        """filename is a plain string"""

        from audiotools.bitstream import BitstreamReader
        from audiotools import ChannelMask
        from io import BytesIO

        WaveContainer.__init__(self, filename)
        try:
            f = open(filename, 'rb')
        except IOError as msg:
            raise InvalidShorten(str(msg))

        reader = BitstreamReader(f, 0)
        try:
            if (reader.parse("4b 8u") != ["ajkg", 2]):
                raise InvalidShorten("invalid Shorten header")
        except IOError:
            raise InvalidShorten("invalid Shorten header")

        def read_unsigned(r, c):
            MSB = r.unary(1)
            LSB = r.read(c)
            return MSB * 2**c + LSB

        def read_long(r):
            return read_unsigned(r, read_unsigned(r, 2))

        # populate channels and bits_per_sample from Shorten header
        (file_type, self.__channels__, block_length, max_LPC, number_of_means,
         bytes_to_skip) = [read_long(reader) for i in range(6)]

        if ((1 <= file_type) and (file_type <= 2)):
            self.__bits_per_sample__ = 8
        elif ((3 <= file_type) and (file_type <= 6)):
            self.__bits_per_sample__ = 16
        else:
            # FIXME
            raise InvalidShorten("unsupported Shorten file type")

        # setup some default dummy metadata
        self.__sample_rate__ = 44100
        if (self.__channels__ == 1):
            self.__channel_mask__ = ChannelMask(0x4)
        elif (self.__channels__ == 2):
            self.__channel_mask__ = ChannelMask(0x3)
        else:
            self.__channel_mask__ = ChannelMask(0)
        self.__total_frames__ = 0

        # populate sample_rate and total_frames from first VERBATIM command
        command = read_unsigned(reader, 2)
        if (command == 9):
            verbatim_bytes = "".join([
                chr(read_unsigned(reader, 8) & 0xFF)
                for i in range(read_unsigned(reader, 5))
            ])
            try:
                wave = BitstreamReader(BytesIO(verbatim_bytes), 1)
                header = wave.read_bytes(12)
                if (header.startswith("RIFF") and header.endswith("WAVE")):
                    # got RIFF/WAVE header, so parse wave blocks as needed
                    total_size = len(verbatim_bytes) - 12
                    while (total_size >= 8):
                        (chunk_id, chunk_size) = wave.parse("4b 32u")
                        total_size -= 8
                        if (chunk_id == 'fmt '):
                            from audiotools.wav import parse_fmt

                            (channels, self.__sample_rate__, bits_per_sample,
                             self.__channel_mask__) = parse_fmt(
                                 wave.substream(chunk_size))
                        elif (chunk_id == 'data'):
                            self.__total_frames__ = \
                                (chunk_size //
                                 (self.__channels__ *
                                  (self.__bits_per_sample__ // 8)))
                        else:
                            if (chunk_size % 2):
                                wave.read_bytes(chunk_size + 1)
                                total_size -= (chunk_size + 1)
                            else:
                                wave.read_bytes(chunk_size)
                                total_size -= chunk_size
            except (IOError, ValueError):
                pass

            try:
                aiff = BitstreamReader(BytesIO(verbatim_bytes), 0)
                header = aiff.read_bytes(12)
                if (header.startswith("FORM") and header.endswith("AIFF")):
                    # got FORM/AIFF header, so parse aiff blocks as needed
                    total_size = len(verbatim_bytes) - 12
                    while (total_size >= 8):
                        (chunk_id, chunk_size) = aiff.parse("4b 32u")
                        total_size -= 8
                        if (chunk_id == 'COMM'):
                            from audiotools.aiff import parse_comm

                            (channels, total_sample_frames, bits_per_sample,
                             self.__sample_rate__,
                             self.__channel_mask__) = parse_comm(
                                 aiff.substream(chunk_size))
                        elif (chunk_id == 'SSND'):
                            # subtract 8 bytes for "offset" and "block size"
                            self.__total_frames__ = \
                                ((chunk_size - 8) //
                                 (self.__channels__ *
                                  (self.__bits_per_sample__ // 8)))
                        else:
                            if (chunk_size % 2):
                                aiff.read_bytes(chunk_size + 1)
                                total_size -= (chunk_size + 1)
                            else:
                                aiff.read_bytes(chunk_size)
                                total_size -= chunk_size
            except IOError:
                pass
Beispiel #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()
Beispiel #13
0
class FlacDecoder(object):
    CHANNEL_COUNT = [
        1, 2, 3, 4, 5, 6, 7, 8, 2, 2, 2, None, None, None, None, None
    ]

    (SUBFRAME_CONSTANT, SUBFRAME_VERBATIM, SUBFRAME_FIXED,
     SUBFRAME_LPC) = range(4)

    def __init__(self, filename, channel_mask):
        self.reader = BitstreamReader(open(filename, "rb"), False)

        if (self.reader.read_bytes(4) != b'fLaC'):
            raise ValueError("invalid FLAC file")

        self.current_md5sum = md5()

        # locate the STREAMINFO,
        # which is sometimes needed to handle non-subset streams
        for (block_id, block_size,
             block_reader) in self.metadata_blocks(self.reader):
            if (block_id == 0):
                # read STREAMINFO
                self.minimum_block_size = block_reader.read(16)
                self.maximum_block_size = block_reader.read(16)
                self.minimum_frame_size = block_reader.read(24)
                self.maximum_frame_size = block_reader.read(24)
                self.sample_rate = block_reader.read(20)
                self.channels = block_reader.read(3) + 1
                self.channel_mask = channel_mask
                self.bits_per_sample = block_reader.read(5) + 1
                self.total_frames = block_reader.read(36)
                self.md5sum = block_reader.read_bytes(16)

                # these are frame header lookup tables
                # which vary slightly depending on STREAMINFO's values
                self.BLOCK_SIZE = [
                    self.maximum_block_size, 192, 576, 1152, 2304, 4608, None,
                    None, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
                ]
                self.SAMPLE_RATE = [
                    self.sample_rate, 88200, 176400, 192000, 8000, 16000,
                    22050, 24000, 32000, 44100, 48000, 96000, None, None, None,
                    None
                ]
                self.BITS_PER_SAMPLE = [
                    self.bits_per_sample, 8, 12, None, 16, 20, 24, None
                ]

    def metadata_blocks(self, reader):
        """yields a (block_id, block_size, block_reader) tuple
        per metadata block where block_reader is a BitstreamReader substream"""

        (last_block, block_id, block_size) = self.reader.parse("1u 7u 24u")
        while (last_block == 0):
            yield (block_id, block_size, self.reader.substream(block_size))
            (last_block, block_id, block_size) = self.reader.parse("1u 7u 24u")
        else:
            yield (block_id, block_size, self.reader.substream(block_size))

    def read(self, pcm_frames):
        # if the stream is exhausted,
        # verify its MD5 sum and return an empty pcm.FrameList object
        if (self.total_frames < 1):
            if (self.md5sum == self.current_md5sum.digest()):
                return empty_framelist(self.channels, self.bits_per_sample)
            else:
                raise ValueError("MD5 checksum mismatch")

        crc16 = CRC16()
        self.reader.add_callback(crc16.update)

        # fetch the decoding parameters from the frame header
        (block_size, channel_assignment,
         bits_per_sample) = self.read_frame_header()
        channel_count = self.CHANNEL_COUNT[channel_assignment]
        if (channel_count is None):
            raise ValueError("invalid channel assignment")

        # channel data will be a list of signed sample lists, one per channel
        # such as  [[1, 2, 3, ...], [4, 5, 6, ...]]  for a 2 channel stream
        channel_data = []

        for channel_number in range(channel_count):
            if ((channel_assignment == 0x8) and (channel_number == 1)):
                # for left-difference assignment
                # the difference channel has 1 additional bit
                channel_data.append(
                    self.read_subframe(block_size, bits_per_sample + 1))
            elif ((channel_assignment == 0x9) and (channel_number == 0)):
                # for difference-right assignment
                # the difference channel has 1 additional bit
                channel_data.append(
                    self.read_subframe(block_size, bits_per_sample + 1))
            elif ((channel_assignment == 0xA) and (channel_number == 1)):
                # for average-difference assignment
                # the difference channel has 1 additional bit
                channel_data.append(
                    self.read_subframe(block_size, bits_per_sample + 1))
            else:
                # otherwise, use the frame's bits-per-sample value
                channel_data.append(
                    self.read_subframe(block_size, bits_per_sample))

        # one all the subframes have been decoded,
        # reconstruct them depending on the channel assignment
        if (channel_assignment == 0x8):
            # left-difference
            samples = []
            for (left, difference) in zip(*channel_data):
                samples.append(left)
                samples.append(left - difference)
        elif (channel_assignment == 0x9):
            # difference-right
            samples = []
            for (difference, right) in zip(*channel_data):
                samples.append(difference + right)
                samples.append(right)
        elif (channel_assignment == 0xA):
            # mid-side
            samples = []
            for (mid, side) in zip(*channel_data):
                samples.append((((mid * 2) + (side % 2)) + side) // 2)
                samples.append((((mid * 2) + (side % 2)) - side) // 2)
        else:
            # independent
            samples = [0] * block_size * channel_count
            for (i, channel) in enumerate(channel_data):
                samples[i::channel_count] = channel

        self.reader.byte_align()

        # read and verify the frame's trailing CRC-16 footer
        self.reader.read(16)
        self.reader.pop_callback()
        if (int(crc16) != 0):
            raise ValueError("CRC16 mismatch in frame footer")

        # deduct the amount of PCM frames from the remaining amount
        self.total_frames -= block_size

        # build a pcm.FrameList object from the combined samples
        framelist = from_list(samples, channel_count, bits_per_sample, True)

        # update the running MD5 sum calculation with the frame's data
        self.current_md5sum.update(framelist.to_bytes(0, 1))

        # and finally return the frame data
        return framelist

    def read_frame_header(self):
        crc8 = CRC8()
        self.reader.add_callback(crc8.update)

        # read the 32-bit FLAC frame header
        sync_code = self.reader.read(14)
        if (sync_code != 0x3FFE):
            raise ValueError("invalid sync code")

        self.reader.skip(1)
        blocking_strategy = self.reader.read(1)
        block_size_bits = self.reader.read(4)
        sample_rate_bits = self.reader.read(4)
        channel_assignment = self.reader.read(4)
        bits_per_sample_bits = self.reader.read(3)
        self.reader.skip(1)

        # the frame number is a UTF-8 encoded value
        # which takes a variable number of whole bytes
        frame_number = self.read_utf8()

        # unpack the 4 bit block size field
        # which is the total PCM frames in the FLAC frame
        # and may require up to 16 more bits if the frame is usually-sized
        # (which typically happens at the end of the stream)
        if (block_size_bits == 0x6):
            block_size = self.reader.read(8) + 1
        elif (block_size_bits == 0x7):
            block_size = self.reader.read(16) + 1
        else:
            block_size = self.BLOCK_SIZE[block_size_bits]

        # unpack the 4 bit sample rate field
        # which is used for playback, but not needed for decoding
        # and may require up to 16 more bits
        # if the stream has a particularly unusual sample rate
        if (sample_rate_bits == 0xC):
            sample_rate = self.reader.read(8) * 1000
        elif (sample_rate_bits == 0xD):
            sample_rate = self.reader.read(16)
        elif (sample_rate_bits == 0xE):
            sample_rate = self.reader.read(16) * 10
        elif (sample_rate_bits == 0xF):
            raise ValueError("invalid sample rate")
        else:
            sample_rate = self.SAMPLE_RATE[sample_rate_bits]

        # unpack the 3 bit bits-per-sample field
        # this never requires additional bits
        if ((bits_per_sample_bits == 0x3) or (bits_per_sample_bits == 0x7)):
            raise ValueError("invalid bits per sample")
        else:
            bits_per_sample = self.BITS_PER_SAMPLE[bits_per_sample_bits]

        # read and verify frame's CRC-8 value
        self.reader.read(8)
        self.reader.pop_callback()
        if (int(crc8) != 0):
            raise ValueError("CRC8 mismatch in frame header")

        return (block_size, channel_assignment, bits_per_sample)

    def read_subframe_header(self):
        """returns a tuple of (subframe_type, subframe_order, wasted_bps)"""

        self.reader.skip(1)
        subframe_type = self.reader.read(6)
        if (self.reader.read(1) == 1):
            wasted_bps = self.reader.unary(1) + 1
        else:
            wasted_bps = 0

        # extract "order" value from 6 bit subframe type, if necessary
        if (subframe_type == 0):
            return (self.SUBFRAME_CONSTANT, None, wasted_bps)
        elif (subframe_type == 1):
            return (self.SUBFRAME_VERBATIM, None, wasted_bps)
        elif ((subframe_type & 0x38) == 0x08):
            return (self.SUBFRAME_FIXED, subframe_type & 0x07, wasted_bps)
        elif ((subframe_type & 0x20) == 0x20):
            return (self.SUBFRAME_LPC, (subframe_type & 0x1F) + 1, wasted_bps)
        else:
            raise ValueError("invalid subframe type")

    def read_subframe(self, block_size, bits_per_sample):
        (subframe_type, subframe_order,
         wasted_bps) = self.read_subframe_header()

        # read a list of signed sample values
        # depending on the subframe type, block size,
        # adjusted bits per sample and optional subframe order
        if (subframe_type == self.SUBFRAME_CONSTANT):
            subframe_samples = self.read_constant_subframe(
                block_size, bits_per_sample - wasted_bps)
        elif (subframe_type == self.SUBFRAME_VERBATIM):
            subframe_samples = self.read_verbatim_subframe(
                block_size, bits_per_sample - wasted_bps)
        elif (subframe_type == self.SUBFRAME_FIXED):
            subframe_samples = self.read_fixed_subframe(
                block_size, bits_per_sample - wasted_bps, subframe_order)
        else:
            subframe_samples = self.read_lpc_subframe(
                block_size, bits_per_sample - wasted_bps, subframe_order)

        # account for wasted bits-per-sample, if necessary
        if (wasted_bps):
            return [sample << wasted_bps for sample in subframe_samples]
        else:
            return subframe_samples

    def read_constant_subframe(self, block_size, bits_per_sample):
        sample = self.reader.read_signed(bits_per_sample)
        return [sample] * block_size

    def read_verbatim_subframe(self, block_size, bits_per_sample):
        return [
            self.reader.read_signed(bits_per_sample) for x in range(block_size)
        ]

    def read_fixed_subframe(self, block_size, bits_per_sample, order):
        # "order" number of warm-up samples
        samples = [
            self.reader.read_signed(bits_per_sample) for i in range(order)
        ]

        # "block_size" - "order" number of residual values
        residuals = self.read_residual(block_size, order)

        # which are applied to the warm-up samples
        # depending on the FIXED subframe order
        # and results in "block_size" number of total samples
        if (order == 0):
            return residuals
        elif (order == 1):
            for residual in residuals:
                samples.append(samples[-1] + residual)
            return samples
        elif (order == 2):
            for residual in residuals:
                samples.append((2 * samples[-1]) - samples[-2] + residual)
            return samples
        elif (order == 3):
            for residual in residuals:
                samples.append((3 * samples[-1]) - (3 * samples[-2]) +
                               samples[-3] + residual)
            return samples
        elif (order == 4):
            for residual in residuals:
                samples.append((4 * samples[-1]) - (6 * samples[-2]) +
                               (4 * samples[-3]) - samples[-4] + residual)
            return samples
        else:
            raise ValueError("unsupported FIXED subframe order")

    def read_lpc_subframe(self, block_size, bits_per_sample, order):
        # "order" number of warm-up samples
        samples = [
            self.reader.read_signed(bits_per_sample) for i in range(order)
        ]

        # the size of each QLP coefficient, in bits
        qlp_precision = self.reader.read(4)

        # the amount of right shift to apply
        # during LPC calculation
        # (though this is a signed value, negative shifts are noops
        # in the reference FLAC decoder)
        qlp_shift_needed = max(self.reader.read_signed(5), 0)

        # "order" number of signed QLP coefficients
        qlp_coeffs = [
            self.reader.read_signed(qlp_precision + 1) for i in range(order)
        ]
        # QLP coefficients are applied in reverse order
        qlp_coeffs.reverse()

        # "block_size" - "order" number of residual values
        residuals = self.read_residual(block_size, order)

        # which are applied to the running LPC calculation
        for residual in residuals:
            samples.append((sum([
                coeff * sample
                for (coeff, sample) in zip(qlp_coeffs, samples[-order:])
            ]) >> qlp_shift_needed) + residual)

        return samples

    def read_residual(self, block_size, order):
        residuals = []

        coding_method = self.reader.read(2)
        partition_order = self.reader.read(4)

        # each parititon contains  block_size / 2 ** partition_order
        # number of residuals
        for partition_number in range(2**partition_order):
            if (partition_number == 0):
                # except for the first partition
                # which contains "order" less than the rest
                residuals.extend(
                    self.read_residual_partition(
                        coding_method,
                        (block_size // 2**partition_order) - order))
            else:
                residuals.extend(
                    self.read_residual_partition(
                        coding_method, block_size // 2**partition_order))

        return residuals

    def read_residual_partition(self, coding_method, residual_count):
        if (coding_method == 0):
            # the Rice parameters determines the number of
            # least-significant bits to read for each residual
            rice_parameter = self.reader.read(4)
            if (rice_parameter == 0xF):
                escape_code = self.reader.read(5)
                return [
                    self.reader.read_signed(escape_code)
                    for i in range(residual_count)
                ]
        elif (coding_method == 1):
            # 24 bps streams may use a 5-bit Rice parameter
            # for better compression
            rice_parameter = self.reader.read(5)
            if (rice_parameter == 0x1F):
                escape_code = self.reader.read(5)
                return [
                    self.reader.read_signed(escape_code)
                    for i in range(residual_count)
                ]
        else:
            raise ValueError("invalid Rice coding parameter")

        # a list of signed residual values
        partition_residuals = []

        for i in range(residual_count):
            msb = self.reader.unary(1)  # most-significant bits
            lsb = self.reader.read(rice_parameter)  # least-significant bits
            value = (msb << rice_parameter) | lsb  # combined into a value
            if (value & 1):  # whose least-significant bit is the sign value
                partition_residuals.append(-(value >> 1) - 1)
            else:
                partition_residuals.append(value >> 1)

        return partition_residuals

    def read_utf8(self):
        total_bytes = self.reader.unary(0)
        value = self.reader.read(7 - total_bytes)
        while (total_bytes > 1):
            value = ((value << 6) | self.reader.parse("2p 6u")[0])
            total_bytes -= 1
        return value

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

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()
Beispiel #14
0
    def read_metadata(self):
        from io import BytesIO

        command = self.unsigned(2)
        if (command == 9):
            # got verbatim, so read data
            verbatim_bytes = ints_to_bytes(
                [self.unsigned(8) & 0xFF for i in range(self.unsigned(5))])

            try:
                wave = BitstreamReader(BytesIO(verbatim_bytes), True)
                header = wave.read_bytes(12)
                if (header.startswith(b"RIFF") and header.endswith(b"WAVE")):
                    # got RIFF/WAVE header, so parse wave blocks as needed
                    total_size = len(verbatim_bytes) - 12
                    while (total_size > 0):
                        (chunk_id, chunk_size) = wave.parse("4b 32u")
                        total_size -= 8
                        if (chunk_id == b'fmt '):
                            (channels, self.sample_rate, bits_per_sample,
                             channel_mask) = parse_fmt(
                                 wave.substream(chunk_size))
                            self.channel_mask = int(channel_mask)
                            return
                        else:
                            if (chunk_size % 2):
                                wave.read_bytes(chunk_size + 1)
                                total_size -= (chunk_size + 1)
                            else:
                                wave.read_bytes(chunk_size)
                                total_size -= chunk_size
                    else:
                        # no fmt chunk, so use default metadata
                        pass
            except (IOError, ValueError):
                pass

            try:
                aiff = BitstreamReader(BytesIO(verbatim_bytes), False)
                header = aiff.read_bytes(12)
                if (header.startswith(b"FORM") and header.endswith(b"AIFF")):
                    # got FORM/AIFF header, so parse aiff blocks as needed
                    total_size = len(verbatim_bytes) - 12
                    while (total_size > 0):
                        (chunk_id, chunk_size) = aiff.parse("4b 32u")
                        total_size -= 8
                        if (chunk_id == b'COMM'):
                            (channels, total_sample_frames, bits_per_sample,
                             self.sample_rate, channel_mask) = parse_comm(
                                 aiff.substream(chunk_size))
                            self.channel_mask = int(channel_mask)
                            return
                        else:
                            if (chunk_size % 2):
                                aiff.read_bytes(chunk_size + 1)
                                total_size -= (chunk_size + 1)
                            else:
                                aiff.read_bytes(chunk_size)
                                total_size -= chunk_size
                    else:
                        # no COMM chunk, so use default metadata
                        pass
            except IOError:
                pass

        # got something else, so invent some PCM parameters
        self.sample_rate = 44100
        if (self.channels == 1):
            self.channel_mask = 0x4
        elif (self.channels == 2):
            self.channel_mask = 0x3
        else:
            self.channel_mask = 0
Beispiel #15
0
class SHNDecoder(object):
    def __init__(self, filename):
        self.reader = BitstreamReader(open(filename, "rb"), False)

        (self.file_type, self.channels, self.block_length, self.max_LPC,
         self.number_of_means) = self.read_header()

        if ((1 <= self.file_type) and (self.file_type <= 2)):
            self.bits_per_sample = 8
            self.signed_samples = (self.file_type == 1)
        elif ((3 <= self.file_type) and (self.file_type <= 6)):
            self.bits_per_sample = 16
            self.signed_samples = (self.file_type in (3, 5))
        else:
            raise ValueError("unsupported Shorten file type")

        self.wrapped_samples = [[0] * 3 for c in range(self.channels)]
        self.means = [[0] * self.number_of_means for c in range(self.channels)]
        self.left_shift = 0
        self.stream_finished = False

        # try to read the first command for a wave/aiff header
        self.reader.mark()
        self.read_metadata()
        self.reader.rewind()
        self.reader.unmark()

    def read_metadata(self):
        from io import BytesIO

        command = self.unsigned(2)
        if (command == 9):
            # got verbatim, so read data
            verbatim_bytes = ints_to_bytes(
                [self.unsigned(8) & 0xFF for i in range(self.unsigned(5))])

            try:
                wave = BitstreamReader(BytesIO(verbatim_bytes), True)
                header = wave.read_bytes(12)
                if (header.startswith(b"RIFF") and header.endswith(b"WAVE")):
                    # got RIFF/WAVE header, so parse wave blocks as needed
                    total_size = len(verbatim_bytes) - 12
                    while (total_size > 0):
                        (chunk_id, chunk_size) = wave.parse("4b 32u")
                        total_size -= 8
                        if (chunk_id == b'fmt '):
                            (channels, self.sample_rate, bits_per_sample,
                             channel_mask) = parse_fmt(
                                 wave.substream(chunk_size))
                            self.channel_mask = int(channel_mask)
                            return
                        else:
                            if (chunk_size % 2):
                                wave.read_bytes(chunk_size + 1)
                                total_size -= (chunk_size + 1)
                            else:
                                wave.read_bytes(chunk_size)
                                total_size -= chunk_size
                    else:
                        # no fmt chunk, so use default metadata
                        pass
            except (IOError, ValueError):
                pass

            try:
                aiff = BitstreamReader(BytesIO(verbatim_bytes), False)
                header = aiff.read_bytes(12)
                if (header.startswith(b"FORM") and header.endswith(b"AIFF")):
                    # got FORM/AIFF header, so parse aiff blocks as needed
                    total_size = len(verbatim_bytes) - 12
                    while (total_size > 0):
                        (chunk_id, chunk_size) = aiff.parse("4b 32u")
                        total_size -= 8
                        if (chunk_id == b'COMM'):
                            (channels, total_sample_frames, bits_per_sample,
                             self.sample_rate, channel_mask) = parse_comm(
                                 aiff.substream(chunk_size))
                            self.channel_mask = int(channel_mask)
                            return
                        else:
                            if (chunk_size % 2):
                                aiff.read_bytes(chunk_size + 1)
                                total_size -= (chunk_size + 1)
                            else:
                                aiff.read_bytes(chunk_size)
                                total_size -= chunk_size
                    else:
                        # no COMM chunk, so use default metadata
                        pass
            except IOError:
                pass

        # got something else, so invent some PCM parameters
        self.sample_rate = 44100
        if (self.channels == 1):
            self.channel_mask = 0x4
        elif (self.channels == 2):
            self.channel_mask = 0x3
        else:
            self.channel_mask = 0

    def unsigned(self, c):
        MSB = self.reader.unary(1)
        LSB = self.reader.read(c)
        return MSB * 2**c + LSB

    def signed(self, c):
        u = self.unsigned(c + 1)
        if ((u % 2) == 0):
            return u // 2
        else:
            return -(u // 2) - 1

    def long(self):
        return self.unsigned(self.unsigned(2))

    def skip_unsigned(self, c):
        self.reader.skip_unary(1)
        self.reader.skip(c)

    def read_header(self):
        magic = self.reader.read_bytes(4)
        if (magic != b"ajkg"):
            raise ValueError("invalid magic number")
        version = self.reader.read(8)
        if (version != 2):
            raise ValueError("unsupported version")

        file_type = self.long()
        channels = self.long()
        block_length = self.long()
        max_LPC = self.long()
        number_of_means = self.long()
        bytes_to_skip = self.long()
        self.reader.read_bytes(bytes_to_skip)

        return (file_type, channels, block_length, max_LPC, number_of_means)

    def read(self, pcm_frames):
        if (self.stream_finished):
            return from_channels([
                empty_framelist(1, self.bits_per_sample)
                for channel in range(self.channels)
            ])

        c = 0
        samples = []
        unshifted = []
        while (True):
            command = self.unsigned(2)
            if (((0 <= command) and (command <= 3)
                 or (7 <= command) and (command <= 8))):
                # audio data commands
                if (command == 0):  # DIFF0
                    samples.append(
                        self.read_diff0(self.block_length, self.means[c]))
                elif (command == 1):  # DIFF1
                    samples.append(
                        self.read_diff1(self.block_length,
                                        self.wrapped_samples[c]))
                elif (command == 2):  # DIFF2
                    samples.append(
                        self.read_diff2(self.block_length,
                                        self.wrapped_samples[c]))
                elif (command == 3):  # DIFF3
                    samples.append(
                        self.read_diff3(self.block_length,
                                        self.wrapped_samples[c]))
                elif (command == 7):  # QLPC
                    samples.append(
                        self.read_qlpc(self.block_length, self.means[c],
                                       self.wrapped_samples[c]))
                elif (command == 8):  # ZERO
                    samples.append([0] * self.block_length)

                # update means for channel
                self.means[c].append(shnmean(samples[c]))
                self.means[c] = self.means[c][1:]

                # wrap samples for next command in channel
                self.wrapped_samples[c] = samples[c][-(max(3, self.max_LPC)):]

                # apply left shift to samples
                if (self.left_shift > 0):
                    unshifted.append(
                        [s << self.left_shift for s in samples[c]])
                else:
                    unshifted.append(samples[c])

                c += 1
                if (c == self.channels):
                    # return a FrameList from shifted data
                    return from_channels([
                        from_list(channel, 1, self.bits_per_sample,
                                  self.signed_samples) for channel in unshifted
                    ])
            else:
                # non audio commands
                if (command == 4):  # QUIT
                    self.stream_finished = True
                    return from_channels([
                        empty_framelist(1, self.bits_per_sample)
                        for channel in range(self.channels)
                    ])
                elif (command == 5):  # BLOCKSIZE
                    self.block_length = self.long()
                elif (command == 6):  # BITSHIFT
                    self.left_shift = self.unsigned(2)
                elif (command == 9):  # VERBATIM
                    # skip this command during reading
                    size = self.unsigned(5)
                    for i in range(size):
                        self.skip_unsigned(8)
                else:
                    raise ValueError("unsupported Shorten command")

    def read_diff0(self, block_length, means):
        offset = shnmean(means)
        energy = self.unsigned(3)
        samples = []
        for i in range(block_length):
            residual = self.signed(energy)
            samples.append(residual + offset)
        return samples

    def read_diff1(self, block_length, previous_samples):
        samples = previous_samples[-1:]
        energy = self.unsigned(3)
        for i in range(1, block_length + 1):
            residual = self.signed(energy)
            samples.append(samples[i - 1] + residual)
        return samples[1:]

    def read_diff2(self, block_length, previous_samples):
        samples = previous_samples[-2:]
        energy = self.unsigned(3)
        for i in range(2, block_length + 2):
            residual = self.signed(energy)
            samples.append((2 * samples[i - 1]) - samples[i - 2] + residual)
        return samples[2:]

    def read_diff3(self, block_length, previous_samples):
        samples = previous_samples[-3:]
        energy = self.unsigned(3)
        for i in range(3, block_length + 3):
            residual = self.signed(energy)
            samples.append((3 * (samples[i - 1] - samples[i - 2])) +
                           samples[i - 3] + residual)
        return samples[3:]

    def read_qlpc(self, block_length, means, previous_samples):
        offset = shnmean(means)
        energy = self.unsigned(3)
        LPC_count = self.unsigned(2)
        LPC_coeff = [self.signed(5) for i in range(LPC_count)]
        unoffset = []
        samples = previous_samples[-LPC_count:]
        for i in range(block_length):
            residual = self.signed(energy)
            LPC_sum = 2**5
            for j in range(LPC_count):
                if ((i - j - 1) < 0):
                    # apply offset to warm-up samples
                    LPC_sum += (LPC_coeff[j] * (samples[LPC_count +
                                                        (i - j - 1)] - offset))
                else:
                    LPC_sum += LPC_coeff[j] * unoffset[i - j - 1]
            unoffset.append(LPC_sum // 2**5 + residual)
        return [u + offset for u in unoffset]

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

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()
Beispiel #16
0
class AuReader(object):
    def __init__(self, au_filename):
        from audiotools.bitstream import BitstreamReader
        from audiotools.text import (ERR_AU_INVALID_HEADER,
                                     ERR_AU_UNSUPPORTED_FORMAT)

        self.stream = BitstreamReader(open(au_filename, "rb"), False)
        (magic_number, self.data_offset, data_size, encoding_format,
         self.sample_rate, self.channels) = self.stream.parse("4b 5* 32u")

        if (magic_number != b'.snd'):
            self.stream.close()
            raise ValueError(ERR_AU_INVALID_HEADER)
        try:
            self.bits_per_sample = {2: 8, 3: 16, 4: 24}[encoding_format]
        except KeyError:
            self.stream.close()
            raise ValueError(ERR_AU_UNSUPPORTED_FORMAT)

        self.channel_mask = {1: 0x4, 2: 0x3}.get(self.channels, 0)
        self.bytes_per_pcm_frame = ((self.bits_per_sample // 8) *
                                    self.channels)
        self.total_pcm_frames = (data_size // self.bytes_per_pcm_frame)
        self.remaining_pcm_frames = self.total_pcm_frames

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    def read(self, pcm_frames):
        # try to read requested PCM frames or remaining frames
        requested_pcm_frames = min(max(pcm_frames, 1),
                                   self.remaining_pcm_frames)
        requested_bytes = (self.bytes_per_pcm_frame * requested_pcm_frames)
        pcm_data = self.stream.read_bytes(requested_bytes)

        # raise exception if data block exhausted early
        if (len(pcm_data) < requested_bytes):
            from audiotools.text import ERR_AU_TRUNCATED_DATA
            raise IOError(ERR_AU_TRUNCATED_DATA)
        else:
            self.remaining_pcm_frames -= requested_pcm_frames

            # return parsed chunk
            return FrameList(pcm_data, self.channels, self.bits_per_sample,
                             True, True)

    def read_closed(self, pcm_frames):
        raise ValueError("cannot read closed stream")

    def seek(self, pcm_frame_offset):
        if (pcm_frame_offset < 0):
            from audiotools.text import ERR_NEGATIVE_SEEK
            raise ValueError(ERR_NEGATIVE_SEEK)

        # ensure one doesn't walk off the end of the file
        pcm_frame_offset = min(pcm_frame_offset, self.total_pcm_frames)

        # position file in data block
        self.stream.seek(
            self.data_offset + (pcm_frame_offset * self.bytes_per_pcm_frame),
            0)
        self.remaining_pcm_frames = (self.total_pcm_frames - pcm_frame_offset)

        return pcm_frame_offset

    def seek_closed(self, pcm_frame_offset):
        raise ValueError("cannot seek closed stream")

    def close(self):
        self.stream.close()
        self.read = self.read_closed
        self.seek = self.seek_closed
Beispiel #17
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()
Beispiel #18
0
class SHNDecoder(object):
    def __init__(self, filename):
        self.reader = BitstreamReader(open(filename, "rb"), False)

        (self.file_type,
         self.channels,
         self.block_length,
         self.max_LPC,
         self.number_of_means) = self.read_header()

        if (1 <= self.file_type) and (self.file_type <= 2):
            self.bits_per_sample = 8
            self.signed_samples = (self.file_type == 1)
        elif (3 <= self.file_type) and (self.file_type <= 6):
            self.bits_per_sample = 16
            self.signed_samples = (self.file_type in (3, 5))
        else:
            raise ValueError("unsupported Shorten file type")

        self.wrapped_samples = [[0] * 3 for c in range(self.channels)]
        self.means = [[0] * self.number_of_means
                      for c in range(self.channels)]
        self.left_shift = 0
        self.stream_finished = False

        # try to read the first command for a wave/aiff header
        data_start = self.reader.getpos()
        self.read_metadata()
        self.reader.setpos(data_start)

    def read_metadata(self):
        from io import BytesIO

        command = self.unsigned(2)
        if command == 9:
            # got verbatim, so read data
            verbatim_bytes = ints_to_bytes([self.unsigned(8) & 0xFF
                                            for i in range(self.unsigned(5))])

            try:
                wave = BitstreamReader(BytesIO(verbatim_bytes), True)
                header = wave.read_bytes(12)
                if header.startswith(b"RIFF") and header.endswith(b"WAVE"):
                    # got RIFF/WAVE header, so parse wave blocks as needed
                    total_size = len(verbatim_bytes) - 12
                    while total_size > 0:
                        (chunk_id, chunk_size) = wave.parse("4b 32u")
                        total_size -= 8
                        if chunk_id == b'fmt ':
                            (channels,
                             self.sample_rate,
                             bits_per_sample,
                             channel_mask) = parse_fmt(
                                wave.substream(chunk_size))
                            self.channel_mask = int(channel_mask)
                            return
                        else:
                            if chunk_size % 2:
                                wave.read_bytes(chunk_size + 1)
                                total_size -= (chunk_size + 1)
                            else:
                                wave.read_bytes(chunk_size)
                                total_size -= chunk_size
                    else:
                        # no fmt chunk, so use default metadata
                        pass
            except (IOError, ValueError):
                pass

            try:
                aiff = BitstreamReader(BytesIO(verbatim_bytes), False)
                header = aiff.read_bytes(12)
                if header.startswith(b"FORM") and header.endswith(b"AIFF"):
                    # got FORM/AIFF header, so parse aiff blocks as needed
                    total_size = len(verbatim_bytes) - 12
                    while total_size > 0:
                        (chunk_id, chunk_size) = aiff.parse("4b 32u")
                        total_size -= 8
                        if chunk_id == b'COMM':
                            (channels,
                             total_sample_frames,
                             bits_per_sample,
                             self.sample_rate,
                             channel_mask) = parse_comm(
                                aiff.substream(chunk_size))
                            self.channel_mask = int(channel_mask)
                            return
                        else:
                            if chunk_size % 2:
                                aiff.read_bytes(chunk_size + 1)
                                total_size -= (chunk_size + 1)
                            else:
                                aiff.read_bytes(chunk_size)
                                total_size -= chunk_size
                    else:
                        # no COMM chunk, so use default metadata
                        pass
            except IOError:
                pass

        # got something else, so invent some PCM parameters
        self.sample_rate = 44100
        if self.channels == 1:
            self.channel_mask = 0x4
        elif self.channels == 2:
            self.channel_mask = 0x3
        else:
            self.channel_mask = 0

    def unsigned(self, c):
        MSB = self.reader.unary(1)
        LSB = self.reader.read(c)
        return MSB * 2 ** c + LSB

    def signed(self, c):
        u = self.unsigned(c + 1)
        if (u % 2) == 0:
            return u // 2
        else:
            return -(u // 2) - 1

    def long(self):
        return self.unsigned(self.unsigned(2))

    def skip_unsigned(self, c):
        self.reader.skip_unary(1)
        self.reader.skip(c)

    def read_header(self):
        magic = self.reader.read_bytes(4)
        if magic != b"ajkg":
            raise ValueError("invalid magic number")
        version = self.reader.read(8)
        if version != 2:
            raise ValueError("unsupported version")

        file_type = self.long()
        channels = self.long()
        block_length = self.long()
        max_LPC = self.long()
        number_of_means = self.long()
        bytes_to_skip = self.long()
        self.reader.read_bytes(bytes_to_skip)

        return (file_type, channels, block_length, max_LPC, number_of_means)

    def read(self, pcm_frames):
        if self.stream_finished:
            return from_channels([empty_framelist(1, self.bits_per_sample)
                                  for channel in range(self.channels)])

        c = 0
        samples = []
        unshifted = []
        while True:
            command = self.unsigned(2)
            if (((0 <= command) and (command <= 3) or
                 (7 <= command) and (command <= 8))):
                # audio data commands
                if command == 0:    # DIFF0
                    samples.append(self.read_diff0(self.block_length,
                                                   self.means[c]))
                elif command == 1:  # DIFF1
                    samples.append(self.read_diff1(self.block_length,
                                                   self.wrapped_samples[c]))
                elif command == 2:  # DIFF2
                    samples.append(self.read_diff2(self.block_length,
                                                   self.wrapped_samples[c]))
                elif command == 3:  # DIFF3
                    samples.append(self.read_diff3(self.block_length,
                                                   self.wrapped_samples[c]))
                elif command == 7:  # QLPC
                    samples.append(self.read_qlpc(self.block_length,
                                                  self.means[c],
                                                  self.wrapped_samples[c]))
                elif command == 8:  # ZERO
                    samples.append([0] * self.block_length)

                # update means for channel
                self.means[c].append(shnmean(samples[c]))
                self.means[c] = self.means[c][1:]

                # wrap samples for next command in channel
                self.wrapped_samples[c] = samples[c][-(max(3, self.max_LPC)):]

                # apply left shift to samples
                if self.left_shift > 0:
                    unshifted.append([s << self.left_shift
                                      for s in samples[c]])
                else:
                    unshifted.append(samples[c])

                c += 1
                if c == self.channels:
                    # return a FrameList from shifted data
                    return from_channels([from_list(channel, 1,
                                                    self.bits_per_sample,
                                                    self.signed_samples)
                                          for channel in unshifted])
            else:
                # non audio commands
                if command == 4:  # QUIT
                    self.stream_finished = True
                    return from_channels(
                        [empty_framelist(1, self.bits_per_sample)
                         for channel in range(self.channels)])
                elif command == 5:  # BLOCKSIZE
                    self.block_length = self.long()
                elif command == 6:  # BITSHIFT
                    self.left_shift = self.unsigned(2)
                elif command == 9:  # VERBATIM
                    # skip this command during reading
                    size = self.unsigned(5)
                    for i in range(size):
                        self.skip_unsigned(8)
                else:
                    raise ValueError("unsupported Shorten command")

    def read_diff0(self, block_length, means):
        offset = shnmean(means)
        energy = self.unsigned(3)
        samples = []
        for i in range(block_length):
            residual = self.signed(energy)
            samples.append(residual + offset)
        return samples

    def read_diff1(self, block_length, previous_samples):
        samples = previous_samples[-1:]
        energy = self.unsigned(3)
        for i in range(1, block_length + 1):
            residual = self.signed(energy)
            samples.append(samples[i - 1] + residual)
        return samples[1:]

    def read_diff2(self, block_length, previous_samples):
        samples = previous_samples[-2:]
        energy = self.unsigned(3)
        for i in range(2, block_length + 2):
            residual = self.signed(energy)
            samples.append((2 * samples[i - 1]) - samples[i - 2] + residual)
        return samples[2:]

    def read_diff3(self, block_length, previous_samples):
        samples = previous_samples[-3:]
        energy = self.unsigned(3)
        for i in range(3, block_length + 3):
            residual = self.signed(energy)
            samples.append((3 * (samples[i - 1] - samples[i - 2])) +
                           samples[i - 3] + residual)
        return samples[3:]

    def read_qlpc(self, block_length, means, previous_samples):
        offset = shnmean(means)
        energy = self.unsigned(3)
        LPC_count = self.unsigned(2)
        LPC_coeff = [self.signed(5) for i in range(LPC_count)]
        unoffset = []
        samples = previous_samples[-LPC_count:]
        for i in range(block_length):
            residual = self.signed(energy)
            LPC_sum = 2 ** 5
            for j in range(LPC_count):
                if (i - j - 1) < 0:
                    # apply offset to warm-up samples
                    LPC_sum += (LPC_coeff[j] *
                                (samples[LPC_count + (i - j - 1)] - offset))
                else:
                    LPC_sum += LPC_coeff[j] * unoffset[i - j - 1]
            unoffset.append(LPC_sum // 2 ** 5 + residual)
        return [u + offset for u in unoffset]

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

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()
Beispiel #19
0
class FlacDecoder:
    CHANNEL_COUNT = [1, 2, 3, 4, 5, 6, 7, 8, 2, 2, 2,
                     None, None, None, None, None]

    (SUBFRAME_CONSTANT,
     SUBFRAME_VERBATIM,
     SUBFRAME_FIXED,
     SUBFRAME_LPC) = range(4)

    def __init__(self, filename, channel_mask):
        self.reader = BitstreamReader(open(filename, "rb"), 0)

        if (self.reader.read_bytes(4) != 'fLaC'):
            raise ValueError("invalid FLAC file")

        self.current_md5sum = md5()

        #locate the STREAMINFO,
        #which is sometimes needed to handle non-subset streams
        for (block_id,
             block_size,
             block_reader) in self.metadata_blocks(self.reader):
            if (block_id == 0):
                #read STREAMINFO
                self.minimum_block_size = block_reader.read(16)
                self.maximum_block_size = block_reader.read(16)
                self.minimum_frame_size = block_reader.read(24)
                self.maximum_frame_size = block_reader.read(24)
                self.sample_rate = block_reader.read(20)
                self.channels = block_reader.read(3) + 1
                self.channel_mask = channel_mask
                self.bits_per_sample = block_reader.read(5) + 1
                self.total_frames = block_reader.read64(36)
                self.md5sum = block_reader.read_bytes(16)

                #these are frame header lookup tables
                #which vary slightly depending on STREAMINFO's values
                self.BLOCK_SIZE = [self.maximum_block_size,
                                   192,  576,  1152,
                                   2304, 4608,  None,  None,
                                   256,  512,  1024,  2048,
                                   4096, 8192, 16384, 32768]
                self.SAMPLE_RATE = [self.sample_rate,
                                    88200, 176400, 192000,
                                    8000,  16000,  22050, 24000,
                                    32000,  44100,  48000, 96000,
                                    None,   None,   None,  None]
                self.BITS_PER_SAMPLE = [self.bits_per_sample,
                                        8, 12, None, 16, 20, 24, None]

    def metadata_blocks(self, reader):
        """yields a (block_id, block_size, block_reader) tuple
        per metadata block where block_reader is a BitstreamReader substream"""

        (last_block, block_id, block_size) = self.reader.parse("1u 7u 24u")
        while (last_block == 0):
            yield (block_id, block_size, self.reader.substream(block_size))
            (last_block, block_id, block_size) = self.reader.parse("1u 7u 24u")
        else:
            yield (block_id, block_size, self.reader.substream(block_size))

    def read(self, pcm_frames):
        #if the stream is exhausted,
        #verify its MD5 sum and return an empty pcm.FrameList object
        if (self.total_frames < 1):
            if (self.md5sum == self.current_md5sum.digest()):
                return from_list([], self.channels, self.bits_per_sample, True)
            else:
                raise ValueError("MD5 checksum mismatch")

        crc16 = CRC16()
        self.reader.add_callback(crc16.update)

        #fetch the decoding parameters from the frame header
        (block_size,
         channel_assignment,
         bits_per_sample) = self.read_frame_header()
        channel_count = self.CHANNEL_COUNT[channel_assignment]
        if (channel_count is None):
            raise ValueError("invalid channel assignment")

        #channel data will be a list of signed sample lists, one per channel
        #such as  [[1, 2, 3, ...], [4, 5, 6, ...]]  for a 2 channel stream
        channel_data = []

        for channel_number in xrange(channel_count):
            if ((channel_assignment == 0x8) and (channel_number == 1)):
                #for left-difference assignment
                #the difference channel has 1 additional bit
                channel_data.append(self.read_subframe(block_size,
                                                       bits_per_sample + 1))
            elif ((channel_assignment == 0x9) and (channel_number == 0)):
                #for difference-right assignment
                #the difference channel has 1 additional bit
                channel_data.append(self.read_subframe(block_size,
                                                       bits_per_sample + 1))
            elif ((channel_assignment == 0xA) and (channel_number == 1)):
                #for mid-side assignment
                #the side channel has 1 additional bit
                channel_data.append(self.read_subframe(block_size,
                                                       bits_per_sample + 1))
            else:
                #otherwise, use the frame's bits-per-sample value
                channel_data.append(self.read_subframe(block_size,
                                                       bits_per_sample))

        #one all the subframes have been decoded,
        #reconstruct them depending on the channel assignment
        if (channel_assignment == 0x8):
            #left-difference
            samples = []
            for (left, difference) in zip(*channel_data):
                samples.append(left)
                samples.append(left - difference)
        elif (channel_assignment == 0x9):
            #difference-right
            samples = []
            for (difference, right) in zip(*channel_data):
                samples.append(difference + right)
                samples.append(right)
        elif (channel_assignment == 0xA):
            #mid-side
            samples = []
            for (mid, side) in zip(*channel_data):
                samples.append((((mid * 2) + (side % 2)) + side) / 2)
                samples.append((((mid * 2) + (side % 2)) - side) / 2)
        else:
            #independent
            samples = [0] * block_size * channel_count
            for (i, channel) in enumerate(channel_data):
                samples[i::channel_count] = channel

        self.reader.byte_align()

        #read and verify the frame's trailing CRC-16 footer
        self.reader.read(16)
        self.reader.pop_callback()
        if (int(crc16) != 0):
            raise ValueError("CRC16 mismatch in frame footer")

        #deduct the amount of PCM frames from the remaining amount
        self.total_frames -= block_size

        #build a pcm.FrameList object from the combined samples
        framelist = from_list(samples, channel_count, bits_per_sample, True)

        #update the running MD5 sum calculation with the frame's data
        self.current_md5sum.update(framelist.to_bytes(0, 1))

        #and finally return the frame data
        return framelist

    def read_frame_header(self):
        crc8 = CRC8()
        self.reader.add_callback(crc8.update)

        #read the 32-bit FLAC frame header
        sync_code = self.reader.read(14)
        if (sync_code != 0x3FFE):
            raise ValueError("invalid sync code")

        self.reader.skip(1)
        blocking_strategy = self.reader.read(1)
        block_size_bits = self.reader.read(4)
        sample_rate_bits = self.reader.read(4)
        channel_assignment = self.reader.read(4)
        bits_per_sample_bits = self.reader.read(3)
        self.reader.skip(1)

        #the frame number is a UTF-8 encoded value
        #which takes a variable number of whole bytes
        frame_number = self.read_utf8()

        #unpack the 4 bit block size field
        #which is the total PCM frames in the FLAC frame
        #and may require up to 16 more bits if the frame is usually-sized
        #(which typically happens at the end of the stream)
        if (block_size_bits == 0x6):
            block_size = self.reader.read(8) + 1
        elif (block_size_bits == 0x7):
            block_size = self.reader.read(16) + 1
        else:
            block_size = self.BLOCK_SIZE[block_size_bits]

        #unpack the 4 bit sample rate field
        #which is used for playback, but not needed for decoding
        #and may require up to 16 more bits
        #if the stream has a particularly unusual sample rate
        if (sample_rate_bits == 0xC):
            sample_rate = self.reader.read(8) * 1000
        elif (sample_rate_bits == 0xD):
            sample_rate = self.reader.read(16)
        elif (sample_rate_bits == 0xE):
            sample_rate = self.reader.read(16) * 10
        elif (sample_rate_bits == 0xF):
            raise ValueError("invalid sample rate")
        else:
            sample_rate = self.SAMPLE_RATE[sample_rate_bits]

        #unpack the 3 bit bits-per-sample field
        #this never requires additional bits
        if ((bits_per_sample_bits == 0x3) or (bits_per_sample_bits == 0x7)):
            raise ValueError("invalid bits per sample")
        else:
            bits_per_sample = self.BITS_PER_SAMPLE[bits_per_sample_bits]

        #read and verify frame's CRC-8 value
        self.reader.read(8)
        self.reader.pop_callback()
        if (int(crc8) != 0):
            raise ValueError("CRC8 mismatch in frame header")

        return (block_size, channel_assignment, bits_per_sample)

    def read_subframe_header(self):
        """returns a tuple of (subframe_type, subframe_order, wasted_bps)"""

        self.reader.skip(1)
        subframe_type = self.reader.read(6)
        if (self.reader.read(1) == 1):
            wasted_bps = self.reader.unary(1) + 1
        else:
            wasted_bps = 0

        #extract "order" value from 6 bit subframe type, if necessary
        if (subframe_type == 0):
            return (self.SUBFRAME_CONSTANT, None, wasted_bps)
        elif (subframe_type == 1):
            return (self.SUBFRAME_VERBATIM, None, wasted_bps)
        elif ((subframe_type & 0x38) == 0x08):
            return (self.SUBFRAME_FIXED, subframe_type & 0x07, wasted_bps)
        elif ((subframe_type & 0x20) == 0x20):
            return (self.SUBFRAME_LPC, (subframe_type & 0x1F) + 1, wasted_bps)
        else:
            raise ValueError("invalid subframe type")

    def read_subframe(self, block_size, bits_per_sample):
        (subframe_type,
         subframe_order,
         wasted_bps) = self.read_subframe_header()

        #read a list of signed sample values
        #depending on the subframe type, block size,
        #adjusted bits per sample and optional subframe order
        if (subframe_type == self.SUBFRAME_CONSTANT):
            subframe_samples = self.read_constant_subframe(
                block_size, bits_per_sample - wasted_bps)
        elif (subframe_type == self.SUBFRAME_VERBATIM):
            subframe_samples = self.read_verbatim_subframe(
                block_size, bits_per_sample - wasted_bps)
        elif (subframe_type == self.SUBFRAME_FIXED):
            subframe_samples = self.read_fixed_subframe(
                block_size, bits_per_sample - wasted_bps, subframe_order)
        else:
            subframe_samples = self.read_lpc_subframe(
                block_size, bits_per_sample - wasted_bps, subframe_order)

        #account for wasted bits-per-sample, if necessary
        if (wasted_bps):
            return [sample << wasted_bps for sample in subframe_samples]
        else:
            return subframe_samples

    def read_constant_subframe(self, block_size, bits_per_sample):
        sample = self.reader.read_signed(bits_per_sample)
        return [sample] * block_size

    def read_verbatim_subframe(self, block_size, bits_per_sample):
        return [self.reader.read_signed(bits_per_sample)
                for x in xrange(block_size)]

    def read_fixed_subframe(self, block_size, bits_per_sample, order):
        #"order" number of warm-up samples
        samples = [self.reader.read_signed(bits_per_sample)
                   for i in xrange(order)]

        #"block_size" - "order" number of residual values
        residuals = self.read_residual(block_size, order)

        #which are applied to the warm-up samples
        #depending on the FIXED subframe order
        #and results in "block_size" number of total samples
        if (order == 0):
            return residuals
        elif (order == 1):
            for residual in residuals:
                samples.append(
                    samples[-1] +
                    residual)
            return samples
        elif (order == 2):
            for residual in residuals:
                samples.append(
                    (2 * samples[-1]) -
                    samples[-2] +
                    residual)
            return samples
        elif (order == 3):
            for residual in residuals:
                samples.append(
                    (3 * samples[-1]) -
                    (3 * samples[-2]) +
                    samples[-3] +
                    residual)
            return samples
        elif (order == 4):
            for residual in residuals:
                samples.append(
                    (4 * samples[-1]) -
                    (6 * samples[-2]) +
                    (4 * samples[-3]) -
                    samples[-4] +
                    residual)
            return samples
        else:
            raise ValueError("unsupported FIXED subframe order")

    def read_lpc_subframe(self, block_size, bits_per_sample, order):
        #"order" number of warm-up samples
        samples = [self.reader.read_signed(bits_per_sample)
                   for i in xrange(order)]

        #the size of each QLP coefficient, in bits
        qlp_precision = self.reader.read(4)

        #the amount of right shift to apply
        #during LPC calculation
        #(though this is a signed value, negative shifts are noops
        # in the reference FLAC decoder)
        qlp_shift_needed = max(self.reader.read_signed(5), 0)

        #"order" number of signed QLP coefficients
        qlp_coeffs = [self.reader.read_signed(qlp_precision + 1)
                      for i in xrange(order)]
        #QLP coefficients are applied in reverse order
        qlp_coeffs.reverse()

        #"block_size" - "order" number of residual values
        residuals = self.read_residual(block_size, order)

        #which are applied to the running LPC calculation
        for residual in residuals:
            samples.append((sum([coeff * sample for (coeff, sample) in
                                 zip(qlp_coeffs, samples[-order:])]) >>
                            qlp_shift_needed) + residual)

        return samples

    def read_residual(self, block_size, order):
        residuals = []

        coding_method = self.reader.read(2)
        partition_order = self.reader.read(4)

        #each parititon contains  block_size / 2 ** partition_order
        #number of residuals
        for partition_number in xrange(2 ** partition_order):
            if (partition_number == 0):
                #except for the first partition
                #which contains "order" less than the rest
                residuals.extend(
                    self.read_residual_partition(
                        coding_method,
                        (block_size / 2 ** partition_order) - order))
            else:
                residuals.extend(
                    self.read_residual_partition(
                        coding_method,
                        block_size / 2 ** partition_order))

        return residuals

    def read_residual_partition(self, coding_method, residual_count):
        if (coding_method == 0):
            #the Rice parameters determines the number of
            #least-significant bits to read for each residual
            rice_parameter = self.reader.read(4)
            if (rice_parameter == 0xF):
                escape_code = self.reader.read(5)
                return [self.reader.read_signed(escape_code)
                        for i in xrange(residual_count)]
        elif (coding_method == 1):
            #24 bps streams may use a 5-bit Rice parameter
            #for better compression
            rice_parameter = self.reader.read(5)
            if (rice_parameter == 0x1F):
                escape_code = self.reader.read(5)
                return [self.reader.read_signed(escape_code)
                        for i in xrange(residual_count)]
        else:
            raise ValueError("invalid Rice coding parameter")

        #a list of signed residual values
        partition_residuals = []

        for i in xrange(residual_count):
            msb = self.reader.unary(1)              # most-significant bits
            lsb = self.reader.read(rice_parameter)  # least-significant bits
            value = (msb << rice_parameter) | lsb   # combined into a value
            if (value & 1):   # whose least-significant bit is the sign value
                partition_residuals.append(-(value >> 1) - 1)
            else:
                partition_residuals.append(value >> 1)

        return partition_residuals

    def read_utf8(self):
        total_bytes = self.reader.unary(0)
        value = self.reader.read(7 - total_bytes)
        while (total_bytes > 1):
            value = ((value << 6) | self.reader.parse("2p 6u")[0])
            total_bytes -= 1
        return value

    def close(self):
        self.reader.close()
Beispiel #20
0
    def read_metadata(self):
        from io import BytesIO

        command = self.unsigned(2)
        if command == 9:
            # got verbatim, so read data
            verbatim_bytes = ints_to_bytes([self.unsigned(8) & 0xFF
                                            for i in range(self.unsigned(5))])

            try:
                wave = BitstreamReader(BytesIO(verbatim_bytes), True)
                header = wave.read_bytes(12)
                if header.startswith(b"RIFF") and header.endswith(b"WAVE"):
                    # got RIFF/WAVE header, so parse wave blocks as needed
                    total_size = len(verbatim_bytes) - 12
                    while total_size > 0:
                        (chunk_id, chunk_size) = wave.parse("4b 32u")
                        total_size -= 8
                        if chunk_id == b'fmt ':
                            (channels,
                             self.sample_rate,
                             bits_per_sample,
                             channel_mask) = parse_fmt(
                                wave.substream(chunk_size))
                            self.channel_mask = int(channel_mask)
                            return
                        else:
                            if chunk_size % 2:
                                wave.read_bytes(chunk_size + 1)
                                total_size -= (chunk_size + 1)
                            else:
                                wave.read_bytes(chunk_size)
                                total_size -= chunk_size
                    else:
                        # no fmt chunk, so use default metadata
                        pass
            except (IOError, ValueError):
                pass

            try:
                aiff = BitstreamReader(BytesIO(verbatim_bytes), False)
                header = aiff.read_bytes(12)
                if header.startswith(b"FORM") and header.endswith(b"AIFF"):
                    # got FORM/AIFF header, so parse aiff blocks as needed
                    total_size = len(verbatim_bytes) - 12
                    while total_size > 0:
                        (chunk_id, chunk_size) = aiff.parse("4b 32u")
                        total_size -= 8
                        if chunk_id == b'COMM':
                            (channels,
                             total_sample_frames,
                             bits_per_sample,
                             self.sample_rate,
                             channel_mask) = parse_comm(
                                aiff.substream(chunk_size))
                            self.channel_mask = int(channel_mask)
                            return
                        else:
                            if chunk_size % 2:
                                aiff.read_bytes(chunk_size + 1)
                                total_size -= (chunk_size + 1)
                            else:
                                aiff.read_bytes(chunk_size)
                                total_size -= chunk_size
                    else:
                        # no COMM chunk, so use default metadata
                        pass
            except IOError:
                pass

        # got something else, so invent some PCM parameters
        self.sample_rate = 44100
        if self.channels == 1:
            self.channel_mask = 0x4
        elif self.channels == 2:
            self.channel_mask = 0x3
        else:
            self.channel_mask = 0
Beispiel #21
0
class AuReader(object):
    def __init__(self, au_filename):
        from audiotools.bitstream import BitstreamReader
        from audiotools.text import (ERR_AU_INVALID_HEADER,
                                     ERR_AU_UNSUPPORTED_FORMAT)

        self.stream = BitstreamReader(open(au_filename, "rb"), False)
        (magic_number,
         self.data_offset,
         data_size,
         encoding_format,
         self.sample_rate,
         self.channels) = self.stream.parse("4b 5* 32u")

        if magic_number != b'.snd':
            self.stream.close()
            raise ValueError(ERR_AU_INVALID_HEADER)
        try:
            self.bits_per_sample = {2: 8, 3: 16, 4: 24}[encoding_format]
        except KeyError:
            self.stream.close()
            raise ValueError(ERR_AU_UNSUPPORTED_FORMAT)

        self.channel_mask = {1: 0x4, 2: 0x3}.get(self.channels, 0)
        self.bytes_per_pcm_frame = ((self.bits_per_sample // 8) *
                                    self.channels)
        self.total_pcm_frames = (data_size // self.bytes_per_pcm_frame)
        self.remaining_pcm_frames = self.total_pcm_frames

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    def read(self, pcm_frames):
        # try to read requested PCM frames or remaining frames
        requested_pcm_frames = min(max(pcm_frames, 1),
                                   self.remaining_pcm_frames)
        requested_bytes = (self.bytes_per_pcm_frame *
                           requested_pcm_frames)
        pcm_data = self.stream.read_bytes(requested_bytes)

        # raise exception if data block exhausted early
        if len(pcm_data) < requested_bytes:
            from audiotools.text import ERR_AU_TRUNCATED_DATA
            raise IOError(ERR_AU_TRUNCATED_DATA)
        else:
            self.remaining_pcm_frames -= requested_pcm_frames

            # return parsed chunk
            return FrameList(pcm_data,
                             self.channels,
                             self.bits_per_sample,
                             True,
                             True)

    def read_closed(self, pcm_frames):
        raise ValueError("cannot read closed stream")

    def seek(self, pcm_frame_offset):
        if pcm_frame_offset < 0:
            from audiotools.text import ERR_NEGATIVE_SEEK
            raise ValueError(ERR_NEGATIVE_SEEK)

        # ensure one doesn't walk off the end of the file
        pcm_frame_offset = min(pcm_frame_offset,
                               self.total_pcm_frames)

        # position file in data block
        self.stream.seek(self.data_offset +
                         (pcm_frame_offset *
                          self.bytes_per_pcm_frame), 0)
        self.remaining_pcm_frames = (self.total_pcm_frames -
                                     pcm_frame_offset)

        return pcm_frame_offset

    def seek_closed(self, pcm_frame_offset):
        raise ValueError("cannot seek closed stream")

    def close(self):
        self.stream.close()
        self.read = self.read_closed
        self.seek = self.seek_closed
Beispiel #22
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()