def build(self, writer): """outputs an APEv2 tag to writer""" from audiotools.bitstream import BitstreamRecorder tags = BitstreamRecorder(1) for tag in self.tags: tag.build(tags) if (self.contains_header): writer.build(ApeTag.HEADER_FORMAT, ("APETAGEX", # preamble 2000, # version tags.bytes() + 32, # tag size len(self.tags), # item count 0, # read only 0, # encoding 1, # is header not self.contains_footer, # no footer self.contains_header)) # has header tags.copy(writer) if (self.contains_footer): writer.build(ApeTag.HEADER_FORMAT, ("APETAGEX", # preamble 2000, # version tags.bytes() + 32, # tag size len(self.tags), # item count 0, # read only 0, # encoding 0, # is header not self.contains_footer, # no footer self.contains_header)) # has header
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()
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()
def build(self, writer): """outputs an APEv2 tag to writer""" from audiotools.bitstream import BitstreamRecorder tags = BitstreamRecorder(1) for tag in self.tags: tag.build(tags) if (self.contains_header): writer.build( ApeTag.HEADER_FORMAT, ( "APETAGEX", # preamble 2000, # version tags.bytes() + 32, # tag size len(self.tags), # item count 0, # read only 0, # encoding 1, # is header not self.contains_footer, # no footer self.contains_header)) # has header tags.copy(writer) if (self.contains_footer): writer.build( ApeTag.HEADER_FORMAT, ( "APETAGEX", # preamble 2000, # version tags.bytes() + 32, # tag size len(self.tags), # item count 0, # read only 0, # encoding 0, # is header not self.contains_footer, # no footer self.contains_header)) # has header
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()
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 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 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 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 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): current_offset = 0 frame_offsets = [] 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) flac_frame = BitstreamRecorder(0) while (len(frame) > 0): frame_offsets.append((current_offset, frame.frames)) streaminfo.input_update(frame) flac_frame.reset() encode_flac_frame(flac_frame, pcmreader, options, frame_number, frame) current_offset += flac_frame.bytes() 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 output_file.seek(8, 0) streaminfo.write(writer) writer.close() return frame_offsets