Example #1
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()
    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 #3
0
def encode_flac(filename, pcmreader,
                block_size=4096,
                max_lpc_order=8,
                adaptive_mid_side=False,
                mid_side=True,
                exhaustive_model_search=False,
                max_residual_partition_order=5):

    options = Encoding_Options(block_size,
                               max_lpc_order,
                               adaptive_mid_side,
                               mid_side,
                               exhaustive_model_search,
                               max_residual_partition_order,
                               14 if pcmreader.bits_per_sample <= 16 else 30)

    streaminfo = STREAMINFO(block_size, block_size,
                            2 ** 32, 0,
                            pcmreader.sample_rate,
                            pcmreader.channels,
                            pcmreader.bits_per_sample,
                            0, md5())

    pcmreader = BufferedPCMReader(pcmreader)
    output_file = open(filename, "wb")
    writer = BitstreamWriter(output_file, 0)

    #write placeholder metadata blocks
    writer.write_bytes("fLaC")
    writer.build("1u 7u 24u", [1, 0, 34])
    streaminfo.write(writer)

    #walk through PCM reader's FrameLists
    frame_number = 0
    frame = pcmreader.read(block_size *
                           (pcmreader.bits_per_sample / 8) *
                           pcmreader.channels)

    flac_frame = BitstreamRecorder(0)

    while (len(frame) > 0):

        streaminfo.input_update(frame)

        flac_frame.reset()
        encode_flac_frame(flac_frame, pcmreader, options, frame_number, frame)

        streaminfo.output_update(flac_frame)

        flac_frame.copy(writer)

        frame_number += 1
        frame = pcmreader.read(block_size *
                               (pcmreader.bits_per_sample / 8) *
                               pcmreader.channels)

    #return to beginning of file and rewrite STREAMINFO block
    output_file.seek(8, 0)
    streaminfo.write(writer)
    writer.close()
Example #4
0
    def set_metadata(self, metadata):
        """takes a MetaData object and sets this track's metadata

        raises IOError if unable to write the file"""

        from audiotools.bitstream import BitstreamWriter

        if metadata is None:
            return self.delete_metadata()

        new_metadata = ApeTag.converted(metadata)
        old_metadata = self.get_metadata()

        if old_metadata is not None:
            # transfer ReplayGain tags from old metadata to new metadata
            for tag in [b"replaygain_track_gain",
                        b"replaygain_track_peak",
                        b"replaygain_album_gain",
                        b"replaygain_album_peak"]:
                try:
                    # if old_metadata has tag, shift it over
                    new_metadata[tag] = old_metadata[tag]
                except KeyError:
                    try:
                        # otherwise, if new_metadata has tag, delete it
                        del(new_metadata[tag])
                    except KeyError:
                        # if neither has tag, ignore it
                        continue

            # transfer Cuesheet from old metadata to new metadata
            if b"Cuesheet" in old_metadata:
                new_metadata[b"Cuesheet"] = old_metadata[b"Cuesheet"]
            elif b"Cuesheet" in new_metadata:
                del(new_metadata[b"Cuesheet"])

            self.update_metadata(new_metadata)
        else:
            # delete ReplayGain tags from new metadata
            for tag in [b"replaygain_track_gain",
                        b"replaygain_track_peak",
                        b"replaygain_album_gain",
                        b"replaygain_album_peak"]:
                try:
                    del(new_metadata[tag])
                except KeyError:
                    continue

            # delete Cuesheet from new metadata
            if b"Cuesheet" in new_metadata:
                del(new_metadata[b"Cuesheet"])

            if len(new_metadata.keys()) > 0:
                # no existing metadata, so simply append a fresh tag
                with BitstreamWriter(open(self.filename, "ab"),
                                     True) as writer:
                    new_metadata.build(writer)
Example #5
0
    def build(self, mp3_file):
        """given an MP3 file positioned at the file's end, generate a tag"""

        from audiotools.bitstream import BitstreamWriter

        BitstreamWriter(mp3_file, 0).build(
            "3b 30b 30b 30b 4b 28b 8p 1b 1b",
            ("TAG", self.__track_name__, self.__artist_name__,
             self.__album_name__, self.__year__, self.__comment__,
             self.__track_number__, self.__genre__))
