Ejemplo n.º 1
0
    def __init__(self, filename):
        from audiotools.id3 import skip_id3v2_comment

        AudioFile.__init__(self, filename)

        try:
            with open(filename, "rb") as f:
                skip_id3v2_comment(f)

                from audiotools.bitstream import BitstreamReader
                from audiotools.text import (ERR_TTA_INVALID_SIGNATURE,
                                             ERR_TTA_INVALID_FORMAT)

                reader = BitstreamReader(f, True)

                (signature, format_, self.__channels__,
                 self.__bits_per_sample__, self.__sample_rate__,
                 self.__total_pcm_frames__
                 ) = reader.parse("4b 16u 16u 16u 32u 32u 32p")

                if signature != b"TTA1":
                    raise InvalidTTA(ERR_TTA_INVALID_SIGNATURE)
                elif format_ != 1:
                    raise InvalidTTA(ERR_TTA_INVALID_FORMAT)

                self.__total_tta_frames__ = div_ceil(
                    self.__total_pcm_frames__ * 245,
                    self.__sample_rate__ * 256)
                self.__frame_lengths__ = list(
                    reader.parse(
                        "{:d}* 32u".format(self.__total_tta_frames__) + "32p"))
        except IOError as msg:
            raise InvalidTTA(str(msg))
Ejemplo n.º 2
0
    def __init__(self, filename):
        from audiotools.id3 import skip_id3v2_comment

        AudioFile.__init__(self, filename)

        try:
            with open(filename, "rb") as f:
                skip_id3v2_comment(f)

                from audiotools.bitstream import BitstreamReader
                from audiotools.text import (ERR_TTA_INVALID_SIGNATURE,
                                             ERR_TTA_INVALID_FORMAT)

                reader = BitstreamReader(f, True)

                (signature,
                 format_,
                 self.__channels__,
                 self.__bits_per_sample__,
                 self.__sample_rate__,
                 self.__total_pcm_frames__) = reader.parse(
                    "4b 16u 16u 16u 32u 32u 32p")

                if (signature != b"TTA1"):
                    raise InvalidTTA(ERR_TTA_INVALID_SIGNATURE)
                elif (format_ != 1):
                    raise InvalidTTA(ERR_TTA_INVALID_FORMAT)

                self.__total_tta_frames__ = div_ceil(
                    self.__total_pcm_frames__ * 245,
                    self.__sample_rate__ * 256)
                self.__frame_lengths__ = list(reader.parse(
                    "%d* 32u" % (self.__total_tta_frames__) + "32p"))
        except IOError as msg:
            raise InvalidTTA(str(msg))
Ejemplo n.º 3
0
    def blocks(self, reader=None):
        """yields (length, reader) tuples of WavPack frames

        length is the total length of all the substreams
        reader is a BitstreamReader which can be parsed
        """

        def blocks_iter(reader):
            try:
                while (True):
                    (wvpk, block_size) = reader.parse("4b 32u 192p")
                    if (wvpk == 'wvpk'):
                        yield (block_size - 24,
                               reader.substream(block_size - 24))
                    else:
                        return
            except IOError:
                return

        if (reader is None):
            from audiotools.bitstream import BitstreamReader

            reader = BitstreamReader(open(self.filename), 1)
            try:
                for block in blocks_iter(reader):
                    yield block
            finally:
                reader.close()
        else:
            for block in blocks_iter(reader):
                yield block
Ejemplo n.º 4
0
    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()
Ejemplo n.º 5
0
    def read(cls, apefile):
        """returns an ApeTag object from an APEv2 tagged file object

        may return None if the file object has no tag"""

        from audiotools.bitstream import BitstreamReader

        apefile.seek(-32, 2)
        reader = BitstreamReader(apefile, 1)

        (preamble,
         version,
         tag_size,
         item_count,
         read_only,
         item_encoding,
         is_header,
         no_footer,
         has_header) = reader.parse(cls.HEADER_FORMAT)

        if ((preamble != "APETAGEX") or (version != 2000)):
            return None

        apefile.seek(-tag_size, 2)

        return cls([ApeTagItem.parse(reader) for i in range(item_count)],
                   contains_header=has_header,
                   contains_footer=True)
Ejemplo n.º 6
0
    def parse(cls, mp3_file):
        """given an MP3 file, returns an ID3v1Comment

        raises ValueError if the comment is invalid"""

        from audiotools.bitstream import BitstreamReader

        mp3_file.seek(-128, 2)
        reader = BitstreamReader(mp3_file, 0)
        (tag,
         track_name,
         artist_name,
         album_name,
         year,
         comment,
         track_number,
         genre) = reader.parse("3b 30b 30b 30b 4b 28b 8p 1b 1b")
        if (tag != 'TAG'):
            raise ValueError(u"invalid ID3v1 tag")

        return ID3v1Comment(track_name=track_name,
                            artist_name=artist_name,
                            album_name=album_name,
                            year=year,
                            comment=comment,
                            track_number=track_number,
                            genre=genre)
Ejemplo n.º 7
0
    def get_replay_gain(self):
        """returns a ReplayGain object of our ReplayGain values

        returns None if we have no values

        may raise IOError if unable to read the file"""

        from audiotools import ReplayGain

        try:
            rg = BitstreamReader(self.get_block(b"RG"), False)
        except KeyError:
            return None

        version = rg.read(8)
        if version != 1:
            return None

        gain_title = rg.read(16)
        peak_title = rg.read(16)
        gain_album = rg.read(16)
        peak_album = rg.read(16)

        if ((gain_title == 0) and (peak_title == 0) and (gain_album == 0)
                and (peak_album == 0)):
            return None
        else:
            return ReplayGain(
                track_gain=64.82 - float(gain_title) / 256,
                track_peak=(10**(float(peak_title) / 256 / 20)) / 2**15,
                album_gain=64.82 - float(gain_album) / 256,
                album_peak=(10**(float(peak_album) / 256 / 20)) / 2**15)
Ejemplo n.º 8
0
    def get_replay_gain(self):
        """returns a ReplayGain object of our ReplayGain values

        returns None if we have no values

        may raise IOError if unable to read the file"""

        from audiotools import ReplayGain

        try:
            rg = BitstreamReader(self.get_block(b"RG"), False)
        except KeyError:
            return None

        version = rg.read(8)
        if version != 1:
            return None

        gain_title = rg.read(16)
        peak_title = rg.read(16)
        gain_album = rg.read(16)
        peak_album = rg.read(16)

        if ((gain_title == 0) and (peak_title == 0) and
            (gain_album == 0) and (peak_album == 0)):
            return None
        else:
            return ReplayGain(
                track_gain=64.82 - float(gain_title) / 256,
                track_peak=(10 ** (float(peak_title) / 256 / 20)) / 2 ** 15,
                album_gain=64.82 - float(gain_album) / 256,
                album_peak=(10 ** (float(peak_album) / 256 / 20)) / 2 ** 15)
Ejemplo n.º 9
0
    def __init__(self, filename):
        self.reader = BitstreamReader(open(filename, "rb"), False)

        stream_start = self.reader.getpos()

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

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

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

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

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

        # finally, set our stream to the "mdat" atom
        self.reader.setpos(stream_start)
        (atom_size, atom_name) = self.reader.parse("32u 4b")
        while atom_name != b"mdat":
            self.reader.skip_bytes(atom_size - 8)
            (atom_size, atom_name) = self.reader.parse("32u 4b")
