Example #1
0
    def delete_metadata(self):
        """deletes the track's MetaData

        this removes or unsets tags as necessary in order to remove all data
        raises IOError if unable to write the file"""

        import os
        from audiotools import (transfer_data,
                                LimitedFileReader,
                                TemporaryFile)
        from audiotools.id3 import skip_id3v2_comment

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

        # overwrite original with no tags attached
        old_tta = open(self.filename, "rb")
        skip_id3v2_comment(old_tta)
        old_tta = LimitedFileReader(old_tta, self.data_size())

        new_tta = TemporaryFile(self.filename)
        transfer_data(old_tta.read, new_tta.write)

        old_tta.close()
        new_tta.close()
Example #2
0
    def delete_metadata(self):
        """deletes the track's MetaData

        this removes or unsets tags as necessary in order to remove all data
        raises IOError if unable to write the file"""

        import os
        from audiotools import (TemporaryFile,
                                LimitedFileReader,
                                transfer_data)

        #this works a lot like update_metadata
        #but without any new metadata to set

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

        new_mp3 = TemporaryFile(self.filename)

        #get the original MP3 data
        old_mp3 = open(self.filename, "rb")
        MP3Audio.__find_last_mp3_frame__(old_mp3)
        data_end = old_mp3.tell()
        old_mp3.seek(0, 0)
        MP3Audio.__find_mp3_start__(old_mp3)
        data_start = old_mp3.tell()
        old_mp3 = LimitedFileReader(old_mp3, data_end - data_start)

        #write data to file
        transfer_data(old_mp3.read, new_mp3.write)

        #commit change to disk
        old_mp3.close()
        new_mp3.close()
Example #3
0
    def delete_metadata(self):
        """deletes the track's MetaData

        this removes or unsets tags as necessary in order to remove all data
        raises IOError if unable to write the file"""

        import os
        from audiotools import (transfer_data,
                                LimitedFileReader,
                                TemporaryFile)
        from audiotools.id3 import skip_id3v2_comment

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

        # overwrite original with no tags attached
        old_tta = open(self.filename, "rb")
        skip_id3v2_comment(old_tta)
        old_tta = LimitedFileReader(old_tta, self.data_size())

        new_tta = TemporaryFile(self.filename)
        transfer_data(old_tta.read, new_tta.write)

        old_tta.close()
        new_tta.close()
Example #4
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 import transfer_data, TemporaryFile
        from audiotools.id3 import ID3v22Comment
        from audiotools.bitstream import BitstreamRecorder
        from audiotools.text import ERR_FOREIGN_METADATA
        import os

        if metadata is None:
            return
        elif not isinstance(metadata, ID3v22Comment):
            raise ValueError(ERR_FOREIGN_METADATA)
        elif not os.access(self.filename, os.W_OK):
            raise IOError(self.filename)

        # turn our ID3v2.2 tag into a raw binary chunk
        id3_chunk = BitstreamRecorder(0)
        metadata.build(id3_chunk)

        # generate a temporary AIFF file in which our new ID3v2.2 chunk
        # replaces the existing ID3v2.2 chunk
        new_aiff = TemporaryFile(self.filename)

        self.__class__.aiff_from_chunks(
            new_aiff, [(chunk if chunk.id != b"ID3 " else AIFF_Chunk(
                b"ID3 ", id3_chunk.bytes(), id3_chunk.data()))
                       for chunk in self.chunks()])

        new_aiff.close()
Example #5
0
    def delete_metadata(self):
        """deletes the track's MetaData

        this removes or unsets tags as necessary in order to remove all data
        raises IOError if unable to write the file"""

        import os
        from audiotools import (TemporaryFile, LimitedFileReader,
                                transfer_data)

        #this works a lot like update_metadata
        #but without any new metadata to set

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

        new_mp3 = TemporaryFile(self.filename)

        #get the original MP3 data
        old_mp3 = open(self.filename, "rb")
        MP3Audio.__find_last_mp3_frame__(old_mp3)
        data_end = old_mp3.tell()
        old_mp3.seek(0, 0)
        MP3Audio.__find_mp3_start__(old_mp3)
        data_start = old_mp3.tell()
        old_mp3 = LimitedFileReader(old_mp3, data_end - data_start)

        #write data to file
        transfer_data(old_mp3.read, new_mp3.write)

        #commit change to disk
        old_mp3.close()
        new_mp3.close()