Example #6
0
def encode_mdat(file, pcmreader,
                block_size=4096,
                initial_history=10,
                history_multiplier=40,
                maximum_k=14,
                interlacing_shift=2,
                min_interlacing_leftweight=0,
                max_interlacing_leftweight=4):

    options = Encoding_Options(block_size,
                               initial_history,
                               history_multiplier,
                               maximum_k,
                               interlacing_shift,
                               min_interlacing_leftweight,
                               max_interlacing_leftweight)

    pcmreader = BufferedPCMReader(pcmreader)

    mdat = BitstreamWriter(file, 0)
    total_pcm_frames = 0
    frame_byte_sizes = []

    #write placeholder mdat header
    mdat_start = file.tell()
    mdat.write(32, 0)
    mdat.write_bytes("mdat")

    #read FrameList objects until stream is empty
    frame = pcmreader.read(block_size)
    while (len(frame) > 0):
        total_pcm_frames += frame.frames
        frame_start = file.tell()
        encode_frameset(mdat, pcmreader, options, frame)
        mdat.flush()
        frame_byte_sizes.append(file.tell() - frame_start)
        frame = pcmreader.read(block_size)

    #finally, return to start of mdat and write actual length
    file.seek(mdat_start)
    mdat.write(32, sum(frame_byte_sizes) + 8)

    return (frame_byte_sizes, total_pcm_frames)
Example #7
0
def encode_tta(file, pcmreader):
    """given a file object and buffered PCMReader,
    writes TTA frames to the writer
    and returns a list of TTA frame lengths, in bytes"""

    writer = BitstreamWriter(file, True)
    block_size = (pcmreader.sample_rate * 256) // 245
    frame_sizes = []

    # encode FrameLists from PCMReader to temporary space
    framelist = pcmreader.read(block_size)
    while len(framelist) > 0:
        frame_sizes.append(
            encode_tta_frame(writer, pcmreader.bits_per_sample, framelist))
        framelist = pcmreader.read(block_size)

    writer.flush()

    return frame_sizes
Example #8
0
def encode_mdat(
    file,
    pcmreader,
    block_size=4096,
    initial_history=10,
    history_multiplier=40,
    maximum_k=14,
    interlacing_shift=2,
    min_interlacing_leftweight=0,
    max_interlacing_leftweight=4,
):

    options = Encoding_Options(
        block_size,
        initial_history,
        history_multiplier,
        maximum_k,
        interlacing_shift,
        min_interlacing_leftweight,
        max_interlacing_leftweight,
    )

    pcmreader = BufferedPCMReader(pcmreader)

    mdat = BitstreamWriter(file, 0)
    total_pcm_frames = 0
    frame_byte_sizes = []

    # write placeholder mdat header
    mdat_start = file.tell()
    mdat.write(32, 0)
    mdat.write_bytes("mdat")

    # read FrameList objects until stream is empty
    frame = pcmreader.read(block_size)
    while len(frame) > 0:
        total_pcm_frames += frame.frames
        frame_start = file.tell()
        encode_frameset(mdat, pcmreader, options, frame)
        mdat.flush()
        frame_byte_sizes.append(file.tell() - frame_start)
        frame = pcmreader.read(block_size)

    # finally, return to start of mdat and write actual length
    file.seek(mdat_start)
    mdat.write(32, sum(frame_byte_sizes) + 8)

    return (frame_byte_sizes, total_pcm_frames)
Example #9
0
def encode_tta(file, pcmreader):
    """given a file object and buffered PCMReader,
    writes TTA frames to the writer
    and returns a list of TTA frame lengths, in bytes"""

    writer = BitstreamWriter(file, True)
    block_size = (pcmreader.sample_rate * 256) / 245
    frame_sizes = []

    #encode FrameLists from PCMReader to temporary space
    framelist = pcmreader.read(block_size)
    while (len(framelist) > 0):
        frame_sizes.append(encode_tta_frame(writer,
                                            pcmreader.bits_per_sample,
                                            framelist))
        framelist = pcmreader.read(block_size)

    writer.flush()

    return frame_sizes