Ejemplo n.º 10
0
    def blocks(self, reader=None):
        """yields (length, reader) tuples of WavPack frames

        length is the total length of all the substreams
        reader is a BitstreamReader which can be parsed
        """

        def blocks_iter(reader):
            try:
                while (True):
                    (wvpk, block_size) = reader.parse("4b 32u 192p")
                    if (wvpk == 'wvpk'):
                        yield (block_size - 24,
                               reader.substream(block_size - 24))
                    else:
                        return
            except IOError:
                return

        if (reader is None):
            from audiotools.bitstream import BitstreamReader

            reader = BitstreamReader(open(self.filename), 1)
            try:
                for block in blocks_iter(reader):
                    yield block
            finally:
                reader.close()
        else:
            for block in blocks_iter(reader):
                yield block
Ejemplo n.º 11
0
        def tags(file, order):
            while True:
                reader = BitstreamReader(file, order)
                # read all the tags in an IFD
                tag_count = reader.read(16)
                sub_reader = reader.substream(tag_count * 12)
                next_ifd = reader.read(32)

                for i in range(tag_count):
                    (tag_code,
                     tag_datatype,
                     tag_value_count) = sub_reader.parse("16u 16u 32u")
                    if tag_datatype == 1:    # BYTE type
                        tag_struct = "8u" * tag_value_count
                    elif tag_datatype == 3:  # SHORT type
                        tag_struct = "16u" * tag_value_count
                    elif tag_datatype == 4:  # LONG type
                        tag_struct = "32u" * tag_value_count
                    else:                      # all other types
                        tag_struct = "4b"
                    if format_size(tag_struct) <= 32:
                        yield (tag_code, sub_reader.parse(tag_struct))
                        sub_reader.skip(32 - format_size(tag_struct))
                    else:
                        offset = sub_reader.read(32)
                        file.seek(offset, 0)
                        yield (tag_code,
                               BitstreamReader(file, order).parse(tag_struct))

                if next_ifd != 0:
                    file.seek(next_ifd, 0)
                else:
                    break
Ejemplo n.º 12
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
Ejemplo n.º 13
0
    def __read_identification__(self):
        from .bitstream import BitstreamReader

        f = open(self.filename, "rb")
        try:
            ogg_reader = BitstreamReader(f, 1)
            (magic_number,
             version,
             header_type,
             granule_position,
             self.__serial_number__,
             page_sequence_number,
             checksum,
             segment_count) = ogg_reader.parse("4b 8u 8u 64S 32u 32u 32u 8u")

            if (magic_number != 'OggS'):
                from .text import ERR_OGG_INVALID_MAGIC_NUMBER
                raise InvalidFLAC(ERR_OGG_INVALID_MAGIC_NUMBER)
            if (version != 0):
                from .text import ERR_OGG_INVALID_VERSION
                raise InvalidFLAC(ERR_OGG_INVALID_VERSION)

            segment_length = ogg_reader.read(8)

            (vorbis_type,
             header,
             version,
             self.__channels__,
             self.__sample_rate__,
             maximum_bitrate,
             nominal_bitrate,
             minimum_bitrate,
             blocksize0,
             blocksize1,
             framing) = ogg_reader.parse(
                "8u 6b 32u 8u 32u 32u 32u 32u 4u 4u 1u")

            if (vorbis_type != 1):
                from .text import ERR_VORBIS_INVALID_TYPE
                raise InvalidVorbis(ERR_VORBIS_INVALID_TYPE)
            if (header != 'vorbis'):
                from .text import ERR_VORBIS_INVALID_HEADER
                raise InvalidVorbis(ERR_VORBIS_INVALID_HEADER)
            if (version != 0):
                from .text import ERR_VORBIS_INVALID_VERSION
                raise InvalidVorbis(ERR_VORBIS_INVALID_VERSION)
            if (framing != 1):
                from .text import ERR_VORBIS_INVALID_FRAMING_BIT
                raise InvalidVorbis(ERR_VORBIS_INVALID_FRAMING_BIT)
        finally:
            f.close()
Ejemplo n.º 14
0
def perform_lookup(disc_id,
                   accuraterip_server="www.accuraterip.com",
                   accuraterip_port=80):
    """performs web-based lookup using the given DiscID object
    and returns a dict of
    {track_number:[(confidence, crc, crc2), ...], ...}
    where track_number starts from 1

    may return a dict of empty lists if no AccurateRip entry is found

    may raise urllib2.HTTPError if an error occurs querying the server
    """

    from audiotools.bitstream import BitstreamReader
    try:
        from urllib.request import urlopen, URLError
    except ImportError:
        from urllib2 import urlopen, URLError

    matches = {n: [] for n in disc_id.track_numbers()}

    url = "http://%s:%s/accuraterip/%s/%s/%s/%s" % (accuraterip_server,
                                                    accuraterip_port,
                                                    str(disc_id)[16],
                                                    str(disc_id)[15],
                                                    str(disc_id)[14],
                                                    disc_id)

    try:
        response = BitstreamReader(urlopen(url), True)
    except URLError:
        # no CD found matching given parameters
        return matches

    try:
        while True:
            (track_count,
             id1,
             id2,
             freedb_disc_id) = response.parse("8u 32u 32u 32u")
            if (((id1 == disc_id.id1()) and
                 (id2 == disc_id.id2()) and
                 (freedb_disc_id == disc_id.freedb_disc_id()))):
                for track_number in range(1, track_count + 1):
                    if track_number in matches:
                        matches[track_number].append(
                            tuple(response.parse("8u 32u 32u")))
    except IOError:
        # keep trying to parse values until the data runs out
        response.close()
        return matches
Ejemplo n.º 15
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
Ejemplo n.º 16
0
    def blocks(self, reader=None):
        """yields (length, reader) tuples of WavPack frames

        length is the total length of all the substreams
        reader is a BitstreamReader which can be parsed
        """
        def blocks_iter(reader):
            try:
                while True:
                    (wvpk, block_size) = reader.parse("4b 32u 192p")
                    if wvpk == b"wvpk":
                        yield (block_size - 24,
                               reader.substream(block_size - 24))
                    else:
                        return
            except IOError:
                return

        if reader is None:
            from audiotools.bitstream import BitstreamReader

            with BitstreamReader(open(self.filename, "rb"), True) as reader:
                for block in blocks_iter(reader):
                    yield block
        else:
            for block in blocks_iter(reader):
                yield block
Ejemplo n.º 17
0
    def parse(cls, file_data):
        try:
            (magic_number,
             file_size,
             data_offset,
             header_size,
             width,
             height,
             color_planes,
             bits_per_pixel,
             compression_method,
             image_size,
             horizontal_resolution,
             vertical_resolution,
             colors_used,
             important_colors_used) = BitstreamReader(file_data, True).parse(
                "2b 32u 16p 16p 32u " +
                "32u 32u 32u 16u 16u 32u 32u 32u 32u 32u 32u")
        except IOError:
            from audiotools.text import ERR_IMAGE_IOERROR_BMP
            raise InvalidBMP(ERR_IMAGE_IOERROR_BMP)

        if (magic_number != b'BM'):
            from audiotools.text import ERR_IMAGE_INVALID_BMP
            raise InvalidBMP(ERR_IMAGE_INVALID_BMP)
        else:
            return cls(width=width,
                       height=height,
                       bits_per_pixel=bits_per_pixel,
                       color_count=colors_used)