Example #6
0
    def set_metadata(self, metadata):
        """takes a MetaData object and sets this track's metadata

        this metadata includes track name, album name, and so on
        raises IOError if unable to write the file"""

        from audiotools.id3 import ID3v22Comment

        if metadata is None:
            return self.delete_metadata()
        elif self.get_metadata() is not None:
            # current file has metadata, so replace it with new metadata
            self.update_metadata(ID3v22Comment.converted(metadata))
        else:
            # current file has no metadata, so append new ID3 block
            import os
            from audiotools.bitstream import BitstreamRecorder
            from audiotools import transfer_data, TemporaryFile

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

            # turn our ID3v2.2 tag into a raw binary chunk
            id3_chunk = BitstreamRecorder(0)
            ID3v22Comment.converted(metadata).build(id3_chunk)

            # generate a temporary AIFF file in which our new ID3v2.2 chunk
            # is appended to the file's set of chunks
            new_aiff = TemporaryFile(self.filename)
            self.__class__.aiff_from_chunks(
                new_aiff, [c for c in self.chunks()] +
                [AIFF_Chunk(b"ID3 ", id3_chunk.bytes(), id3_chunk.data())])

            new_aiff.close()
Example #7
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()
Example #8
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()
Example #9
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()
Example #10
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
        """

        import os
        from audiotools import (TemporaryFile,
                                LimitedFileReader,
                                transfer_data)
        from audiotools.id3 import (ID3v2Comment, ID3CommentPair)
        from audiotools.id3v1 import ID3v1Comment
        from audiotools.bitstream import BitstreamWriter

        if (metadata is None):
            return
        elif (not (isinstance(metadata, ID3v2Comment) or
                   isinstance(metadata, ID3CommentPair) or
                   isinstance(metadata, ID3v1Comment))):
            from .text import ERR_FOREIGN_METADATA
            raise ValueError(ERR_FOREIGN_METADATA)
        elif (not os.access(self.filename, os.W_OK)):
            raise IOError(self.filename)

        new_mp3 = TemporaryFile(self.filename)

        #get the original MP3 data
        old_mp3 = open(self.filename, "rb")
        MP3Audio.__find_last_mp3_frame__(old_mp3)
        data_end = old_mp3.tell()
        old_mp3.seek(0, 0)
        MP3Audio.__find_mp3_start__(old_mp3)
        data_start = old_mp3.tell()
        old_mp3 = LimitedFileReader(old_mp3, data_end - data_start)

        #write id3v2 + data + id3v1 to file
        if (isinstance(metadata, ID3CommentPair)):
            metadata.id3v2.build(BitstreamWriter(new_mp3, 0))
            transfer_data(old_mp3.read, new_mp3.write)
            metadata.id3v1.build(new_mp3)
        elif (isinstance(metadata, ID3v2Comment)):
            metadata.build(BitstreamWriter(new_mp3, 0))
            transfer_data(old_mp3.read, new_mp3.write)
        elif (isinstance(metadata, ID3v1Comment)):
            transfer_data(old_mp3.read, new_mp3.write)
            metadata.build(new_mp3)

        #commit change to disk
        old_mp3.close()
        new_mp3.close()
    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
        """

        import os
        from audiotools import (TemporaryFile,
                                LimitedFileReader,
                                transfer_data)
        from audiotools.id3 import (ID3v2Comment, ID3CommentPair)
        from audiotools.id3v1 import ID3v1Comment
        from audiotools.bitstream import BitstreamWriter

        if metadata is None:
            return
        elif (not (isinstance(metadata, ID3v2Comment) or
                   isinstance(metadata, ID3CommentPair) or
                   isinstance(metadata, ID3v1Comment))):
            from audiotools.text import ERR_FOREIGN_METADATA
            raise ValueError(ERR_FOREIGN_METADATA)
        elif not os.access(self.filename, os.W_OK):
            raise IOError(self.filename)

        new_mp3 = TemporaryFile(self.filename)

        # get the original MP3 data
        old_mp3 = open(self.filename, "rb")
        MP3Audio.__find_last_mp3_frame__(old_mp3)
        data_end = old_mp3.tell()
        old_mp3.seek(0, 0)
        MP3Audio.__find_mp3_start__(old_mp3)
        data_start = old_mp3.tell()
        old_mp3 = LimitedFileReader(old_mp3, data_end - data_start)

        # write id3v2 + data + id3v1 to file
        if isinstance(metadata, ID3CommentPair):
            metadata.id3v2.build(BitstreamWriter(new_mp3, False))
            transfer_data(old_mp3.read, new_mp3.write)
            metadata.id3v1.build(new_mp3)
        elif isinstance(metadata, ID3v2Comment):
            metadata.build(BitstreamWriter(new_mp3, False))
            transfer_data(old_mp3.read, new_mp3.write)
        elif isinstance(metadata, ID3v1Comment):
            transfer_data(old_mp3.read, new_mp3.write)
            metadata.build(new_mp3)

        # commit change to disk
        old_mp3.close()
        new_mp3.close()