Example #10
0
def encode_shn(filename,
               pcmreader,
               is_big_endian,
               signed_samples,
               header_data,
               footer_data="",
               block_size=256):
    """filename is a string to the output file's path
    pcmreader is a PCMReader object
    header_data and footer_data are binary strings
    block_size is the default size of each Shorten audio command
    """

    pcmreader = BufferedPCMReader(pcmreader)
    output_file = open(filename, "wb")
    writer = BitstreamWriter(output_file, 0)

    left_shift = 0
    wrapped_channels = [[] for c in xrange(pcmreader.channels)]

    #write magic number and version
    writer.build("4b 8u", ["ajkg", 2])

    bytes_written = __Counter__()
    writer.add_callback(bytes_written.byte)

    #write header from PCMReader info and encoding options
    if (pcmreader.bits_per_sample == 8):
        if (signed_samples):
            write_long(writer, 1)  # signed, 8-bit
            sign_adjustment = 0
        else:
            write_long(writer, 2)  # unsigned, 8-bit
            sign_adjustment = 1 << (pcmreader.bits_per_sample - 1)
        #8-bit samples have no endianness
    elif (pcmreader.bits_per_sample == 16):
        if (signed_samples):
            if (is_big_endian):
                write_long(writer, 3)  # signed, 16-bit, big-endian
            else:
                write_long(writer, 5)  # signed, 16-bit, little-endian
            sign_adjustment = 0
        else:
            if (is_big_endian):
                write_long(writer, 4)  # unsigned, 16-bit, big-endian
            else:
                write_long(writer, 6)  # unsigned, 16-bit, little-endian
            sign_adjustment = 1 << (pcmreader.bits_per_sample - 1)
    else:
        raise ValueError("unsupported bits_per_sample")

    write_long(writer, pcmreader.channels)
    write_long(writer, block_size)
    write_long(writer, 0)  # max LPC
    write_long(writer, 0)  # mean count
    write_long(writer, 0)  # bytes to skip

    #write header as a VERBATIM block
    write_unsigned(writer, COMMAND_SIZE, FN_VERBATIM)
    write_unsigned(writer, VERBATIM_SIZE, len(header_data))
    for b in header_data:
        write_unsigned(writer, VERBATIM_BYTE_SIZE, ord(b))

    #split PCMReader into block_size chunks
    #and continue until the number of PCM frames is 0
    frame = pcmreader.read(block_size)
    while (len(frame) > 0):
        #if the chunk isn't block_size frames long,
        #issue a command to change it
        if (frame.frames != block_size):
            block_size = frame.frames
            write_unsigned(writer, COMMAND_SIZE, FN_BLOCKSIZE)
            write_long(writer, block_size)

        #split chunk into individual channels
        for c in xrange(pcmreader.channels):
            #convert PCM data to unsigned, if necessary
            if (signed_samples):
                channel = list(frame.channel(c))
            else:
                channel = [s + sign_adjustment for s in frame.channel(c)]

            #if all samples are 0, issue a ZERO command
            if (all_zeroes(channel)):
                write_unsigned(writer, COMMAND_SIZE, FN_ZERO)

                #wrap zeroes around for next set of channels
                wrapped_channels[c] = channel
            else:
                #if channel's shifted bits have changed
                #from the previous channel's shift
                #issue a new BITSHIFT command
                wasted_bits = wasted_bps(channel)
                if (wasted_bits != left_shift):
                    write_unsigned(writer, COMMAND_SIZE, FN_BITSHIFT)
                    write_unsigned(writer, BITSHIFT_SIZE, wasted_bits)
                    left_shift = wasted_bits

                #and shift the channel's bits if the amount is still > 0
                if (left_shift > 0):
                    shifted = [s >> left_shift for s in channel]
                else:
                    shifted = channel

                #determine the best DIFF command and residuals
                #to issue for shifted channel data
                (diff, residuals) = best_diff(wrapped_channels[c], shifted)

                #determine the best energy size for DIFF's residuals
                energy = best_energy(residuals)

                #write DIFF command, energy size and residuals
                write_unsigned(writer, COMMAND_SIZE, {
                    1: FN_DIFF1,
                    2: FN_DIFF2,
                    3: FN_DIFF3
                }[diff])
                write_unsigned(writer, ENERGY_SIZE, energy)
                for residual in residuals:
                    write_signed(writer, energy, residual)

                #wrap shifted channels around for next set of channels
                wrapped_channels[c] = shifted

        #and get another set of channels to encode
        frame = pcmreader.read(block_size)

    #once all PCM data has been sent
    #if there's any footer data, write it as another VERBATIM block
    if (len(footer_data) > 0):
        write_unsigned(writer, COMMAND_SIZE, FN_VERBATIM)
        write_unsigned(writer, VERBATIM_SIZE, len(footer_data))
        for b in footer_data:
            write_unsigned(writer, VERBATIM_BYTE_SIZE, ord(b))

    #issue a QUIT command
    write_unsigned(writer, COMMAND_SIZE, FN_QUIT)

    #finally, due to Shorten's silly way of using bit buffers,
    #output (not counting the 5 bytes of magic + version)
    #must be padded to a multiple of 4 bytes
    #or its reference decoder explodes
    writer.byte_align()
    while ((int(bytes_written) % 4) != 0):
        writer.write(8, 0)
