def encode_residual_partition(rice_parameter, residuals): partition = BitstreamRecorder(0) for residual in residuals: if residual >= 0: unsigned = residual << 1 else: unsigned = ((-residual - 1) << 1) | 1 MSB = unsigned >> rice_parameter LSB = unsigned - (MSB << rice_parameter) partition.unary(1, MSB) partition.write(rice_parameter, LSB) return partition
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()
def write_block(writer, context, channels, block_index, first_block, last_block, parameters): """writer is a BitstreamWriter-compatible object context is an EncoderContext object channels[c][s] is sample "s" in channel "c" block_index is an integer of the block's offset in PCM frames first_block and last_block are flags indicating the block's sequence parameters is an EncodingParameters object """ assert (len(channels) == 1) or (len(channels) == 2) if (len(channels) == 1) or (channels[0] == channels[1]): # 1 channel block or equivalent if len(channels) == 1: false_stereo = 0 else: false_stereo = 1 # calculate maximum magnitude of channel_0 magnitude = max(map(bits, channels[0])) # determine wasted bits wasted = min(map(wasted_bps, channels[0])) if wasted == INFINITY: # all samples are 0 wasted = 0 # if wasted bits, remove them from channel_0 if (wasted > 0) and (wasted != INFINITY): shifted = [[s >> wasted for s in channels[0]]] else: shifted = [channels[0]] # calculate CRC of shifted_0 crc = calculate_crc(shifted) else: # 2 channel block false_stereo = 0 # calculate maximum magnitude of channel_0/channel_1 magnitude = max(max(map(bits, channels[0])), max(map(bits, channels[1]))) # determine wasted bits wasted = min(min(map(wasted_bps, channels[0])), min(map(wasted_bps, channels[1]))) if wasted == INFINITY: # all samples are 0 wasted = 0 # if wasted bits, remove them from channel_0/channel_1 if wasted > 0: shifted = [[s >> wasted for s in channels[0]], [s >> wasted for s in channels[1]]] else: shifted = channels # calculate CRC of shifted_0/shifted_1 crc = calculate_crc(shifted) # joint stereo conversion of shifted_0/shifted_1 to mid/side channels mid_side = joint_stereo(shifted[0], shifted[1]) sub_blocks = BitstreamRecorder(1) sub_block = BitstreamRecorder(1) # if first block in file, write Wave header if not context.first_block_written: sub_block.reset() if context.wave_header is None: if context.wave_footer is None: write_wave_header(sub_block, context.pcmreader, 0, 0) else: write_wave_header(sub_block, context.pcmreader, 0, len(context.wave_footer)) else: sub_block.write_bytes(context.wave_header) write_sub_block(sub_blocks, WV_WAVE_HEADER, 1, sub_block) context.first_block_written = True # if correlation passes, write three sub blocks of pass data if parameters.correlation_passes > 0: sub_block.reset() write_correlation_terms( sub_block, [p.term for p in parameters.correlation_parameters(false_stereo)], [p.delta for p in parameters.correlation_parameters(false_stereo)], ) write_sub_block(sub_blocks, WV_TERMS, 0, sub_block) sub_block.reset() write_correlation_weights(sub_block, [p.weights for p in parameters.correlation_parameters(false_stereo)]) write_sub_block(sub_blocks, WV_WEIGHTS, 0, sub_block) sub_block.reset() write_correlation_samples( sub_block, [p.term for p in parameters.correlation_parameters(false_stereo)], [p.samples for p in parameters.correlation_parameters(false_stereo)], 2 if ((len(channels) == 2) and (not false_stereo)) else 1, ) write_sub_block(sub_blocks, WV_SAMPLES, 0, sub_block) # if wasted bits, write extended integers sub block if wasted > 0: sub_block.reset() write_extended_integers(sub_block, 0, wasted, 0, 0) write_sub_block(sub_blocks, WV_INT32_INFO, 0, sub_block) # if channel count > 2, write channel info sub block if context.pcmreader.channels > 2: sub_block.reset() sub_block.write(8, context.pcmreader.channels) sub_block.write(32, context.pcmreader.channel_mask) write_sub_block(sub_blocks, WV_CHANNEL_INFO, 0, sub_block) # if nonstandard sample rate, write sample rate sub block if context.pcmreader.sample_rate not in ( 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000, ): sub_block.reset() sub_block.write(32, context.pcmreader.sample_rate) write_sub_block(sub_blocks, WV_SAMPLE_RATE, 1, sub_block) if (len(channels) == 1) or (false_stereo): # 1 channel block # correlate shifted_0 with terms/deltas/weights/samples if parameters.correlation_passes > 0: assert len(shifted) == 1 correlated = correlate_channels(shifted, parameters.correlation_parameters(false_stereo), 1) else: correlated = shifted else: # 2 channel block # correlate shifted_0/shifted_1 with terms/deltas/weights/samples if parameters.correlation_passes > 0: assert len(mid_side) == 2 correlated = correlate_channels(mid_side, parameters.correlation_parameters(false_stereo), 2) else: correlated = mid_side # write entropy variables sub block sub_block.reset() write_entropy_variables(sub_block, correlated, parameters.entropy_variables) write_sub_block(sub_blocks, WV_ENTROPY, 0, sub_block) # write bitstream sub block sub_block.reset() write_bitstream(sub_block, correlated, parameters.entropy_variables) write_sub_block(sub_blocks, WV_BITSTREAM, 0, sub_block) # write block header with size of all sub blocks write_block_header( writer, sub_blocks.bytes(), block_index, len(channels[0]), context.pcmreader.bits_per_sample, len(channels), (len(channels) == 2) and (false_stereo == 0), len(set([-1, -2, -3]) & set([p.term for p in parameters.correlation_parameters(false_stereo)])) > 0, wasted, first_block, last_block, magnitude, context.pcmreader.sample_rate, false_stereo, crc, ) # write sub block data to stream sub_blocks.copy(writer) # round-trip entropy variables parameters.entropy_variables = [ [wv_exp2(wv_log2(p)) for p in parameters.entropy_variables[0]], [wv_exp2(wv_log2(p)) for p in parameters.entropy_variables[1]], ]
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()