Ejemplo n.º 18
0
    def __init__(self, filename):
        """filename is a plain string"""

        AudioFile.__init__(self, filename)
        self.__channels__ = 0
        self.__channel_mask__ = 0

        # get channel count and channel mask from first packet
        from audiotools.bitstream import BitstreamReader
        try:
            with BitstreamReader(open(filename, "rb"), True) as ogg_reader:
                (magic_number, version, header_type, granule_position,
                 self.__serial_number__, page_sequence_number, checksum,
                 segment_count
                 ) = ogg_reader.parse("4b 8u 8u 64S 32u 32u 32u 8u")

                if magic_number != b'OggS':
                    from audiotools.text import ERR_OGG_INVALID_MAGIC_NUMBER
                    raise InvalidOpus(ERR_OGG_INVALID_MAGIC_NUMBER)
                if version != 0:
                    from audiotools.text import ERR_OGG_INVALID_VERSION
                    raise InvalidOpus(ERR_OGG_INVALID_VERSION)

                segment_length = ogg_reader.read(8)

                (opushead, version, self.__channels__, pre_skip,
                 input_sample_rate, output_gain,
                 mapping_family) = ogg_reader.parse("8b 8u 8u 16u 32u 16s 8u")

                if opushead != b"OpusHead":
                    from audiotools.text import ERR_OPUS_INVALID_TYPE
                    raise InvalidOpus(ERR_OPUS_INVALID_TYPE)
                if version != 1:
                    from audiotools.text import ERR_OPUS_INVALID_VERSION
                    raise InvalidOpus(ERR_OPUS_INVALID_VERSION)
                if self.__channels__ == 0:
                    from audiotools.text import ERR_OPUS_INVALID_CHANNELS
                    raise InvalidOpus(ERR_OPUS_INVALID_CHANNELS)

                # FIXME - assign channel mask from mapping family
                if mapping_family == 0:
                    if self.__channels__ == 1:
                        self.__channel_mask__ = VorbisChannelMask(0x4)
                    elif self.__channels__ == 2:
                        self.__channel_mask__ = VorbisChannelMask(0x3)
                    else:
                        self.__channel_mask__ = VorbisChannelMask(0)
                else:
                    (stream_count,
                     coupled_stream_count) = ogg_reader.parse("8u 8u")
                    if (self.__channels__ !=
                        ((coupled_stream_count * 2) +
                         (stream_count - coupled_stream_count))):
                        from audiotools.text import ERR_OPUS_INVALID_CHANNELS
                        raise InvalidOpus(ERR_OPUS_INVALID_CHANNELS)
                    channel_mapping = [
                        ogg_reader.read(8) for i in range(self.__channels__)
                    ]
        except IOError as msg:
            raise InvalidOpus(str(msg))
Ejemplo n.º 19
0
    def __read_identification__(self):
        from audiotools.bitstream import BitstreamReader

        with BitstreamReader(open(self.filename, "rb"), True) as ogg_reader:
            (magic_number, version, header_type, granule_position,
             self.__serial_number__, page_sequence_number, checksum,
             segment_count) = ogg_reader.parse("4b 8u 8u 64S 32u 32u 32u 8u")

            if magic_number != b'OggS':
                from audiotools.text import ERR_OGG_INVALID_MAGIC_NUMBER
                raise InvalidVorbis(ERR_OGG_INVALID_MAGIC_NUMBER)
            if version != 0:
                from audiotools.text import ERR_OGG_INVALID_VERSION
                raise InvalidVorbis(ERR_OGG_INVALID_VERSION)

            segment_length = ogg_reader.read(8)

            (vorbis_type, header, version, self.__channels__,
             self.__sample_rate__, maximum_bitrate, nominal_bitrate,
             minimum_bitrate, blocksize0, blocksize1, framing
             ) = ogg_reader.parse("8u 6b 32u 8u 32u 32u 32u 32u 4u 4u 1u")

            if vorbis_type != 1:
                from audiotools.text import ERR_VORBIS_INVALID_TYPE
                raise InvalidVorbis(ERR_VORBIS_INVALID_TYPE)
            if header != b'vorbis':
                from audiotools.text import ERR_VORBIS_INVALID_HEADER
                raise InvalidVorbis(ERR_VORBIS_INVALID_HEADER)
            if version != 0:
                from audiotools.text import ERR_VORBIS_INVALID_VERSION
                raise InvalidVorbis(ERR_VORBIS_INVALID_VERSION)
            if framing != 1:
                from audiotools.text import ERR_VORBIS_INVALID_FRAMING_BIT
                raise InvalidVorbis(ERR_VORBIS_INVALID_FRAMING_BIT)
Ejemplo n.º 20
0
    def __titlesets__(self):
        """return valid audio titleset integers from AUDIO_TS.IFO"""

        from audiotools.bitstream import BitstreamReader

        try:
            f = open(self.files['AUDIO_TS.IFO'], 'rb')
        except (KeyError, IOError):
            from audiotools.text import ERR_DVDA_IOERROR_AUDIO_TS
            raise InvalidDVDA(ERR_DVDA_IOERROR_AUDIO_TS)
        try:
            (identifier, AMG_start_sector, AMGI_end_sector, DVD_version,
             volume_count, volume_number, disc_side, autoplay, ts_to_sv,
             video_titlesets, audio_titlesets,
             provider_information) = BitstreamReader(f, 0).parse(
                 "12b 32u 12P 32u 16u 4P 16u 16u 8u 4P 8u 32u 10P 8u 8u 40b")

            if (identifier != 'DVDAUDIO-AMG'):
                from audiotools.text import ERR_DVDA_INVALID_AUDIO_TS
                raise InvalidDVDA(ERR_DVDA_INVALID_AUDIO_TS)

            for titleset in range(1, audio_titlesets + 1):
                # ensure there are IFO files and AOBs
                # for each valid titleset
                if (("ATS_%2.2d_0.IFO" % (titleset) in self.files.keys()) and
                    ("ATS_%2.2d_1.AOB" % (titleset) in self.files.keys())):
                    yield titleset
        finally:
            f.close()
Ejemplo n.º 21
0
    def read(cls, apefile):
        """returns an ApeTag object from an APEv2 tagged file object

        may return None if the file object has no tag"""

        from audiotools.bitstream import BitstreamReader, parse

        apefile.seek(-32, 2)
        tag_footer = apefile.read(32)

        if len(tag_footer) < 32:
            # not enough bytes for an ApeV2 tag
            return None

        (preamble,
         version,
         tag_size,
         item_count,
         read_only,
         item_encoding,
         is_header,
         no_footer,
         has_header) = parse(cls.HEADER_FORMAT, True, tag_footer)

        if (preamble != b"APETAGEX") or (version != 2000):
            return None

        apefile.seek(-tag_size, 2)
        reader = BitstreamReader(apefile, True)

        return cls([ApeTagItem.parse(reader) for i in range(item_count)],
                   contains_header=has_header,
                   contains_footer=True)
Ejemplo n.º 22
0
    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
Ejemplo n.º 23
0
    def __init__(self, filename):
        """filename is a plain string"""

        from audiotools import ChannelMask

        AiffContainer.__init__(self, filename)

        self.__channels__ = 0
        self.__bits_per_sample__ = 0
        self.__sample_rate__ = 0
        self.__channel_mask__ = ChannelMask(0)
        self.__total_sample_frames__ = 0

        from audiotools.bitstream import BitstreamReader

        try:
            for chunk in self.chunks():
                if chunk.id == b"COMM":
                    try:
                        (self.__channels__, self.__total_sample_frames__,
                         self.__bits_per_sample__, self.__sample_rate__,
                         self.__channel_mask__) = parse_comm(
                             BitstreamReader(chunk.data(), False))
                        break
                    except IOError:
                        continue
        except IOError:
            raise InvalidAIFF("I/O error reading wave")