Example #11
0
def encode_flac(filename,
                pcmreader,
                block_size=4096,
                max_lpc_order=8,
                min_residual_partition_order=0,
                max_residual_partition_order=5,
                mid_side=True,
                adaptive_mid_side=False,
                exhaustive_model_search=False,
                disable_verbatim_subframes=False,
                disable_constant_subframes=False,
                disable_fixed_subframes=False,
                disable_lpc_subframes=False,
                padding_size=4096):

    frame_sizes = []

    options = Encoding_Options(block_size,
                               max_lpc_order,
                               adaptive_mid_side,
                               mid_side,
                               exhaustive_model_search,
                               max_residual_partition_order,
                               14 if pcmreader.bits_per_sample <= 16 else 30)

    streaminfo = STREAMINFO(block_size,
                            block_size,
                            (2 ** 24) - 1,
                            0,
                            pcmreader.sample_rate,
                            pcmreader.channels,
                            pcmreader.bits_per_sample,
                            0, md5())

    pcmreader = BufferedPCMReader(pcmreader)
    output_file = open(filename, "wb")
    writer = BitstreamWriter(output_file, False)

    # write placeholder metadata blocks such as STREAMINFO and PADDING
    writer.write_bytes("fLaC")
    writer.build("1u 7u 24u", [0, 0, 34])
    streaminfo_start = writer.getpos()
    streaminfo.write(writer)

    writer.build("1u 7u 24u", [1, 1, padding_size])
    writer.write_bytes(b"\x00" * padding_size)

    # walk through PCM reader's FrameLists
    frame_number = 0
    frame = pcmreader.read(block_size)

    flac_frame = BitstreamRecorder(0)

    while len(frame) > 0:
        streaminfo.input_update(frame)

        flac_frame.reset()
        encode_flac_frame(flac_frame, pcmreader, options, frame_number, frame)
        frame_sizes.append((flac_frame.bytes(), frame.frames))
        streaminfo.output_update(flac_frame)

        flac_frame.copy(writer)

        frame_number += 1
        frame = pcmreader.read(block_size)

    # return to beginning of file and rewrite STREAMINFO block
    writer.setpos(streaminfo_start)
    streaminfo.write(writer)
    writer.flush()
    writer.close()

    return frame_sizes
Example #12
0
def encode_mdat(file, pcmreader,
                block_size=4096,
                initial_history=10,
                history_multiplier=40,
                maximum_K=14,
                interlacing_shift=2,
                min_interlacing_leftweight=0,
                max_interlacing_leftweight=4):

    options = Encoding_Options(block_size,
                               initial_history,
                               history_multiplier,
                               maximum_K,
                               interlacing_shift,
                               min_interlacing_leftweight,
                               max_interlacing_leftweight)

    pcmreader = BufferedPCMReader(pcmreader)

    mdat = BitstreamWriter(file, 0)
    mdat_length = ByteCounter()
    mdat.add_callback(mdat_length.update)

    frame_sample_sizes = []
    frame_byte_sizes = []
    frame_file_offsets = []

    #write placeholder mdat header
    mdat.write(32, 0)
    mdat.write_bytes("mdat")

    #read FrameList objects until stream is empty
    frame = pcmreader.read(block_size *
                           pcmreader.channels *
                           (pcmreader.bits_per_sample / 8))
    while (len(frame) > 0):
        frame_sample_sizes.append(frame.frames)
        frame_file_offsets.append(int(mdat_length))
        encode_frameset(mdat, pcmreader, options, frame)
        frame_byte_sizes.append(int(mdat_length) - frame_file_offsets[-1])
        frame = pcmreader.read(block_size *
                               pcmreader.channels *
                               (pcmreader.bits_per_sample / 8))

    #finally, return to start of mdat and write actual length
    mdat.byte_align()
    mdat.pop_callback()
    file.seek(0, 0)
    mdat.write(32, int(mdat_length))

    return (frame_sample_sizes,
            frame_byte_sizes,
            frame_file_offsets,
            int(mdat_length))