Example #12
0
    def delete_metadata(self):
        """deletes the track's MetaData

        this removes or unsets tags as necessary in order to remove all data
        raises IOError if unable to write the file"""

        import os
        from audiotools import transfer_data, TemporaryFile

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

        new_aiff = TemporaryFile(self.filename)
        self.__class__.aiff_from_chunks(
            new_aiff, [c for c in self.chunks() if c.id != b"ID3 "])

        new_aiff.close()
Example #13
0
    def delete_metadata(self):
        """deletes the track's MetaData

        this removes or unsets tags as necessary in order to remove all data
        raises IOError if unable to write the file"""

        import os
        from audiotools import transfer_data, TemporaryFile

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

        new_aiff = TemporaryFile(self.filename)
        self.__class__.aiff_from_chunks(
            new_aiff,
            [c for c in self.chunks() if c.id != b"ID3 "])

        new_aiff.close()
Example #14
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 import transfer_data, TemporaryFile
        from audiotools.id3 import ID3v22Comment
        from audiotools.bitstream import BitstreamRecorder
        from audiotools.text import ERR_FOREIGN_METADATA
        import os

        if metadata is None:
            return
        elif not isinstance(metadata, ID3v22Comment):
            raise ValueError(ERR_FOREIGN_METADATA)
        elif not os.access(self.filename, os.W_OK):
            raise IOError(self.filename)

        # turn our ID3v2.2 tag into a raw binary chunk
        id3_chunk = BitstreamRecorder(0)
        metadata.build(id3_chunk)

        # generate a temporary AIFF file in which our new ID3v2.2 chunk
        # replaces the existing ID3v2.2 chunk
        new_aiff = TemporaryFile(self.filename)

        self.__class__.aiff_from_chunks(
            new_aiff,
            [(chunk if chunk.id != b"ID3 " else
              AIFF_Chunk(b"ID3 ",
                         id3_chunk.bytes(),
                         id3_chunk.data())) for chunk in self.chunks()])

        new_aiff.close()
Example #15
0
    def set_metadata(self, metadata):
        """takes a MetaData object and sets this track's metadata

        this metadata includes track name, album name, and so on
        raises IOError if unable to write the file"""

        from audiotools.id3 import ID3v22Comment

        if metadata is None:
            return self.delete_metadata()
        elif self.get_metadata() is not None:
            # current file has metadata, so replace it with new metadata
            self.update_metadata(ID3v22Comment.converted(metadata))
        else:
            # current file has no metadata, so append new ID3 block
            import os
            from audiotools.bitstream import BitstreamRecorder
            from audiotools import transfer_data, TemporaryFile

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

            # turn our ID3v2.2 tag into a raw binary chunk
            id3_chunk = BitstreamRecorder(0)
            ID3v22Comment.converted(metadata).build(id3_chunk)

            # generate a temporary AIFF file in which our new ID3v2.2 chunk
            # is appended to the file's set of chunks
            new_aiff = TemporaryFile(self.filename)
            self.__class__.aiff_from_chunks(
                new_aiff,
                [c for c in self.chunks()] + [AIFF_Chunk(b"ID3 ",
                                                         id3_chunk.bytes(),
                                                         id3_chunk.data())])

            new_aiff.close()