Ejemplo n.º 24
0
    def __init__(self, filename):
        """filename is a plain string"""

        from audiotools.bitstream import BitstreamReader

        AudioFile.__init__(self, filename)

        # first, fetch the mdia atom
        # which is the parent of both the mp4a and mdhd atoms
        try:
            with BitstreamReader(open(filename, "rb"), False) as reader:
                mdia = get_m4a_atom(reader, b"moov", b"trak", b"mdia")[1]
                mdia_start = mdia.getpos()
        except IOError:
            from audiotools.text import ERR_M4A_IOERROR
            raise InvalidM4A(ERR_M4A_IOERROR)
        except KeyError:
            from audiotools.text import ERR_M4A_MISSING_MDIA
            raise InvalidM4A(ERR_M4A_MISSING_MDIA)

        try:
            stsd = get_m4a_atom(mdia, b"minf", b"stbl", b"stsd")[1]
        except KeyError:
            from audiotools.text import ERR_M4A_MISSING_STSD
            raise InvalidM4A(ERR_M4A_MISSING_STSD)

        # then, fetch the mp4a atom for bps, channels and sample rate
        try:
            (stsd_version, descriptions) = stsd.parse("8u 24p 32u")
            (mp4a, self.__channels__, self.__bits_per_sample__
             ) = stsd.parse("32p 4b 48p 16p 16p 16p 4P 16u 16u 16p 16p 32p")
        except IOError:
            from audiotools.text import ERR_M4A_INVALID_MP4A
            raise InvalidM4A(ERR_M4A_INVALID_MP4A)

        # finally, fetch the mdhd atom for total track length
        mdia.setpos(mdia_start)
        try:
            mdhd = get_m4a_atom(mdia, b"mdhd")[1]
        except KeyError:
            from audiotools.text import ERR_M4A_MISSING_MDHD
            raise InvalidM4A(ERR_M4A_MISSING_MDHD)
        try:
            (version, ) = mdhd.parse("8u 24p")
            if version == 0:
                (
                    self.__sample_rate__,
                    self.__length__,
                ) = mdhd.parse("32p 32p 32u 32u 2P 16p")
            elif version == 1:
                (
                    self.__sample_rate__,
                    self.__length__,
                ) = mdhd.parse("64p 64p 32u 64U 2P 16p")
            else:
                from audiotools.text import ERR_M4A_UNSUPPORTED_MDHD
                raise InvalidM4A(ERR_M4A_UNSUPPORTED_MDHD)
        except IOError:
            from audiotools.text import ERR_M4A_INVALID_MDHD
            raise InvalidM4A(ERR_M4A_INVALID_MDHD)
Ejemplo n.º 25
0
    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)
Ejemplo n.º 26
0
    def parse(cls, file_data):
        def segments(reader):
            if reader.read(8) != 0xFF:
                from audiotools.text import ERR_IMAGE_INVALID_JPEG_MARKER
                raise InvalidJPEG(ERR_IMAGE_INVALID_JPEG_MARKER)
            segment_type = reader.read(8)

            while segment_type != 0xDA:
                if segment_type not in {0xD8, 0xD9}:
                    yield (segment_type, reader.substream(reader.read(16) - 2))
                else:
                    yield (segment_type, None)

                if reader.read(8) != 0xFF:
                    from audiotools.text import ERR_IMAGE_INVALID_JPEG_MARKER
                    raise InvalidJPEG(ERR_IMAGE_INVALID_JPEG_MARKER)
                segment_type = reader.read(8)

        try:
            for (segment_type,
                 segment_data) in segments(BitstreamReader(file_data, False)):
                if (segment_type in {
                        0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0XC5, 0xC6, 0xC7, 0xC9,
                        0xCA, 0xCB, 0xCD, 0xCE, 0xCF
                }):  # start of frame
                    (data_precision, image_height, image_width,
                     components) = segment_data.parse("8u 16u 16u 8u")
                    return __JPEG__(width=image_width,
                                    height=image_height,
                                    bits_per_pixel=data_precision * components)
        except IOError:
            from audiotools.text import ERR_IMAGE_IOERROR_JPEG
            raise InvalidJPEG(ERR_IMAGE_IOERROR_JPEG)
Ejemplo n.º 27
0
    def __init__(self, filename):
        from audiotools.bitstream import BitstreamReader

        AudioFile.__init__(self, filename)

        try:
            with BitstreamReader(open(self.filename, "rb"),
                                 True) as ogg_reader:
                (magic_number, version, header_type, granule_position,
                 self.__serial_number__, page_sequence_number, checksum,
                 segment_count
                 ) = ogg_reader.parse("4b 8u 8u 64S 32u 32u 32u 8u")

                if magic_number != b'OggS':
                    from audiotools.text import ERR_OGG_INVALID_MAGIC_NUMBER
                    raise InvalidVorbis(ERR_OGG_INVALID_MAGIC_NUMBER)
                if version != 0:
                    from audiotools.text import ERR_OGG_INVALID_VERSION
                    raise InvalidVorbis(ERR_OGG_INVALID_VERSION)

                segment_lengths = [
                    ogg_reader.read(8) for i in range(segment_count)
                ]

                (speex_string, speex_version, speex_version_id, header_size,
                 self.__sampling_rate__, mode, mode_bitstream_version,
                 self.__channels__, bitrate, frame_size, vbr, frame_per_packet,
                 extra_headers, reserved1,
                 reserved2) = ogg_reader.parse("8b 20b 13*32u")

                if speex_string != b"Speex   ":
                    raise InvalidSpeex(ERR_SPEEX_INVALID_VERSION)
        except IOError as err:
            raise InvalidSpeex(str(err))
Ejemplo n.º 28
0
    def update_metadata(self, metadata):
        """takes this track's current MetaData object
        as returned by get_metadata() and sets this track's metadata
        with any fields updated in that object

        raises IOError if unable to write the file
        """

        if (metadata is None):
            return
        elif (not isinstance(metadata, ApeTag)):
            from audiotools.text import ERR_FOREIGN_METADATA
            raise ValueError(ERR_FOREIGN_METADATA)

        from audiotools.bitstream import BitstreamReader, BitstreamWriter
        from audiotools import transfer_data

        f = open(self.filename, "r+b")
        f.seek(-32, 2)

        (preamble, version, tag_size, item_count, read_only, item_encoding,
         is_header, no_footer,
         has_header) = BitstreamReader(f, 1).parse(ApeTag.HEADER_FORMAT)

        if ((preamble == 'APETAGEX') and (version == 2000)):
            if (has_header):
                old_tag_size = 32 + tag_size
            else:
                old_tag_size = tag_size

            if (metadata.total_size() >= old_tag_size):
                # metadata has grown
                # so append it to existing file
                f.seek(-old_tag_size, 2)
                metadata.build(BitstreamWriter(f, 1))
            else:
                # metadata has shrunk
                # so rewrite file with smaller metadata
                from audiotools import TemporaryFile
                from os.path import getsize

                # copy everything but the last "old_tag_size" bytes
                # from existing file to rewritten file
                new_apev2 = TemporaryFile(self.filename)
                old_apev2 = open(self.filename, "rb")

                limited_transfer_data(old_apev2.read, new_apev2.write,
                                      getsize(self.filename) - old_tag_size)

                # append new tag to rewritten file
                metadata.build(BitstreamWriter(new_apev2, 1))

                old_apev2.close()
                new_apev2.close()
        else:
            # no existing metadata, so simply append a fresh tag
            f = open(self.filename, "ab")
            metadata.build(BitstreamWriter(f, 1))
            f.close()