Example #13
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()
    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 #15
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 #16
0
def encode_flac(filename,
                pcmreader,
                block_size=4096,
                max_lpc_order=8,
                min_residual_partition_order=0,
                max_residual_partition_order=5,
                mid_side=True,
                adaptive_mid_side=False,
                exhaustive_model_search=False,
                disable_verbatim_subframes=False,
                disable_constant_subframes=False,
                disable_fixed_subframes=False,
                disable_lpc_subframes=False,
                padding_size=4096):

    frame_sizes = []

    options = Encoding_Options(block_size, max_lpc_order, adaptive_mid_side,
                               mid_side, exhaustive_model_search,
                               max_residual_partition_order,
                               14 if pcmreader.bits_per_sample <= 16 else 30)

    streaminfo = STREAMINFO(block_size, block_size, (2**24) - 1, 0,
                            pcmreader.sample_rate, pcmreader.channels,
                            pcmreader.bits_per_sample, 0, md5())

    pcmreader = BufferedPCMReader(pcmreader)
    output_file = open(filename, "wb")
    writer = BitstreamWriter(output_file, False)

    # write placeholder metadata blocks such as STREAMINFO and PADDING
    writer.write_bytes("fLaC")
    writer.build("1u 7u 24u", [0, 0, 34])
    streaminfo_start = writer.getpos()
    streaminfo.write(writer)

    writer.build("1u 7u 24u", [1, 1, padding_size])
    writer.write_bytes(b"\x00" * padding_size)

    # walk through PCM reader's FrameLists
    frame_number = 0
    frame = pcmreader.read(block_size)

    flac_frame = BitstreamRecorder(0)

    while len(frame) > 0:
        streaminfo.input_update(frame)

        flac_frame.reset()
        encode_flac_frame(flac_frame, pcmreader, options, frame_number, frame)
        frame_sizes.append((flac_frame.bytes(), frame.frames))
        streaminfo.output_update(flac_frame)

        flac_frame.copy(writer)

        frame_number += 1
        frame = pcmreader.read(block_size)

    # return to beginning of file and rewrite STREAMINFO block
    writer.setpos(streaminfo_start)
    streaminfo.write(writer)
    writer.flush()
    writer.close()

    return frame_sizes
    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 #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
        """

        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 #19
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"""

        import os
        from audiotools.ape import ApeTag
        from audiotools.bitstream import BitstreamWriter

        if (metadata is None):
            return
        else:
            new_metadata = ApeTag.converted(metadata)

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

        # if current metadata is present and in a particular format
        # set_metadata() should continue using that format
        old_metadata = ApeTag.converted(self.get_metadata())
        if (old_metadata is not None):
            # transfer ReplayGain tags from old metadata to new metadata
            for tag in [
                    b"replaygain_track_gain", b"replaygain_track_peak",
                    b"replaygain_album_gain", b"replaygain_album_peak"
            ]:
                try:
                    # if old_metadata has tag, shift it over
                    new_metadata[tag] = old_metadata[tag]
                except KeyError:
                    try:
                        # otherwise, if new_metadata has tag, delete it
                        del (new_metadata[tag])
                    except KeyError:
                        # if neither has tag, ignore it
                        continue

            # transfer Cuesheet from old metadata to new metadata
            if (b"Cuesheet" in old_metadata):
                new_metadata[b"Cuesheet"] = old_metadata[b"Cuesheet"]
            elif (b"Cuesheet" in new_metadata):
                del (new_metadata[b"Cuesheet"])

            self.update_metadata(new_metadata)
        else:
            # delete ReplayGain tags from new metadata
            for tag in [
                    b"replaygain_track_gain", b"replaygain_track_peak",
                    b"replaygain_album_gain", b"replaygain_album_peak"
            ]:
                try:
                    del (new_metadata[tag])
                except KeyError:
                    continue

            # delete Cuesheet from new metadata
            if (b"Cuesheet" in new_metadata):
                del (new_metadata[b"Cuesheet"])

            # no current metadata, so append a fresh APEv2 tag
            with BitstreamWriter(open(self.filename, "ab"), True) as writer:
                new_metadata.build(writer)
Example #20
0
    def from_pcm(cls,
                 filename,
                 pcmreader,
                 compression=None,
                 total_pcm_frames=None,
                 encoding_function=None):
        """encodes a new file from PCM data

        takes a filename string, PCMReader object,
        optional compression level string and
        optional total_pcm_frames integer
        encodes a new audio file from pcmreader's data
        at the given filename with the specified compression level
        and returns a new AudioFile-compatible object

        may raise EncodingError if some problem occurs when
        encoding the input file.  This includes an error
        in the input stream, a problem writing the output file,
        or even an EncodingError subclass such as
        "UnsupportedBitsPerSample" if the input stream
        is formatted in a way this class is unable to support
        """

        from audiotools import (BufferedPCMReader, CounterPCMReader,
                                transfer_data, EncodingError)
        # from audiotools.py_encoders import encode_tta
        from audiotools.encoders import encode_tta
        from audiotools.bitstream import BitstreamWriter

        # open output file right away
        # so we can fail as soon as possible
        try:
            file = open(filename, "wb")
        except IOError as err:
            pcmreader.close()
            raise EncodingError(str(err))

        writer = BitstreamWriter(file, True)
        counter = CounterPCMReader(pcmreader)
        try:
            if (total_pcm_frames is not None):
                # write header to disk
                write_header(writer, pcmreader.channels,
                             pcmreader.bits_per_sample, pcmreader.sample_rate,
                             total_pcm_frames)

                block_size = (pcmreader.sample_rate * 256) // 245
                total_tta_frames = div_ceil(total_pcm_frames, block_size)

                # write temporary seektable to disk
                writer.mark()
                write_seektable(writer, [0] * total_tta_frames)
                writer.flush()

                # write frames to disk
                try:
                    frame_sizes = \
                        (encode_tta if encoding_function is None
                         else encoding_function)(file,
                                                 BufferedPCMReader(counter))
                except (IOError, ValueError) as err:
                    cls.__unlink__(filename)
                    raise EncodingError(str(err))

                # ensure written number of PCM frames
                # matches total_pcm_frames
                if (counter.frames_written != total_pcm_frames):
                    from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH
                    cls.__unlink__(filename)
                    raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH)

                assert (len(frame_sizes) == total_tta_frames)

                # go back and rewrite seektable with completed one
                writer.rewind()
                write_seektable(writer, frame_sizes)
                writer.unmark()
            else:
                import tempfile

                frames = tempfile.TemporaryFile()

                # encode TTA frames to temporary file
                try:
                    frame_sizes = \
                        (encode_tta if encoding_function is None
                         else encoding_function)(frames,
                                                 BufferedPCMReader(counter))
                except (IOError, ValueError) as err:
                    frames.close()
                    cls.__unlink__(filename)
                    raise EncodingError(str(err))

                # write header to disk
                write_header(writer, pcmreader.channels,
                             pcmreader.bits_per_sample, pcmreader.sample_rate,
                             counter.frames_written)

                # write seektable to disk
                write_seektable(writer, frame_sizes)

                # transfer TTA frames from temporary space to disk
                frames.seek(0, 0)
                transfer_data(frames.read, writer.write_bytes)
                frames.close()
        finally:
            counter.close()
            if (writer.has_mark()):
                writer.unmark()
            writer.close()

        return cls(filename)
