Esempio n. 1
0
    def parse(cls, mp3_file):
        """given an MP3 file, returns an ID3v1Comment

        raises ValueError if the comment is invalid"""

        from audiotools.bitstream import parse

        def decode_string(s):
            return s.rstrip(b"\x00").decode("ascii", "replace")

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

        return ID3v1Comment(track_name=decode_string(track_name),
                            artist_name=decode_string(artist_name),
                            album_name=decode_string(album_name),
                            year=decode_string(year),
                            comment=decode_string(comment),
                            track_number=track_number,
                            genre=genre)
Esempio n. 2
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)
Esempio n. 3
0
    def parse(cls, mp3_file):
        """given an MP3 file, returns an ID3v1Comment

        raises ValueError if the comment is invalid"""

        from audiotools.bitstream import parse

        def decode_string(s):
            return s.rstrip(b"\x00").decode("ascii", "replace")

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

        return ID3v1Comment(track_name=decode_string(track_name),
                            artist_name=decode_string(artist_name),
                            album_name=decode_string(album_name),
                            year=decode_string(year),
                            comment=decode_string(comment),
                            track_number=track_number,
                            genre=genre)
Esempio n. 4
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)
Esempio n. 5
0
    def __titlesets__(self):
        """return valid audio titleset integers from AUDIO_TS.IFO"""

        from audiotools.bitstream import parse

        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) = parse(
                "12b 32u 12P 32u 16u 4P 16u 16u 8u 4P 8u 32u 10P 8u 8u 40b",
                False,
                f.read(104))

            if (identifier != b'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()
Esempio n. 6
0
    def __init__(self, filename):
        AudioFile.__init__(self, filename)

        from audiotools.bitstream import parse
        from audiotools.text import (ERR_AU_INVALID_HEADER,
                                     ERR_AU_UNSUPPORTED_FORMAT)

        try:
            with open(filename, "rb") as f:
                (magic_number, self.__data_offset__, self.__data_size__,
                 encoding_format, self.__sample_rate__,
                 self.__channels__) = parse("4b 5* 32u", False, f.read(24))
        except IOError as msg:
            raise InvalidAU(str(msg))

        if (magic_number != b'.snd'):
            raise InvalidAU(ERR_AU_INVALID_HEADER)
        try:
            self.__bits_per_sample__ = {2: 8, 3: 16, 4: 24}[encoding_format]
        except KeyError:
            raise InvalidAU(ERR_AU_UNSUPPORTED_FORMAT)
Esempio n. 7
0
    def __init__(self, filename):
        AudioFile.__init__(self, filename)

        from audiotools.bitstream import parse
        from audiotools.text import (ERR_AU_INVALID_HEADER,
                                     ERR_AU_UNSUPPORTED_FORMAT)

        try:
            with open(filename, "rb") as f:
                (magic_number,
                 self.__data_offset__,
                 self.__data_size__,
                 encoding_format,
                 self.__sample_rate__,
                 self.__channels__) = parse("4b 5* 32u", False, f.read(24))
        except IOError as msg:
            raise InvalidAU(str(msg))

        if magic_number != b'.snd':
            raise InvalidAU(ERR_AU_INVALID_HEADER)
        try:
            self.__bits_per_sample__ = {2: 8, 3: 16, 4: 24}[encoding_format]
        except KeyError:
            raise InvalidAU(ERR_AU_UNSUPPORTED_FORMAT)
Esempio n. 8
0
    def __init__(self, filename):
        """filename is a plain string"""

        AudioFile.__init__(self, filename)

        from audiotools.bitstream import parse

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

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

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

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

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

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

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

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

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

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

                        (frame_sync,
                         mpeg_id,
                         layer,
                         bit_rate,
                         sample_rate,
                         pad) = reader.parse("11u 2u 2u 1p 4u 2u 1u 9p")
                except IOError:
                    pass
                except ValueError as err:
                    raise InvalidMP3(err)
        finally:
            mp3file.close()
Esempio n. 9
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 parse, BitstreamWriter
        from audiotools import transfer_data

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

        if len(tag_footer) < 32:
            # no existing ApeTag can fit, so append fresh tag
            f.close()
            with BitstreamWriter(open(self.filename, "ab"), True) as writer:
                metadata.build(writer)
            return

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

        if (preamble == b'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)
                writer = BitstreamWriter(f, True)
                metadata.build(writer)
                writer.close()
            else:
                f.close()

                # 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)

                with open(self.filename, "rb") as old_apev2:
                    limited_transfer_data(
                        old_apev2.read,
                        new_apev2.write,
                        getsize(self.filename) - old_tag_size)

                # append new tag to rewritten file
                with BitstreamWriter(new_apev2, True) as writer:
                    metadata.build(writer)
                    # closing writer closes new_apev2 also
        else:
            # no existing metadata, so simply append a fresh tag
            f.close()
            with BitstreamWriter(open(self.filename, "ab"), True) as writer:
                metadata.build(writer)
Esempio n. 10
0
    def __init__(self, filename):
        """filename is a plain string"""

        AudioFile.__init__(self, filename)

        from audiotools.bitstream import parse

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

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

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

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

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

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

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

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

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

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

                        (frame_sync, mpeg_id, layer, bit_rate, sample_rate,
                         pad) = reader.parse("11u 2u 2u 1p 4u 2u 1u 9p")
                except IOError:
                    pass
                except ValueError as err:
                    raise InvalidMP3(err)
        finally:
            mp3file.close()
Esempio n. 11
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
        """

        from audiotools.bitstream import (parse,
                                          BitstreamWriter,
                                          BitstreamReader)
        from audiotools import transfer_data

        if metadata is None:
            return
        elif not isinstance(metadata, ApeTag):
            from audiotools.text import ERR_FOREIGN_METADATA
            raise ValueError(ERR_FOREIGN_METADATA)
        elif len(metadata.keys()) == 0:
            # wipe out entire block of metadata

            from os import access, R_OK, W_OK

            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, transfer_data
                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()
        else:
            # re-set metadata block at end of file

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

            if len(tag_footer) < 32:
                # no existing ApeTag can fit, so append fresh tag
                f.close()
                with BitstreamWriter(open(self.filename, "ab"),
                                     True) as writer:
                    metadata.build(writer)
                return

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

            if (preamble == b'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)
                    writer = BitstreamWriter(f, True)
                    metadata.build(writer)
                    writer.close()
                else:
                    f.close()

                    # 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)

                    with open(self.filename, "rb") as old_apev2:
                        limited_transfer_data(
                            old_apev2.read,
                            new_apev2.write,
                            getsize(self.filename) - old_tag_size)

                    # append new tag to rewritten file
                    with BitstreamWriter(new_apev2, True) as writer:
                        metadata.build(writer)
                        # closing writer closes new_apev2 also
            else:
                # no existing metadata, so simply append a fresh tag
                f.close()
                with BitstreamWriter(open(self.filename, "ab"),
                                     True) as writer:
                    metadata.build(writer)