Example #16
0
    def set_replay_gain(self, replaygain):
        """given a ReplayGain object, sets the track's gain to those values

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

        from math import log10
        from audiotools import TemporaryFile

        gain_title = int(round((64.82 - replaygain.track_gain) * 256))
        if replaygain.track_peak > 0.0:
            peak_title = int(log10(replaygain.track_peak * 2**15) * 20 * 256)
        else:
            peak_title = 0
        gain_album = int(round((64.82 - replaygain.album_gain) * 256))
        if replaygain.album_peak > 0.0:
            peak_album = int(log10(replaygain.album_peak * 2**15) * 20 * 256)
        else:
            peak_album = 0

        #FIXME - check for missing "RG" block and add one if not present

        metadata = self.get_metadata()

        writer = BitstreamWriter(TemporaryFile(self.filename), False)
        writer.write_bytes(b"MPCK")
        for key, size, block in self.blocks():
            if key != b"RG":
                writer.write_bytes(key)
                size.build(writer)
                writer.write_bytes(block)
            else:
                writer.write_bytes(b"RG")
                MPC_Size(2 + 1 + 1 + 2 * 4, 1).build(writer)
                writer.write(8, 1)
                writer.write(16, gain_title)
                writer.write(16, peak_title)
                writer.write(16, gain_album)
                writer.write(16, peak_album)

        if metadata is not None:
            writer.set_endianness(True)
            metadata.build(writer)

        writer.close()
Example #17
0
    def delete_replay_gain(self):
        """removes ReplayGain values from file, if any

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

        from audiotools import TemporaryFile

        writer = BitstreamWriter(TemporaryFile(self.filename), False)
        writer.write_bytes(b"MPCK")
        for key, size, block in self.blocks():
            if key != b"RG":
                writer.write_bytes(key)
                size.build(writer)
                writer.write_bytes(block)
            else:
                writer.write_bytes(b"RG")
                MPC_Size(2 + 1 + 1 + 2 * 4, 1).build(writer)
                writer.write(8, 1)
                writer.write(16, 0)
                writer.write(16, 0)
                writer.write(16, 0)
                writer.write(16, 0)
        writer.close()