Ejemplo n.º 29
0
    def delete_metadata(self):
        """deletes the track's MetaData

        raises IOError if unable to write the file"""

        if ((self.get_replay_gain() is not None) or
            (self.get_cuesheet() is not None)):
            # non-textual metadata is present and needs preserving
            self.set_metadata(MetaData())
        else:
            # no non-textual metadata, so wipe out the entire block
            from os import access, R_OK, W_OK
            from audiotools.bitstream import BitstreamReader
            from audiotools import transfer_data

            if not access(self.filename, R_OK | W_OK):
                raise IOError(self.filename)

            with open(self.filename, "rb") as f:
                f.seek(-32, 2)

                (preamble,
                 version,
                 tag_size,
                 item_count,
                 read_only,
                 item_encoding,
                 is_header,
                 no_footer,
                 has_header) = BitstreamReader(f, True).parse(
                    ApeTag.HEADER_FORMAT)

            if (preamble == b'APETAGEX') and (version == 2000):
                from audiotools import TemporaryFile
                from os.path import getsize

                # there's existing metadata to delete
                # so rewrite file without trailing metadata tag
                if has_header:
                    old_tag_size = 32 + tag_size
                else:
                    old_tag_size = tag_size

                # copy everything but the last "old_tag_size" bytes
                # from existing file to rewritten file
                new_apev2 = TemporaryFile(self.filename)
                old_apev2 = open(self.filename, "rb")

                limited_transfer_data(
                    old_apev2.read,
                    new_apev2.write,
                    getsize(self.filename) - old_tag_size)

                old_apev2.close()
                new_apev2.close()
Ejemplo n.º 30
0
def validate_footer(footer, ssnd_bytes_written):
    """given a footer string as returned by aiff_header_footer()
    and PCM stream parameters, returns True if the footer is valid

    raises ValueError is the footer is invalid"""

    from io import BytesIO
    from audiotools.bitstream import BitstreamReader

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

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

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

    raises ValueError if the footer is invalid"""

    from io import BytesIO
    from audiotools.bitstream import BitstreamReader

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

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

            if chunk_id == b"fmt ":
                # ensure no fmt chunks are found
                from audiotools.text import ERR_WAV_MULTIPLE_FMT
                raise ValueError(ERR_WAV_MULTIPLE_FMT)
            elif chunk_id == b"data":
                # ensure no data chunks are found
                from audiotools.text import ERR_WAV_MULTIPLE_DATA
                raise ValueError(ERR_WAV_MULTIPLE_DATA)
            else:
                # skip the full contents of non-audio chunks
                if chunk_size % 2:
                    wave_file.skip_bytes(chunk_size + 1)
                    total_size -= (chunk_size + 1)
                else:
                    wave_file.skip_bytes(chunk_size)
                    total_size -= chunk_size
        else:
            return True
    except IOError:
        from audiotools.text import ERR_WAV_FOOTER_IOERROR
        raise ValueError(ERR_WAV_FOOTER_IOERROR)
Ejemplo n.º 32
0
    def __init__(self, filename):
        self.reader = BitstreamReader(open(filename, "rb"), True)
        crc = CRC32()
        self.reader.add_callback(crc.update)

        # read the header
        (signature,
         format_,
         self.channels,
         self.bits_per_sample,
         self.sample_rate,
         self.total_pcm_frames) = self.reader.parse(
            "4b 16u 16u 16u 32u 32u")

        self.reader.pop_callback()
        header_crc = self.reader.read(32)
        if (int(crc) != header_crc):
            raise ValueError(
                "CRC32 mismatch in header (0x%8.8X != 0x%8.8X)" %
                (header_crc, int(crc)))

        self.channel_mask = {1: 0x4, 2: 0x3}.get(self.channels, 0)

        total_tta_frames = div_ceil(self.total_pcm_frames * 245,
                                    self.sample_rate * 256)

        self.pcm_frames_per_tta_frame = (self.sample_rate * 256) // 245

        # read the seektable
        crc = CRC32()
        self.reader.add_callback(crc.update)
        self.frame_sizes = [self.reader.read(32) for i in
                            range(total_tta_frames)]
        self.reader.pop_callback()
        seektable_crc = self.reader.read(32)
        if (int(crc) != seektable_crc):
            raise ValueError(
                "CRC32 mismatch in seektable (0x%8.8X != 0x%8.8X)" %
                (header_crc, int(crc)))

        self.current_tta_frame = 0
Ejemplo n.º 33
0
def perform_lookup(disc_id,
                   accuraterip_server="www.accuraterip.com",
                   accuraterip_port=80):
    """performs web-based lookup using the given DiscID object
    and returns a dict of
    {track_number:[(confidence, crc, crc2), ...], ...}
    where track_number starts from 1

    may return a dict of empty lists if no AccurateRip entry is found

    may raise urllib2.HTTPError if an error occurs querying the server
    """

    from audiotools.bitstream import BitstreamReader
    from urllib2 import urlopen, URLError

    matches = {n: [] for n in disc_id.track_numbers()}

    url = "http://%s:%s/accuraterip/%s/%s/%s/%s" % (
        accuraterip_server, accuraterip_port, str(disc_id)[16],
        str(disc_id)[15], str(disc_id)[14], disc_id)

    try:
        response = BitstreamReader(urlopen(url), True)
    except URLError:
        # no CD found matching given parameters
        return matches

    try:
        while (True):
            (track_count, id1, id2,
             freedb_disc_id) = response.parse("8u 32u 32u 32u")
            if (((id1 == disc_id.id1()) and (id2 == disc_id.id2())
                 and (freedb_disc_id == disc_id.freedb_disc_id()))):
                for track_number in range(1, track_count + 1):
                    if (track_number in matches):
                        matches[track_number].append(
                            tuple(response.parse("8u 32u 32u")))
    except IOError:
        # keep trying to parse values until the data runs out
        return matches
Ejemplo n.º 34
0
    def parse(cls, mp3_file):
        """given an MP3 file, returns an ID3v1Comment

        raises ValueError if the comment is invalid"""

        from audiotools.bitstream import BitstreamReader

        mp3_file.seek(-128, 2)
        reader = BitstreamReader(mp3_file, 0)
        (tag, track_name, artist_name, album_name, year, comment, track_number,
         genre) = reader.parse("3b 30b 30b 30b 4b 28b 8p 1b 1b")
        if (tag != 'TAG'):
            raise ValueError(u"invalid ID3v1 tag")

        return ID3v1Comment(track_name=track_name,
                            artist_name=artist_name,
                            album_name=album_name,
                            year=year,
                            comment=comment,
                            track_number=track_number,
                            genre=genre)
Ejemplo n.º 35
0
    def read(cls, apefile):
        """returns an ApeTag object from an APEv2 tagged file object

        may return None if the file object has no tag"""

        from audiotools.bitstream import BitstreamReader

        apefile.seek(-32, 2)
        reader = BitstreamReader(apefile, 1)

        (preamble, version, tag_size, item_count, read_only, item_encoding,
         is_header, no_footer, has_header) = reader.parse(cls.HEADER_FORMAT)

        if ((preamble != "APETAGEX") or (version != 2000)):
            return None

        apefile.seek(-tag_size, 2)

        return cls([ApeTagItem.parse(reader) for i in range(item_count)],
                   contains_header=has_header,
                   contains_footer=True)
Ejemplo n.º 36
0
    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.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]
Ejemplo n.º 37
0
        def tags(file, order):
            while True:
                reader = BitstreamReader(file, order)
                # read all the tags in an IFD
                tag_count = reader.read(16)
                sub_reader = reader.substream(tag_count * 12)
                next_ifd = reader.read(32)

                for i in range(tag_count):
                    (tag_code, tag_datatype,
                     tag_value_count) = sub_reader.parse("16u 16u 32u")
                    if tag_datatype == 1:  # BYTE type
                        tag_struct = "8u" * tag_value_count
                    elif tag_datatype == 3:  # SHORT type
                        tag_struct = "16u" * tag_value_count
                    elif tag_datatype == 4:  # LONG type
                        tag_struct = "32u" * tag_value_count
                    else:  # all other types
                        tag_struct = "4b"
                    if format_size(tag_struct) <= 32:
                        yield (tag_code, sub_reader.parse(tag_struct))
                        sub_reader.skip(32 - format_size(tag_struct))
                    else:
                        offset = sub_reader.read(32)
                        file.seek(offset, 0)
                        yield (tag_code,
                               BitstreamReader(file, order).parse(tag_struct))

                if next_ifd != 0:
                    file.seek(next_ifd, 0)
                else:
                    break
Ejemplo n.º 38
0
    def __find_mp3_start__(cls, mp3file):
        """places mp3file at the position of the MP3 file's start"""

        from audiotools.id3 import skip_id3v2_comment

        # if we're starting at an ID3v2 header, skip it to save a bunch of time
        skip_id3v2_comment(mp3file)

        from audiotools.bitstream import BitstreamReader

        reader = BitstreamReader(mp3file, False)

        # skip over any bytes that aren't a valid MPEG header
        pos = reader.getpos()
        (frame_sync, mpeg_id, layer) = reader.parse("11u 2u 2u 1p")
        while (not ((frame_sync == 0x7FF) and
                    (mpeg_id in (0, 2, 3)) and
                    (layer in (1, 2, 3)))):
            reader.setpos(pos)
            reader.skip(8)
            pos = reader.getpos()
        reader.setpos(pos)