Example #21
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)
Example #22
0
def encode_wavpack(filename, pcmreader, block_size, correlation_passes=0, wave_header=None, wave_footer=None):
    pcmreader = BufferedPCMReader(pcmreader)
    output_file = open(filename, "wb")
    writer = BitstreamWriter(output_file, 1)
    context = EncoderContext(
        pcmreader,
        block_parameters(pcmreader.channels, pcmreader.channel_mask, correlation_passes),
        wave_header,
        wave_footer,
    )

    block_index = 0

    # walk through PCM reader's FrameLists
    frame = pcmreader.read(block_size * (pcmreader.bits_per_sample / 8) * pcmreader.channels)
    while len(frame) > 0:
        context.total_frames += frame.frames
        context.md5sum.update(frame.to_bytes(False, pcmreader.bits_per_sample >= 16))

        c = 0
        for parameters in context.block_parameters:
            if parameters.channel_count == 1:
                channel_data = [list(frame.channel(c))]
            else:
                channel_data = [list(frame.channel(c)), list(frame.channel(c + 1))]

            first_block = parameters is context.block_parameters[0]
            last_block = parameters is context.block_parameters[-1]

            context.block_offsets.append(output_file.tell())
            write_block(writer, context, channel_data, block_index, first_block, last_block, parameters)

            c += parameters.channel_count

        block_index += frame.frames
        frame = pcmreader.read(block_size * (pcmreader.bits_per_sample / 8) * pcmreader.channels)

    # write MD5 sum and optional Wave footer in final block
    sub_blocks = BitstreamRecorder(1)
    sub_block = BitstreamRecorder(1)

    sub_block.reset()
    sub_block.write_bytes(context.md5sum.digest())
    write_sub_block(sub_blocks, WV_MD5, 1, sub_block)

    # write Wave footer in final block, if present
    if context.wave_footer is not None:
        sub_block.reset()
        sub_block.write_bytes(context.wave_footer)
        write_sub_block(sub_blocks, WV_WAVE_FOOTER, 1, sub_block)

    write_block_header(
        writer,
        sub_blocks.bytes(),
        0xFFFFFFFF,
        0,
        pcmreader.bits_per_sample,
        1,
        0,
        0,
        0,
        1,
        1,
        0,
        pcmreader.sample_rate,
        0,
        0xFFFFFFFF,
    )
    sub_blocks.copy(writer)

    # update Wave header's "data" chunk size, if generated
    if context.wave_header is None:
        output_file.seek(32 + 2)
        if context.wave_footer is None:
            write_wave_header(writer, context.pcmreader, context.total_frames, 0)
        else:
            write_wave_header(writer, context.pcmreader, context.total_frames, len(context.wave_footer))

    # go back and populate block headers with total samples
    for block_offset in context.block_offsets:
        output_file.seek(block_offset + 12, 0)
        writer.write(32, block_index)

    writer.close()
    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 #24