Example #18
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)
Example #19
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
        """

        import os
        from audiotools.ape import ApeTag
        from audiotools.id3 import ID3v2Comment
        from audiotools.id3 import ID3CommentPair
        from audiotools.id3v1 import ID3v1Comment

        if (metadata is None):
            return
        elif (not os.access(self.filename, os.W_OK)):
            raise IOError(self.filename)

        # ensure metadata is APEv2, ID3v2, ID3v1, or ID3CommentPair
        if (((not isinstance(metadata, ApeTag)) and
             (not isinstance(metadata, ID3v2Comment)) and
             (not isinstance(metadata, ID3CommentPair)) and
             (not isinstance(metadata, ID3v1Comment)))):
            from audiotools.text import ERR_FOREIGN_METADATA
            raise ValueError(ERR_FOREIGN_METADATA)

        current_metadata = self.get_metadata()

        if (isinstance(metadata, ApeTag) and (current_metadata is None)):
            # if new metadata is APEv2 and no current metadata,
            # simply append APEv2 tag
            from audiotools.bitstream import BitstreamWriter
            with BitstreamWriter(open(self.filename, "ab"), True) as writer:
                metadata.build(writer)
        elif (isinstance(metadata, ApeTag) and
              isinstance(current_metadata, ApeTag) and
              (metadata.total_size() > current_metadata.total_size())):
            # if new metadata is APEv2, current metadata is APEv2
            # and new metadata is larger,
            # overwrite old tag with new tag
            from audiotools.bitstream import BitstreamWriter
            with open(self.filename, "r+b") as f:
                f.seek(-current_metadata.total_size(), 2)
                metadata.build(BitstreamWriter(f, True))
        else:
            from audiotools.bitstream import BitstreamWriter
            from audiotools import (transfer_data,
                                    LimitedFileReader,
                                    TemporaryFile)
            from audiotools.id3 import skip_id3v2_comment

            # otherwise, rebuild TTA with APEv2/ID3 tags in place
            old_tta = open(self.filename, "rb")
            skip_id3v2_comment(old_tta)
            old_tta = LimitedFileReader(old_tta, self.data_size())

            new_tta = TemporaryFile(self.filename)

            if (isinstance(metadata, ApeTag)):
                transfer_data(old_tta.read, new_tta.write)
                metadata.build(BitstreamWriter(new_tta, True))
            elif (isinstance(metadata, ID3CommentPair)):
                metadata.id3v2.build(BitstreamWriter(new_tta, False))
                transfer_data(old_tta.read, new_tta.write)
                metadata.id3v1.build(new_tta)
            elif (isinstance(metadata, ID3v2Comment)):
                metadata.build(BitstreamWriter(new_tta, False))
                transfer_data(old_tta.read, new_tta.write)
            else:
                # ID3v1Comment
                transfer_data(old_tta.read, new_tta.write)
                metadata.build(new_tta)

            old_tta.close()
            new_tta.close()
Example #20
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
        """

        import os
        from audiotools.ape import ApeTag
        from audiotools.id3 import ID3v2Comment
        from audiotools.id3 import ID3CommentPair
        from audiotools.id3v1 import ID3v1Comment

        if (metadata is None):
            return
        elif (not os.access(self.filename, os.W_OK)):
            raise IOError(self.filename)

        # ensure metadata is APEv2, ID3v2, ID3v1, or ID3CommentPair
        if (((not isinstance(metadata, ApeTag))
             and (not isinstance(metadata, ID3v2Comment))
             and (not isinstance(metadata, ID3CommentPair))
             and (not isinstance(metadata, ID3v1Comment)))):
            from audiotools.text import ERR_FOREIGN_METADATA
            raise ValueError(ERR_FOREIGN_METADATA)

        current_metadata = self.get_metadata()

        if (isinstance(metadata, ApeTag) and (current_metadata is None)):
            # if new metadata is APEv2 and no current metadata,
            # simply append APEv2 tag
            from audiotools.bitstream import BitstreamWriter
            with BitstreamWriter(open(self.filename, "ab"), True) as writer:
                metadata.build(writer)
        elif (isinstance(metadata, ApeTag)
              and isinstance(current_metadata, ApeTag)
              and (metadata.total_size() > current_metadata.total_size())):
            # if new metadata is APEv2, current metadata is APEv2
            # and new metadata is larger,
            # overwrite old tag with new tag
            from audiotools.bitstream import BitstreamWriter
            with open(self.filename, "r+b") as f:
                f.seek(-current_metadata.total_size(), 2)
                metadata.build(BitstreamWriter(f, True))
        else:
            from audiotools.bitstream import BitstreamWriter
            from audiotools import (transfer_data, LimitedFileReader,
                                    TemporaryFile)
            from audiotools.id3 import skip_id3v2_comment

            # otherwise, rebuild TTA with APEv2/ID3 tags in place
            old_tta = open(self.filename, "rb")
            skip_id3v2_comment(old_tta)
            old_tta = LimitedFileReader(old_tta, self.data_size())

            new_tta = TemporaryFile(self.filename)

            if (isinstance(metadata, ApeTag)):
                transfer_data(old_tta.read, new_tta.write)
                metadata.build(BitstreamWriter(new_tta, True))
            elif (isinstance(metadata, ID3CommentPair)):
                metadata.id3v2.build(BitstreamWriter(new_tta, False))
                transfer_data(old_tta.read, new_tta.write)
                metadata.id3v1.build(new_tta)
            elif (isinstance(metadata, ID3v2Comment)):
                metadata.build(BitstreamWriter(new_tta, False))
                transfer_data(old_tta.read, new_tta.write)
            else:
                # ID3v1Comment
                transfer_data(old_tta.read, new_tta.write)
                metadata.build(new_tta)

            old_tta.close()
            new_tta.close()
    def update_metadata(self, metadata, old_metadata=None):
        """takes this track's updated MetaData object
        as returned by get_metadata() and sets this track's metadata
        with any fields updated in that object

        old_metadata is the unmodifed metadata returned by get_metadata()

        raises IOError if unable to write the file
        """

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

        if metadata is None:
            return

        if not isinstance(metadata, M4A_META_Atom):
            from audiotools.text import ERR_FOREIGN_METADATA
            raise ValueError(ERR_FOREIGN_METADATA)

        if old_metadata is None:
            # get_metadata() result may still be None, and that's okay
            old_metadata = self.get_metadata()

        # M4A streams often have *two* "free" atoms we can attempt to resize

        # first, attempt to resize the one inside the "meta" atom
        if ((old_metadata is not None) and metadata.has_child(b"free")
                and ((metadata.size() - metadata[b"free"].size()) <=
                     old_metadata.size())):

            metadata.replace_child(
                M4A_FREE_Atom(old_metadata.size() -
                              (metadata.size() - metadata[b"free"].size())))

            f = open(self.filename, 'r+b')
            (meta_size,
             meta_offset) = get_m4a_atom_offset(BitstreamReader(f, False),
                                                b"moov", b"udta", b"meta")
            f.seek(meta_offset + 8, 0)
            with BitstreamWriter(f, False) as writer:
                metadata.build(writer)
            # writer will close "f" when finished
        else:
            from audiotools import TemporaryFile

            # if there's insufficient room,
            # attempt to resize the outermost "free" also

            # this is only possible if the file is laid out correctly,
            # with "free" coming after "moov" but before "mdat"
            # FIXME

            # if neither fix is possible, the whole file must be rewritten
            # which also requires adjusting the "stco" atom offsets
            with open(self.filename, "rb") as f:
                m4a_tree = M4A_Tree_Atom.parse(
                    None, os.path.getsize(self.filename),
                    BitstreamReader(f, False), {
                        b"moov": M4A_Tree_Atom,
                        b"trak": M4A_Tree_Atom,
                        b"mdia": M4A_Tree_Atom,
                        b"minf": M4A_Tree_Atom,
                        b"stbl": M4A_Tree_Atom,
                        b"stco": M4A_STCO_Atom,
                        b"udta": M4A_Tree_Atom
                    })

            # find initial mdat offset
            initial_mdat_offset = m4a_tree.child_offset(b"mdat")

            # adjust moov -> udta -> meta atom
            # (generating sub-atoms as necessary)
            if not m4a_tree.has_child(b"moov"):
                return
            else:
                moov = m4a_tree[b"moov"]
            if not moov.has_child(b"udta"):
                moov.add_child(M4A_Tree_Atom(b"udta", []))
            udta = moov[b"udta"]
            if not udta.has_child(b"meta"):
                udta.add_child(metadata)
            else:
                udta.replace_child(metadata)

            # find new mdat offset
            new_mdat_offset = m4a_tree.child_offset(b"mdat")

            # adjust moov -> trak -> mdia -> minf -> stbl -> stco offsets
            # based on the difference between the new mdat position and the old
            try:
                delta_offset = new_mdat_offset - initial_mdat_offset
                stco = m4a_tree[b"moov"][b"trak"][b"mdia"][b"minf"][b"stbl"][
                    b"stco"]
                stco.offsets = [
                    offset + delta_offset for offset in stco.offsets
                ]
            except KeyError:
                # if there is no stco atom, don't worry about it
                pass

            # then write entire tree back to disk
            with BitstreamWriter(TemporaryFile(self.filename),
                                 False) as writer:
                m4a_tree.build(writer)