Ejemplo n.º 39
0
    def get_metadata(self):
        """returns a MetaData object, or None

        raises IOError if unable to read the file"""

        from audiotools.bitstream import BitstreamReader
        from audiotools.id3 import ID3v22Comment

        for chunk in self.chunks():
            if (chunk.id == 'ID3 '):
                return ID3v22Comment.parse(BitstreamReader(chunk.data(), 0))
        else:
            return None
Ejemplo n.º 40
0
    def blocks(self):
        with BitstreamReader(open(self.filename, "rb"), False) as r:
            if r.read_bytes(4) != b"MPCK":
                from audiotools.text import ERR_MPC_INVALID_ID
                raise InvalidMPC(ERR_MPC_INVALID_ID)

            key = r.read_bytes(2)
            size = MPC_Size.parse(r)
            while key != b"SE":
                yield key, size, r.read_bytes(int(size) - len(size) - 2)
                key = r.read_bytes(2)
                size = MPC_Size.parse(r)
            yield key, size, r.read_bytes(int(size) - len(size) - 2)
Ejemplo n.º 41
0
def validate_footer(footer, ssnd_bytes_written):
    """given a footer string as returned by aiff_header_footer()
    and PCM stream parameters, returns True if the footer is valid

    raises ValueError is the footer is invalid"""

    from io import BytesIO
    from audiotools.bitstream import BitstreamReader

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

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

            if chunk_id == b"COMM":
                # ensure no COMM chunks are found
                from audiotools.text import ERR_AIFF_MULTIPLE_COMM_CHUNKS
                raise ValueError(ERR_AIFF_MULTIPLE_COMM_CHUNKS)
            elif chunk_id == b"SSND":
                # ensure no SSND chunks are found
                from audiotools.text import ERR_AIFF_MULTIPLE_SSND_CHUNKS
                raise ValueError(ERR_AIFF_MULTIPLE_SSND_CHUNKS)
            else:
                # skip the full contents of non-audio chunks
                if chunk_size % 2:
                    aiff_file.skip_bytes(chunk_size + 1)
                    total_size -= (chunk_size + 1)
                else:
                    aiff_file.skip_bytes(chunk_size)
                    total_size -= chunk_size
        else:
            return True
    except IOError:
        from audiotools.text import ERR_AIFF_FOOTER_IOERROR
        raise ValueError(ERR_AIFF_FOOTER_IOERROR)
Ejemplo n.º 42
0
    def __init__(self, filename):
        """filename is a plain string"""

        AudioFile.__init__(self, filename)
        try:
            block = BitstreamReader(self.get_block(b"SH"), False)
            crc = block.read(32)
            if block.read(8) != 8:
                from audiotools.text import ERR_MPC_INVALID_VERSION
                raise InvalidMPC(ERR_MPC_INVALID_VERSION)
            self.__samples__ = int(MPC_Size.parse(block))
            beg_silence = int(MPC_Size.parse(block))
            self.__sample_rate__ = \
                [44100, 48000, 37800, 32000][block.read(3)]
            max_band = block.read(5) + 1
            self.__channels__ = block.read(4) + 1
            ms = block.read(1)
            block_pwr = block.read(3) * 2
        except IOError as err:
            raise InvalidMPC(str(err))
Ejemplo n.º 43
0
    def __init__(self, filename):
        self.reader = BitstreamReader(open(filename, "rb"), True)
        crc = CRC32()
        self.reader.add_callback(crc.update)

        #read the header
        (signature,
         format_,
         self.channels,
         self.bits_per_sample,
         self.sample_rate,
         self.total_pcm_frames) = self.reader.parse(
            "4b 16u 16u 16u 32u 32u")

        self.reader.pop_callback()
        header_crc = self.reader.read(32)
        if (int(crc) != header_crc):
            raise ValueError(
                "CRC32 mismatch in header (0x%8.8X != 0x%8.8X)" %
                (header_crc, int(crc)))

        self.channel_mask = {1:0x4, 2:0x3}.get(self.channels, 0)

        total_tta_frames = div_ceil(self.total_pcm_frames * 245,
                                    self.sample_rate * 256)

        self.pcm_frames_per_tta_frame = (self.sample_rate * 256) / 245

        #read the seektable
        crc = CRC32()
        self.reader.add_callback(crc.update)
        self.frame_sizes = [self.reader.read(32) for i in
                            xrange(total_tta_frames)]
        self.reader.pop_callback()
        seektable_crc = self.reader.read(32)
        if (int(crc) != seektable_crc):
            raise ValueError(
                "CRC32 mismatch in seektable (0x%8.8X != 0x%8.8X)" %
                (header_crc, int(crc)))

        self.current_tta_frame = 0
Ejemplo n.º 44
0
    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]
Ejemplo n.º 45
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

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

            if comment.read_bytes(8) == b"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
Ejemplo n.º 46
0
    def __init__(self, filename):
        self.reader = BitstreamReader(open(filename, "rb"), 1)

        #read initial block to populate
        #sample_rate, bits_per_sample, channels, and channel_mask
        self.reader.mark()
        block_header = Block_Header.read(self.reader)
        sub_blocks_size = block_header.block_size - 24
        sub_blocks_data = self.reader.substream(sub_blocks_size)
        if (block_header.sample_rate != 15):
            self.sample_rate = [6000,  8000,  9600,  11025, 12000,
                                16000, 22050, 24000, 32000, 44100,
                                48000, 64000, 88200, 96000,
                                192000][block_header.sample_rate]
        else:
            sub_blocks_data.mark()
            try:
                for sub_block in sub_blocks(sub_blocks_data, sub_blocks_size):
                    if ((sub_block.metadata_function == 7) and
                        (sub_block.nondecoder_data == 1)):
                        self.sample_rate = sub_block.data.read(
                            sub_block.data_size() * 8)
                        break
                else:
                    raise ValueError("invalid sample rate")
            finally:
                sub_blocks_data.rewind()
                sub_blocks_data.unmark()

        self.bits_per_sample = [8, 16, 24, 32][block_header.bits_per_sample]

        if (block_header.initial_block and block_header.final_block):
            if ((block_header.mono_output == 0) or
                (block_header.false_stereo == 1)):
                self.channels = 2
                self.channel_mask = 0x3
            else:
                self.channels = 1
                self.channel_mask = 0x4
        else:
            #look for channel mask sub block
            sub_blocks_data.mark()
            for sub_block in sub_blocks(sub_blocks_data, sub_blocks_size):
                if ((sub_block.metadata_function == 13) and
                    (sub_block.nondecoder_data == 0)):
                    self.channels = sub_block.data.read(8)
                    self.channel_mask = sub_block.data.read(
                        (sub_block.data_size() - 1) * 8)
                    break
            else:
                #FIXME - handle case of no channel mask sub block
                raise NotImplementedError()
            sub_blocks_data.rewind()
            sub_blocks_data.unmark()

        self.reader.rewind()
        self.reader.unmark()

        self.pcm_finished = False
        self.md5_checked = False
        self.md5sum = md5()