0
def encode_shn(filename, pcmreader, is_big_endian, signed_samples, header_data, footer_data="", block_size=256):
    """filename is a string to the output file's path
    pcmreader is a PCMReader object
    header_data and footer_data are binary strings
    block_size is the default size of each Shorten audio command
    """

    pcmreader = BufferedPCMReader(pcmreader)
    output_file = open(filename, "wb")
    writer = BitstreamWriter(output_file, 0)

    left_shift = 0
    wrapped_channels = [[] for c in xrange(pcmreader.channels)]

    # write magic number and version
    writer.build("4b 8u", ["ajkg", 2])

    bytes_written = __Counter__()
    writer.add_callback(bytes_written.byte)

    # write header from PCMReader info and encoding options
    if pcmreader.bits_per_sample == 8:
        if signed_samples:
            write_long(writer, 1)  # signed, 8-bit
            sign_adjustment = 0
        else:
            write_long(writer, 2)  # unsigned, 8-bit
            sign_adjustment = 1 << (pcmreader.bits_per_sample - 1)
        # 8-bit samples have no endianness
    elif pcmreader.bits_per_sample == 16:
        if signed_samples:
            if is_big_endian:
                write_long(writer, 3)  # signed, 16-bit, big-endian
            else:
                write_long(writer, 5)  # signed, 16-bit, little-endian
            sign_adjustment = 0
        else:
            if is_big_endian:
                write_long(writer, 4)  # unsigned, 16-bit, big-endian
            else:
                write_long(writer, 6)  # unsigned, 16-bit, little-endian
            sign_adjustment = 1 << (pcmreader.bits_per_sample - 1)
    else:
        raise ValueError("unsupported bits_per_sample")

    write_long(writer, pcmreader.channels)
    write_long(writer, block_size)
    write_long(writer, 0)  # max LPC
    write_long(writer, 0)  # mean count
    write_long(writer, 0)  # bytes to skip

    # write header as a VERBATIM block
    write_unsigned(writer, COMMAND_SIZE, FN_VERBATIM)
    write_unsigned(writer, VERBATIM_SIZE, len(header_data))
    for b in header_data:
        write_unsigned(writer, VERBATIM_BYTE_SIZE, ord(b))

    # split PCMReader into block_size chunks
    # and continue until the number of PCM frames is 0
    frame = pcmreader.read(block_size)
    while len(frame) > 0:
        # if the chunk isn't block_size frames long,
        # issue a command to change it
        if frame.frames != block_size:
            block_size = frame.frames
            write_unsigned(writer, COMMAND_SIZE, FN_BLOCKSIZE)
            write_long(writer, block_size)

        # split chunk into individual channels
        for c in xrange(pcmreader.channels):
            # convert PCM data to unsigned, if necessary
            if signed_samples:
                channel = list(frame.channel(c))
            else:
                channel = [s + sign_adjustment for s in frame.channel(c)]

            # if all samples are 0, issue a ZERO command
            if all_zeroes(channel):
                write_unsigned(writer, COMMAND_SIZE, FN_ZERO)

                # wrap zeroes around for next set of channels
                wrapped_channels[c] = channel
            else:
                # if channel's shifted bits have changed
                # from the previous channel's shift
                # issue a new BITSHIFT command
                wasted_bits = wasted_bps(channel)
                if wasted_bits != left_shift:
                    write_unsigned(writer, COMMAND_SIZE, FN_BITSHIFT)
                    write_unsigned(writer, BITSHIFT_SIZE, wasted_bits)
                    left_shift = wasted_bits

                # and shift the channel's bits if the amount is still > 0
                if left_shift > 0:
                    shifted = [s >> left_shift for s in channel]
                else:
                    shifted = channel

                # determine the best DIFF command and residuals
                # to issue for shifted channel data
                (diff, residuals) = best_diff(wrapped_channels[c], shifted)

                # determine the best energy size for DIFF's residuals
                energy = best_energy(residuals)

                # write DIFF command, energy size and residuals
                write_unsigned(writer, COMMAND_SIZE, {1: FN_DIFF1, 2: FN_DIFF2, 3: FN_DIFF3}[diff])
                write_unsigned(writer, ENERGY_SIZE, energy)
                for residual in residuals:
                    write_signed(writer, energy, residual)

                # wrap shifted channels around for next set of channels
                wrapped_channels[c] = shifted

        # and get another set of channels to encode
        frame = pcmreader.read(block_size)

    # once all PCM data has been sent
    # if there's any footer data, write it as another VERBATIM block
    if len(footer_data) > 0:
        write_unsigned(writer, COMMAND_SIZE, FN_VERBATIM)
        write_unsigned(writer, VERBATIM_SIZE, len(footer_data))
        for b in footer_data:
            write_unsigned(writer, VERBATIM_BYTE_SIZE, ord(b))

    # issue a QUIT command
    write_unsigned(writer, COMMAND_SIZE, FN_QUIT)

    # finally, due to Shorten's silly way of using bit buffers,
    # output (not counting the 5 bytes of magic + version)
    # must be padded to a multiple of 4 bytes
    # or its reference decoder explodes
    writer.byte_align()
    while (int(bytes_written) % 4) != 0:
        writer.write(8, 0)