Example #22
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
        """

        import os
        from audiotools import TemporaryFile
        from audiotools.ogg import (PageReader,
                                    PacketReader,
                                    PageWriter,
                                    packet_to_pages,
                                    packets_to_pages)
        from audiotools.vorbiscomment import VorbisComment
        from audiotools.bitstream import BitstreamRecorder

        if (metadata is None):
            return
        elif (not isinstance(metadata, VorbisComment)):
            from .text import ERR_FOREIGN_METADATA
            raise ValueError(ERR_FOREIGN_METADATA)
        elif (not os.access(self.filename, os.W_OK)):
            raise IOError(self.filename)

        original_ogg = PacketReader(PageReader(file(self.filename, "rb")))
        new_ogg = PageWriter(TemporaryFile(self.filename))

        sequence_number = 0

        #transfer current file's identification packet in its own page
        identification_packet = original_ogg.read_packet()
        for (i, page) in enumerate(packet_to_pages(
                identification_packet,
                self.__serial_number__,
                starting_sequence_number=sequence_number)):
            page.stream_beginning = (i == 0)
            new_ogg.write(page)
            sequence_number += 1

        #discard the current file's comment packet
        comment_packet = original_ogg.read_packet()

        #generate new comment packet
        comment_writer = BitstreamRecorder(True)
        comment_writer.build("8u 6b", (3, "vorbis"))
        vendor_string = metadata.vendor_string.encode('utf-8')
        comment_writer.build("32u %db" % (len(vendor_string)),
                             (len(vendor_string), vendor_string))
        comment_writer.write(32, len(metadata.comment_strings))
        for comment_string in metadata.comment_strings:
            comment_string = comment_string.encode('utf-8')
            comment_writer.build("32u %db" % (len(comment_string)),
                                 (len(comment_string), comment_string))

        comment_writer.build("1u a", (1,))  # framing bit

        #transfer codebooks packet from original file to new file
        codebooks_packet = original_ogg.read_packet()

        for page in packets_to_pages(
                [comment_writer.data(), codebooks_packet],
                self.__serial_number__,
                starting_sequence_number=sequence_number):
            new_ogg.write(page)
            sequence_number += 1

        #transfer remaining pages after re-sequencing
        page = original_ogg.read_page()
        page.sequence_number = sequence_number
        sequence_number += 1
        new_ogg.write(page)
        while (not page.stream_end):
            page = original_ogg.read_page()
            page.sequence_number = sequence_number
            page.bitstream_serial_number = self.__serial_number__
            sequence_number += 1
            new_ogg.write(page)

        original_ogg.close()
        new_ogg.close()
Example #23
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()
Example #24
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)
Example #25
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
        """

        import os
        from audiotools import TemporaryFile
        from audiotools.ogg import (PageReader, PacketReader, PageWriter,
                                    packet_to_pages)
        from audiotools.bitstream import BitstreamRecorder

        if (metadata is None):
            return
        elif (not isinstance(metadata, VorbisComment)):
            from .text import ERR_FOREIGN_METADATA
            raise ValueError(ERR_FOREIGN_METADATA)
        elif (not os.access(self.filename, os.W_OK)):
            raise IOError(self.filename)

        original_ogg = PacketReader(PageReader(file(self.filename, "rb")))
        new_ogg = PageWriter(TemporaryFile(self.filename))

        #transfer current file's identification page/packet
        #(the ID packet is always fixed size, and fits in one page)
        identification_page = original_ogg.read_page()
        new_ogg.write(identification_page)
        sequence_number = 1

        #discard the current file's comment packet
        original_ogg.read_packet()

        #write the new comment packet in its own page(s)
        comment_writer = BitstreamRecorder(True)
        comment_writer.write_bytes("OpusTags")
        vendor_string = metadata.vendor_string.encode('utf-8')
        comment_writer.build("32u %db" % (len(vendor_string)),
                             (len(vendor_string), vendor_string))
        comment_writer.write(32, len(metadata.comment_strings))
        for comment_string in metadata.comment_strings:
            comment_string = comment_string.encode('utf-8')
            comment_writer.build("32u %db" % (len(comment_string)),
                                 (len(comment_string), comment_string))

        for page in packet_to_pages(
                comment_writer.data(),
                identification_page.bitstream_serial_number,
                starting_sequence_number=sequence_number):
            new_ogg.write(page)
            sequence_number += 1

        #transfer remaining pages after re-sequencing
        page = original_ogg.read_page()
        page.sequence_number = sequence_number
        sequence_number += 1
        new_ogg.write(page)
        while (not page.stream_end):
            page = original_ogg.read_page()
            page.sequence_number = sequence_number
            sequence_number += 1
            new_ogg.write(page)

        original_ogg.close()
        new_ogg.close()