Ejemplo n.º 47
0
class WavPackDecoder:
    def __init__(self, filename):
        self.reader = BitstreamReader(open(filename, "rb"), 1)

        #read initial block to populate
        #sample_rate, bits_per_sample, channels, and channel_mask
        self.reader.mark()
        block_header = Block_Header.read(self.reader)
        sub_blocks_size = block_header.block_size - 24
        sub_blocks_data = self.reader.substream(sub_blocks_size)
        if (block_header.sample_rate != 15):
            self.sample_rate = [6000,  8000,  9600,  11025, 12000,
                                16000, 22050, 24000, 32000, 44100,
                                48000, 64000, 88200, 96000,
                                192000][block_header.sample_rate]
        else:
            sub_blocks_data.mark()
            try:
                for sub_block in sub_blocks(sub_blocks_data, sub_blocks_size):
                    if ((sub_block.metadata_function == 7) and
                        (sub_block.nondecoder_data == 1)):
                        self.sample_rate = sub_block.data.read(
                            sub_block.data_size() * 8)
                        break
                else:
                    raise ValueError("invalid sample rate")
            finally:
                sub_blocks_data.rewind()
                sub_blocks_data.unmark()

        self.bits_per_sample = [8, 16, 24, 32][block_header.bits_per_sample]

        if (block_header.initial_block and block_header.final_block):
            if ((block_header.mono_output == 0) or
                (block_header.false_stereo == 1)):
                self.channels = 2
                self.channel_mask = 0x3
            else:
                self.channels = 1
                self.channel_mask = 0x4
        else:
            #look for channel mask sub block
            sub_blocks_data.mark()
            for sub_block in sub_blocks(sub_blocks_data, sub_blocks_size):
                if ((sub_block.metadata_function == 13) and
                    (sub_block.nondecoder_data == 0)):
                    self.channels = sub_block.data.read(8)
                    self.channel_mask = sub_block.data.read(
                        (sub_block.data_size() - 1) * 8)
                    break
            else:
                #FIXME - handle case of no channel mask sub block
                raise NotImplementedError()
            sub_blocks_data.rewind()
            sub_blocks_data.unmark()

        self.reader.rewind()
        self.reader.unmark()

        self.pcm_finished = False
        self.md5_checked = False
        self.md5sum = md5()

    def read(self, bytes):
        if (self.pcm_finished):
            if (not self.md5_checked):
                self.reader.mark()
                try:
                    try:
                        header = Block_Header.read(self.reader)
                        sub_blocks_size = header.block_size - 24
                        sub_blocks_data = self.reader.substream(sub_blocks_size)
                        for sub_block in sub_blocks(sub_blocks_data,
                                                    sub_blocks_size):
                            if ((sub_block.metadata_function == 6) and
                                (sub_block.nondecoder_data == 1)):
                                if (sub_block.data.read_bytes(16) !=
                                    self.md5sum.digest()):
                                    raise ValueError("invalid stream MD5 sum")
                    except (IOError, ValueError):
                        #no error if a block isn't found
                        pass
                finally:
                    self.reader.rewind()
                    self.reader.unmark()
            return from_list([], self.channels, self.bits_per_sample, True)

        channels = []

        while (True):  #in place of a do-while loop
            try:
                block_header = Block_Header.read(self.reader)
            except (ValueError, IOError):
                self.pcm_finished = True
                return from_list([], self.channels, self.bits_per_sample, True)
            sub_blocks_size = block_header.block_size - 24
            sub_blocks_data = self.reader.substream(sub_blocks_size)
            channels.extend(read_block(block_header,
                                       sub_blocks_size,
                                       sub_blocks_data))

            if (block_header.final_block == 1):
                break

        if ((block_header.block_index + block_header.block_samples) >=
            block_header.total_samples):
            self.pcm_finished = True

        #combine channels of audio data into single block
        block = from_channels([from_list(ch, 1, self.bits_per_sample, True)
                               for ch in channels])

        #update MD5 sum
        self.md5sum.update(block.to_bytes(False, self.bits_per_sample > 8))

        #return single block of audio data
        return block

    def close(self):
        self.reader.close()
Ejemplo n.º 48
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
Ejemplo n.º 49
0
    def __titles__(self, titleset):
        """returns a list of DVDATitle objects for the given titleset"""

        # this requires bouncing all over the ATS_XX_0.IFO file

        import os
        from audiotools.bitstream import BitstreamReader

        try:
            f = open(self.files['ATS_%2.2d_0.IFO' % (titleset)], 'rb')
        except (KeyError, IOError):
            from audiotools.text import ERR_DVDA_IOERROR_ATS
            raise InvalidDVDA(ERR_DVDA_IOERROR_ATS % (titleset))
        try:
            # ensure the file's identifier is correct
            # which is all we care about from the first sector
            if (f.read(12) != 'DVDAUDIO-ATS'):
                from audiotools.text import ERR_DVDA_INVALID_ATS
                raise InvalidDVDA(ERR_DVDA_INVALID_ATS % (titleset))

            # seek to the second sector and read the title count
            # and list of title table offset values
            f.seek(DVDAudio.SECTOR_SIZE, os.SEEK_SET)
            ats_reader = BitstreamReader(f, 0)
            (title_count, last_byte_address) = ats_reader.parse("16u 16p 32u")
            title_offsets = [ats_reader.parse("8u 24p 32u")[1] for title in
                             range(title_count)]

            titles = []

            for (title_number, title_offset) in enumerate(title_offsets):
                # for each title, seek to its title table
                # and read the title's values and its track timestamps
                f.seek(DVDAudio.SECTOR_SIZE + title_offset, os.SEEK_SET)
                ats_reader = BitstreamReader(f, 0)
                (tracks,
                 indexes,
                 track_length,
                 sector_pointers_table) = ats_reader.parse(
                    "16p 8u 8u 32u 4P 16u 2P")
                timestamps = [ats_reader.parse("32p 8u 8p 32u 32u 48p")
                              for track in range(tracks)]

                # seek to the title's sector pointers table
                # and read the first and last sector values for title's tracks
                f.seek(DVDAudio.SECTOR_SIZE +
                       title_offset +
                       sector_pointers_table,
                       os.SEEK_SET)
                ats_reader = BitstreamReader(f, 0)
                sector_pointers = [ats_reader.parse("32u 32u 32u")
                                   for i in range(indexes)]
                if ((len(sector_pointers) > 1) and
                    ({p[0] for p in sector_pointers[1:]} != {0x01000000})):
                    from audiotools.text import ERR_DVDA_INVALID_SECTOR_POINTER
                    raise InvalidDVDA(ERR_DVDA_INVALID_SECTOR_POINTER)
                else:
                    sector_pointers = [None] + sector_pointers

                # build a preliminary DVDATitle object
                # which we'll populate with track data
                dvda_title = DVDATitle(dvdaudio=self,
                                       titleset=titleset,
                                       title=title_number + 1,
                                       pts_length=track_length,
                                       tracks=[])

                # for each track, determine its first and last sector
                # based on the sector pointers between the track's
                # initial index and the next track's initial index
                for (track_number,
                     (timestamp,
                      next_timestamp)) in enumerate(zip(timestamps,
                                                        timestamps[1:])):
                    (index_number, first_pts, pts_length) = timestamp
                    next_timestamp_index = next_timestamp[0]
                    dvda_title.tracks.append(
                        DVDATrack(
                            dvdaudio=self,
                            titleset=titleset,
                            title=dvda_title,
                            track=track_number + 1,
                            first_pts=first_pts,
                            pts_length=pts_length,
                            first_sector=sector_pointers[index_number][1],
                            last_sector=sector_pointers[
                                next_timestamp_index - 1][2]))

                # for the last track, its sector pointers
                # simply consume what remains on the list
                (index_number, first_pts, pts_length) = timestamps[-1]
                dvda_title.tracks.append(
                    DVDATrack(
                        dvdaudio=self,
                        titleset=titleset,
                        title=dvda_title,
                        track=len(timestamps),
                        first_pts=first_pts,
                        pts_length=pts_length,
                        first_sector=sector_pointers[index_number][1],
                        last_sector=sector_pointers[-1][2]))

                # fill in the title's info such as sample_rate, channels, etc.
                dvda_title.__parse_info__()

                titles.append(dvda_title)

            return titles
        finally:
            f.close()