Example #25
0
def encode_mdat(file, pcmreader,
                block_size=4096,
                initial_history=10,
                history_multiplier=40,
                maximum_k=14,
                interlacing_shift=2,
                min_interlacing_leftweight=0,
                max_interlacing_leftweight=4):

    options = Encoding_Options(block_size,
                               initial_history,
                               history_multiplier,
                               maximum_k,
                               interlacing_shift,
                               min_interlacing_leftweight,
                               max_interlacing_leftweight)

    pcmreader = BufferedPCMReader(pcmreader)

    mdat = BitstreamWriter(file, False)
    total_pcm_frames = 0
    frame_byte_sizes = []

    # write placeholder mdat header
    mdat.mark()
    mdat.write(32, 0)
    mdat.write_bytes(b"mdat")

    # read FrameList objects until stream is empty
    frame = pcmreader.read(block_size)
    while (len(frame) > 0):
        total_pcm_frames += frame.frames
        frame_byte_size = Counter()
        mdat.add_callback(frame_byte_size.add)
        encode_frameset(mdat, pcmreader, options, frame)
        mdat.pop_callback()
        frame_byte_sizes.append(int(frame_byte_size))
        frame = pcmreader.read(block_size)

    # finally, return to start of mdat and write actual length
    mdat.rewind()
    mdat.write(32, sum(frame_byte_sizes) + 8)
    mdat.unmark()

    return (frame_byte_sizes, total_pcm_frames)
Example #26
0
    def from_pcm(cls, filename, pcmreader,
                 compression=None,
                 total_pcm_frames=None,
                 encoding_function=None):
        """encodes a new file from PCM data

        takes a filename string, PCMReader object,
        optional compression level string and
        optional total_pcm_frames integer
        encodes a new audio file from pcmreader's data
        at the given filename with the specified compression level
        and returns a new AudioFile-compatible object

        may raise EncodingError if some problem occurs when
        encoding the input file.  This includes an error
        in the input stream, a problem writing the output file,
        or even an EncodingError subclass such as
        "UnsupportedBitsPerSample" if the input stream
        is formatted in a way this class is unable to support
        """

        from audiotools import (BufferedPCMReader,
                                CounterPCMReader,
                                transfer_data,
                                EncodingError)
        # from audiotools.py_encoders import encode_tta
        from audiotools.encoders import encode_tta
        from audiotools.bitstream import BitstreamWriter

        # open output file right away
        # so we can fail as soon as possible
        try:
            file = open(filename, "wb")
        except IOError as err:
            pcmreader.close()
            raise EncodingError(str(err))

        writer = BitstreamWriter(file, True)
        counter = CounterPCMReader(pcmreader)
        try:
            if (total_pcm_frames is not None):
                # write header to disk
                write_header(writer,
                             pcmreader.channels,
                             pcmreader.bits_per_sample,
                             pcmreader.sample_rate,
                             total_pcm_frames)

                block_size = (pcmreader.sample_rate * 256) // 245
                total_tta_frames = div_ceil(total_pcm_frames, block_size)

                # write temporary seektable to disk
                writer.mark()
                write_seektable(writer, [0] * total_tta_frames)
                writer.flush()

                # write frames to disk
                try:
                    frame_sizes = \
                        (encode_tta if encoding_function is None
                         else encoding_function)(file,
                                                 BufferedPCMReader(counter))
                except (IOError, ValueError) as err:
                    cls.__unlink__(filename)
                    raise EncodingError(str(err))

                # ensure written number of PCM frames
                # matches total_pcm_frames
                if (counter.frames_written != total_pcm_frames):
                    from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH
                    cls.__unlink__(filename)
                    raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH)

                assert(len(frame_sizes) == total_tta_frames)

                # go back and rewrite seektable with completed one
                writer.rewind()
                write_seektable(writer, frame_sizes)
                writer.unmark()
            else:
                import tempfile

                frames = tempfile.TemporaryFile()

                # encode TTA frames to temporary file
                try:
                    frame_sizes = \
                        (encode_tta if encoding_function is None
                         else encoding_function)(frames,
                                                 BufferedPCMReader(counter))
                except (IOError, ValueError) as err:
                    frames.close()
                    cls.__unlink__(filename)
                    raise EncodingError(str(err))

                # write header to disk
                write_header(writer,
                             pcmreader.channels,
                             pcmreader.bits_per_sample,
                             pcmreader.sample_rate,
                             counter.frames_written)

                # write seektable to disk
                write_seektable(writer, frame_sizes)

                # transfer TTA frames from temporary space to disk
                frames.seek(0, 0)
                transfer_data(frames.read, writer.write_bytes)
                frames.close()
        finally:
            counter.close()
            if (writer.has_mark()):
                writer.unmark()
            writer.close()

        return cls(filename)
Example #27
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)