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))
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)
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)
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_start = mdat.getpos() 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.setpos(mdat_start) mdat.write(32, sum(frame_byte_sizes) + 8) return (frame_byte_sizes, total_pcm_frames)
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()
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()
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)
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_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)