Ejemplo n.º 50
0
    def __read_info__(self):
        from audiotools.bitstream import BitstreamReader
        from audiotools import ChannelMask

        reader = BitstreamReader(open(self.filename, "rb"), 1)
        reader.mark()
        try:
            (block_id,
             total_samples,
             bits_per_sample,
             mono_output,
             initial_block,
             final_block,
             sample_rate) = reader.parse(
                "4b 64p 32u 64p 2u 1u 8p 1u 1u 5p 5p 4u 37p")

            if (block_id != 'wvpk'):
                from audiotools.text import ERR_WAVPACK_INVALID_HEADER
                raise InvalidWavPack(ERR_WAVPACK_INVALID_HEADER)

            if (sample_rate != 0xF):
                self.__samplerate__ = WavPackAudio.SAMPLING_RATE[sample_rate]
            else:
                # if unknown, pull from SAMPLE_RATE sub-block
                for (block_id,
                     nondecoder,
                     data_size,
                     data) in self.sub_blocks(reader):
                    if ((block_id == 0x7) and nondecoder):
                        self.__samplerate__ = data.read(data_size * 8)
                        break
                else:
                    # no SAMPLE RATE sub-block found
                    # so pull info from FMT chunk
                    reader.rewind()
                    (self.__samplerate__,) = self.fmt_chunk(reader).parse(
                        "32p 32u")

            self.__bitspersample__ = [8, 16, 24, 32][bits_per_sample]
            self.__total_frames__ = total_samples

            if (initial_block and final_block):
                if (mono_output):
                    self.__channels__ = 1
                    self.__channel_mask__ = ChannelMask(0x4)
                else:
                    self.__channels__ = 2
                    self.__channel_mask__ = ChannelMask(0x3)
            else:
                # if not mono or stereo, pull from CHANNEL INFO sub-block
                reader.rewind()
                for (block_id,
                     nondecoder,
                     data_size,
                     data) in self.sub_blocks(reader):
                    if ((block_id == 0xD) and not nondecoder):
                        self.__channels__ = data.read(8)
                        self.__channel_mask__ = ChannelMask(
                            data.read((data_size - 1) * 8))
                        break
                else:
                    # no CHANNEL INFO sub-block found
                    # so pull info from FMT chunk
                    reader.rewind()
                    fmt = self.fmt_chunk(reader)
                    compression_code = fmt.read(16)
                    self.__channels__ = fmt.read(16)
                    if (compression_code == 1):
                        # this is theoretically possible
                        # with very old .wav files,
                        # but shouldn't happen in practice
                        self.__channel_mask__ = \
                            {1: ChannelMask.from_fields(front_center=True),
                             2: ChannelMask.from_fields(front_left=True,
                                                        front_right=True),
                             3: ChannelMask.from_fields(front_left=True,
                                                        front_right=True,
                                                        front_center=True),
                             4: ChannelMask.from_fields(front_left=True,
                                                        front_right=True,
                                                        back_left=True,
                                                        back_right=True),
                             5: ChannelMask.from_fields(front_left=True,
                                                        front_right=True,
                                                        back_left=True,
                                                        back_right=True,
                                                        front_center=True),
                             6: ChannelMask.from_fields(front_left=True,
                                                        front_right=True,
                                                        back_left=True,
                                                        back_right=True,
                                                        front_center=True,
                                                        low_frequency=True)
                             }.get(self.__channels__, ChannelMask(0))
                    elif (compression_code == 0xFFFE):
                        fmt.skip(128)
                        mask = fmt.read(32)
                        self.__channel_mask__ = ChannelMask(mask)
                    else:
                        from audiotools.text import ERR_WAVPACK_UNSUPPORTED_FMT
                        raise InvalidWavPack(ERR_WAVPACK_UNSUPPORTED_FMT)

        finally:
            reader.unmark()
            reader.close()
Ejemplo n.º 51
0
    def __parse_info__(self):
        """generates a cache of sample_rate, bits-per-sample, etc"""

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

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

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

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

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

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

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

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

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

        finally:
            aob_file.close()
Ejemplo n.º 52
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
Ejemplo n.º 53
0
def validate_header(header):
    """given header string as returned by wave_header_footer(),
    returns (total size, data size)
    where total size is the size of the file in bytes
    and data size is the size of the data chunk in bytes
    (not including any padding byte)

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

    raises ValueError if the header is invalid
    """

    from io import BytesIO
    from audiotools.bitstream import BitstreamReader

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

        fmt_found = False

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

            if chunk_id == b"fmt ":
                if not fmt_found:
                    # skip fmt chunk data when found
                    fmt_found = True
                    if chunk_size % 2:
                        wave_file.skip_bytes(chunk_size + 1)
                        header_size -= (chunk_size + 1)
                    else:
                        wave_file.skip_bytes(chunk_size)
                        header_size -= chunk_size
                else:
                    # ensure only one fmt chunk is found
                    from audiotools.text import ERR_WAV_MULTIPLE_FMT
                    raise ValueError(ERR_WAV_MULTIPLE_FMT)
            elif chunk_id == b"data":
                if not fmt_found:
                    # ensure at least one fmt chunk is found
                    from audiotools.text import ERR_WAV_PREMATURE_DATA
                    raise ValueError(ERR_WAV_PREMATURE_DATA)
                elif header_size > 0:
                    # ensure no data remains after data chunk header
                    from audiotools.text import ERR_WAV_HEADER_EXTRA_DATA
                    raise ValueError(
                        ERR_WAV_HEADER_EXTRA_DATA.format(header_size))
                else:
                    return (total_size, chunk_size)
            else:
                # skip the full contents of non-audio chunks
                if chunk_size % 2:
                    wave_file.skip_bytes(chunk_size + 1)
                    header_size -= (chunk_size + 1)
                else:
                    wave_file.skip_bytes(chunk_size)
                    header_size -= chunk_size
        else:
            # header parsed with no data chunks found
            from audiotools.text import ERR_WAV_NO_DATA_CHUNK
            raise ValueError(ERR_WAV_NO_DATA_CHUNK)
    except IOError:
        from audiotools.text import ERR_WAV_HEADER_IOERROR
        raise ValueError(ERR_WAV_HEADER_IOERROR)
Ejemplo n.º 54
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()
Ejemplo n.º 55
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
Ejemplo n.º 56
0
    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)