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()
def play(self): """if audiofile has been opened, changes current state of player to PLAYER_PLAYING""" from audiotools import BufferedPCMReader if self.__state__ == PLAYER_PLAYING: # already playing, so nothing to do return elif self.__state__ == PLAYER_PAUSED: # go from unpaused to playing self.__audio_output__.resume() self.__state__ = PLAYER_PLAYING elif ((self.__state__ == PLAYER_STOPPED) and (self.__audiofile__ is not None)): # go from stopped to playing # if an audiofile has been opened # get PCMReader from selected audiofile pcmreader = self.__audiofile__.to_pcm() # apply ReplayGain if requested if self.__replay_gain__ in (RG_TRACK_GAIN, RG_ALBUM_GAIN): gain = self.__audiofile__.get_replay_gain() if gain is not None: from audiotools.replaygain import ReplayGainReader if self.__replay_gain__ == RG_TRACK_GAIN: pcmreader = ReplayGainReader(pcmreader, gain.track_gain, gain.track_peak) else: pcmreader = ReplayGainReader(pcmreader, gain.album_gain, gain.album_peak) # buffer PCMReader so that one can process small chunks of data self.__pcmreader__ = BufferedPCMReader(pcmreader) # calculate quarter second buffer size # (or at least 256 samples) self.__buffer_size__ = max(int(round(0.25 * pcmreader.sample_rate)), 256) # set output to be compatible with PCMReader self.__audio_output__.set_format( sample_rate=self.__pcmreader__.sample_rate, channels=self.__pcmreader__.channels, channel_mask=self.__pcmreader__.channel_mask, bits_per_sample=self.__pcmreader__.bits_per_sample) # reset progress self.__current_frames__ = 0 self.__total_frames__ = self.__audiofile__.total_frames() # update state so audio begins playing self.__state__ = PLAYER_PLAYING
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 play(self): """if audiofile has been opened, changes current state of player to PLAYER_PLAYING""" from audiotools import BufferedPCMReader if (self.__state__ == PLAYER_PLAYING): #already playing, so nothing to do return elif (self.__state__ == PLAYER_PAUSED): #go from unpaused to playing self.__audio_output__.resume() self.__state__ = PLAYER_PLAYING elif ((self.__state__ == PLAYER_STOPPED) and (self.__audiofile__ is not None)): #go from stopped to playing #if an audiofile has been opened #get PCMReader from selected audiofile pcmreader = self.__audiofile__.to_pcm() #apply ReplayGain if requested if (self.__replay_gain__ in (RG_TRACK_GAIN, RG_ALBUM_GAIN)): gain = self.__audiofile__.replay_gain() if (gain is not None): from audiotools.replaygain import ReplayGainReader if (replay_gain == RG_TRACK_GAIN): pcmreader = ReplayGainReader(pcmreader, gain.track_gain, gain.track_peak) else: pcmreader = ReplayGainReader(pcmreader, gain.album_gain, gain.album_peak) #buffer PCMReader so that one can process small chunks of data self.__pcmreader__ = BufferedPCMReader(pcmreader) #calculate quarter second buffer size #(or at least 256 samples) self.__buffer_size__ = max(int(round(0.25 * pcmreader.sample_rate)), 256) #set output to be compatible with PCMReader self.__audio_output__.set_format( sample_rate=self.__pcmreader__.sample_rate, channels=self.__pcmreader__.channels, channel_mask=self.__pcmreader__.channel_mask, bits_per_sample=self.__pcmreader__.bits_per_sample) #reset progress self.__current_frames__ = 0 self.__total_frames__ = self.__audiofile__.total_frames() #update state so audio begins playing self.__state__ = PLAYER_PLAYING
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 from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=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 MP2Audio object""" from audiotools import (PCMConverter, BufferedPCMReader, ChannelMask, __default_quality__, EncodingError) from audiotools.encoders import encode_mp2 import bisect if (((compression is None) or (compression not in cls.COMPRESSION_MODES))): compression = __default_quality__(cls.NAME) if (pcmreader.sample_rate in (32000, 48000, 44100)): sample_rate = pcmreader.sample_rate else: sample_rate = [32000, 32000, 44100, 48000][bisect.bisect([32000, 44100, 48000], pcmreader.sample_rate)] if (total_pcm_frames is not None): from audiotools import CounterPCMReader pcmreader = CounterPCMReader(pcmreader) try: encode_mp2( filename, BufferedPCMReader( PCMConverter(pcmreader, sample_rate=sample_rate, channels=min(pcmreader.channels, 2), channel_mask=ChannelMask.from_channels( min(pcmreader.channels, 2)), bits_per_sample=16)), int(compression)) if ((total_pcm_frames is not None) and (total_pcm_frames != pcmreader.frames_written)): from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH cls.__unlink__(filename) raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH) return MP2Audio(filename) except (ValueError, IOError) as err: cls.__unlink__(filename) raise EncodingError(str(err)) finally: pcmreader.close()
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 from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=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, PCMConverter, __default_quality__, EncodingError) from audiotools.encoders import encode_opus if (((compression is None) or (compression not in cls.COMPRESSION_MODES))): compression = __default_quality__(cls.NAME) if ((pcmreader.channels > 2) and (pcmreader.channels <= 8)): channel_mask = int(pcmreader.channel_mask) if ((channel_mask != 0) and (channel_mask not in ( 0x7, # FR, FC, FL 0x33, # FR, FL, BR, BL 0x37, # FR, FC, FL, BL, BR 0x3f, # FR, FC, FL, BL, BR, LFE 0x70f, # FL, FC, FR, SL, SR, BC, LFE 0x63f))): # FL, FC, FR, SL, SR, BL, BR, LFE raise UnsupportedChannelMask(filename, channel_mask) try: encode_opus(filename, BufferedPCMReader( PCMConverter(pcmreader, sample_rate=48000, channels=pcmreader.channels, channel_mask=pcmreader.channel_mask, bits_per_sample=16)), quality=int(compression), original_sample_rate=pcmreader.sample_rate) return cls(filename) except (ValueError, IOError), err: cls.__unlink__(filename) raise EncodingError(err)
def from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=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 VorbisAudio object""" from audiotools import (BufferedPCMReader, __default_quality__, EncodingError) from audiotools.encoders import encode_vorbis if (((compression is None) or (compression not in cls.COMPRESSION_MODES))): compression = __default_quality__(cls.NAME) if ((pcmreader.channels > 2) and (pcmreader.channels <= 8)): channel_mask = int(pcmreader.channel_mask) if ((channel_mask != 0) and (channel_mask not in (0x7, # FR, FC, FL 0x33, # FR, FL, BR, BL 0x37, # FR, FC, FL, BL, BR 0x3f, # FR, FC, FL, BL, BR, LFE 0x70f, # FL, FC, FR, SL, SR, BC, LFE 0x63f))): # FL, FC, FR, SL, SR, BL, BR, LFE from audiotools import UnsupportedChannelMask raise UnsupportedChannelMask(filename, channel_mask) if (total_pcm_frames is not None): from audiotools import CounterPCMReader pcmreader = CounterPCMReader(pcmreader) try: encode_vorbis(filename, BufferedPCMReader(pcmreader), float(compression) / 10) if ((total_pcm_frames is not None) and (total_pcm_frames != pcmreader.frames_written)): from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH cls.__unlink__(filename) raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH) return VorbisAudio(filename) except (ValueError, IOError) as err: cls.__unlink__(filename) raise EncodingError(str(err)) finally: pcmreader.close()
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 WavPackAudio object""" from audiotools.encoders import encode_wavpack from audiotools import BufferedPCMReader from audiotools import CounterPCMReader from audiotools import EncodingError from audiotools import __default_quality__ if (((compression is None) or (compression not in cls.COMPRESSION_MODES))): compression = __default_quality__(cls.NAME) counter = CounterPCMReader(pcmreader) try: (encode_wavpack if encoding_function is None else encoding_function)( filename, BufferedPCMReader(counter), total_pcm_frames=(total_pcm_frames if total_pcm_frames is not None else 0), **cls.__options__[compression]) counter.close() except (ValueError, IOError) as msg: counter.close() cls.__unlink__(filename) raise EncodingError(str(msg)) except Exception: counter.close() cls.__unlink__(filename) raise # ensure actual total PCM frames matches argument, if any if (((total_pcm_frames is not None) and (counter.frames_written != total_pcm_frames))): cls.__unlink__(filename) from audiotools.text import ERR_TOTAL_PCM_FRAMES_MISMATCH raise EncodingError(ERR_TOTAL_PCM_FRAMES_MISMATCH) return cls(filename)
def play(self): """if track has been selected, changes current state of player to PLAYER_PLAYING""" from audiotools import (BufferedPCMReader, ThreadedPCMReader, PCMReaderHead) if self.__state__ == PLAYER_PLAYING: # already playing, so nothing to do return elif self.__state__ == PLAYER_PAUSED: # go from unpaused to playing self.__audio_output__.resume() self.__state__ = PLAYER_PLAYING elif ((self.__state__ == PLAYER_STOPPED) and (self.__track_number__ is not None)): # go from stopped to playing # if a track number has been selected # seek to specified track number self.__cddareader__.seek(self.__offsets__[self.__track_number__]) track = PCMReaderHead(self.__cddareader__, self.__lengths__[self.__track_number__], False) # decode PCMReader in thread # and place in buffer so one can process small chunks of data self.__pcmreader__ = BufferedPCMReader(ThreadedPCMReader(track)) # calculate quarter second buffer size self.__buffer_size__ = int(round(0.25 * 44100)) # set output to be compatible with PCMReader self.__audio_output__.set_format( sample_rate=44100, channels=2, channel_mask=0x3, bits_per_sample=16) # reset progress self.__current_frames__ = 0 self.__total_frames__ = self.__lengths__[self.__track_number__] # update state so audio begins playing self.__state__ = PLAYER_PLAYING
def from_pcm(cls, filename, pcmreader, compression=None): """Encodes a new file from PCM data. Takes a filename string, PCMReader object and optional compression level string. Encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new WavPackAudio object.""" from . import encoders if ((compression is None) or (compression not in cls.COMPRESSION_MODES)): compression = __default_quality__(cls.NAME) try: encoders.encode_wavpack(filename, BufferedPCMReader(pcmreader), **cls.__options__[compression]) return cls(filename) except (ValueError, IOError), msg: cls.__unlink__(filename) raise EncodingError(str(msg))
def from_pcm(cls, filename, pcmreader, compression=None, total_pcm_frames=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 MP3Audio object""" from audiotools import (PCMConverter, BufferedPCMReader, ChannelMask, __default_quality__, EncodingError) from audiotools.encoders import encode_mp3 if (((compression is None) or (compression not in cls.COMPRESSION_MODES))): compression = __default_quality__(cls.NAME) try: encode_mp3( filename, BufferedPCMReader( PCMConverter(pcmreader, sample_rate=pcmreader.sample_rate, channels=min(pcmreader.channels, 2), channel_mask=ChannelMask.from_channels( min(pcmreader.channels, 2)), bits_per_sample=16)), compression) return MP3Audio(filename) except (ValueError, IOError), err: cls.__unlink__(filename) raise EncodingError(str(err))
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 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)
def from_wave(cls, filename, header, pcmreader, footer, compression=None, encoding_function=None): """encodes a new file from wave data takes a filename string, header string, PCMReader object, footer string and optional compression level string encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new WaveAudio object header + pcm data + footer should always result in the original wave file being restored without need for any padding bytes may raise EncodingError if some problem occurs when encoding the input file""" from audiotools.encoders import encode_wavpack from audiotools import BufferedPCMReader from audiotools import CounterPCMReader from audiotools.wav import (validate_header, validate_footer) from audiotools import EncodingError from audiotools import __default_quality__ if (((compression is None) or (compression not in cls.COMPRESSION_MODES))): compression = __default_quality__(cls.NAME) # ensure header is valid try: (total_size, data_size) = validate_header(header) except ValueError as err: raise EncodingError(str(err)) counter = CounterPCMReader(pcmreader) try: (encode_wavpack if encoding_function is None else encoding_function)(filename, BufferedPCMReader(counter), wave_header=header, wave_footer=footer, **cls.__options__[compression]) counter.close() data_bytes_written = counter.bytes_written() # ensure output data size matches the "data" chunk's size if (data_size != data_bytes_written): from audiotools.text import ERR_WAV_TRUNCATED_DATA_CHUNK raise EncodingError(ERR_WAV_TRUNCATED_DATA_CHUNK) # ensure footer validates correctly try: validate_footer(footer, data_bytes_written) except ValueError as err: raise EncodingError(str(err)) # ensure total size is correct if ((len(header) + data_size + len(footer)) != total_size): from audiotools.text import ERR_WAV_INVALID_SIZE raise EncodingError(ERR_WAV_INVALID_SIZE) return cls(filename) except (ValueError, IOError) as msg: counter.close() cls.__unlink__(filename) raise EncodingError(str(msg)) except Exception as err: counter.close() cls.__unlink__(filename) raise err
class AudioPlayer: def __init__(self, audio_output, next_track_callback=lambda: None): """audio_output is an AudioOutput object to play audio to next_track_callback is an optional function which is called with no arguments when the current track is finished""" self.__state__ = PLAYER_STOPPED self.__audio_output__ = audio_output self.__next_track_callback__ = next_track_callback self.__audiofile__ = None self.__pcmreader__ = None self.__buffer_size__ = 1 self.__replay_gain__ = RG_NO_REPLAYGAIN self.__current_frames__ = 0 self.__total_frames__ = 1 def set_audiofile(self, audiofile): """sets audiofile to play""" self.__audiofile__ = audiofile def state(self): """returns current state of player which is one of: PLAYER_STOPPED, PLAYER_PAUSED, PLAYER_PLAYING""" return self.__state__ def progress(self): """returns current progress as a (current frames, total frames) tuple""" return (self.__current_frames__, self.__total_frames__) def stop(self): """changes current state of player to PLAYER_STOPPED""" if (self.__state__ == PLAYER_STOPPED): #already stopped, so nothing to do return else: if (self.__state__ == PLAYER_PAUSED): self.__audio_output__.resume() self.__state__ = PLAYER_STOPPED self.__pcmreader__.close() self.__pcmreader__ = None self.__current_frames__ = 0 self.__total_frames__ = 1 def pause(self): """if playing, changes current state of player to PLAYER_PAUSED""" #do nothing if player is stopped or already paused if (self.__state__ == PLAYER_PLAYING): self.__audio_output__.pause() self.__state__ = PLAYER_PAUSED def play(self): """if audiofile has been opened, changes current state of player to PLAYER_PLAYING""" from audiotools import BufferedPCMReader if (self.__state__ == PLAYER_PLAYING): #already playing, so nothing to do return elif (self.__state__ == PLAYER_PAUSED): #go from unpaused to playing self.__audio_output__.resume() self.__state__ = PLAYER_PLAYING elif ((self.__state__ == PLAYER_STOPPED) and (self.__audiofile__ is not None)): #go from stopped to playing #if an audiofile has been opened #get PCMReader from selected audiofile pcmreader = self.__audiofile__.to_pcm() #apply ReplayGain if requested if (self.__replay_gain__ in (RG_TRACK_GAIN, RG_ALBUM_GAIN)): gain = self.__audiofile__.replay_gain() if (gain is not None): from audiotools.replaygain import ReplayGainReader if (replay_gain == RG_TRACK_GAIN): pcmreader = ReplayGainReader(pcmreader, gain.track_gain, gain.track_peak) else: pcmreader = ReplayGainReader(pcmreader, gain.album_gain, gain.album_peak) #buffer PCMReader so that one can process small chunks of data self.__pcmreader__ = BufferedPCMReader(pcmreader) #calculate quarter second buffer size #(or at least 256 samples) self.__buffer_size__ = max( int(round(0.25 * pcmreader.sample_rate)), 256) #set output to be compatible with PCMReader self.__audio_output__.set_format( sample_rate=self.__pcmreader__.sample_rate, channels=self.__pcmreader__.channels, channel_mask=self.__pcmreader__.channel_mask, bits_per_sample=self.__pcmreader__.bits_per_sample) #reset progress self.__current_frames__ = 0 self.__total_frames__ = self.__audiofile__.total_frames() #update state so audio begins playing self.__state__ = PLAYER_PLAYING def output_audio(self): """if player is playing, output the next chunk of audio if possible if audio is exhausted, stop playing and call the next_track callback""" if (self.__state__ == PLAYER_PLAYING): try: frame = self.__pcmreader__.read(self.__buffer_size__) except (IOError, ValueError), err: #some sort of read error occurred #so cease playing file and move on to next self.stop() if (callable(self.__next_track_callback__)): self.__next_track_callback__() return if (len(frame) > 0): self.__current_frames__ += frame.frames self.__audio_output__.play(frame) else: #audio has been exhausted self.stop() if (callable(self.__next_track_callback__)): self.__next_track_callback__()
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 from_aiff(cls, filename, header, pcmreader, footer, compression=None, block_size=256, encoding_function=None): """encodes a new file from AIFF data takes a filename string, header string, PCMReader object, footer string and optional compression level string encodes a new audio file from pcmreader's data at the given filename with the specified compression level and returns a new AiffAudio object header + pcm data + footer should always result in the original AIFF file being restored without need for any padding bytes may raise EncodingError if some problem occurs when encoding the input file""" from audiotools import (CounterPCMReader, BufferedPCMReader, UnsupportedBitsPerSample, EncodingError) from audiotools.aiff import (validate_header, validate_footer) if (encoding_function is None): from audiotools.encoders import encode_shn else: encode_shn = encoding_function if (pcmreader.bits_per_sample not in (8, 16)): raise UnsupportedBitsPerSample(filename, pcmreader.bits_per_sample) # ensure header is valid try: (total_size, ssnd_size) = validate_header(header) except ValueError as err: raise EncodingError(str(err)) counter = CounterPCMReader(pcmreader) try: if (len(footer) == 0): encode_shn(filename=filename, pcmreader=BufferedPCMReader(counter), is_big_endian=True, signed_samples=True, header_data=header, block_size=block_size) else: encode_shn(filename=filename, pcmreader=BufferedPCMReader(counter), is_big_endian=True, signed_samples=True, header_data=header, footer_data=footer, block_size=block_size) ssnd_bytes_written = counter.bytes_written() # ensure output data size matches the "SSND" chunk's size if (ssnd_size != ssnd_bytes_written): from audiotools.text import ERR_AIFF_TRUNCATED_SSND_CHUNK raise EncodingError(ERR_AIFF_TRUNCATED_SSND_CHUNK) # ensure footer validates correctly try: validate_footer(footer, ssnd_bytes_written) except ValueError as err: raise EncodingError(str(err)) # ensure total size is correct if ((len(header) + ssnd_size + len(footer)) != total_size): from audiotools.text import ERR_AIFF_INVALID_SIZE raise EncodingError(ERR_AIFF_INVALID_SIZE) return cls(filename) except IOError as err: cls.__unlink__(filename) raise EncodingError(str(err)) except Exception as err: cls.__unlink__(filename) raise err
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()
class AudioPlayer(object): def __init__(self, audio_output, next_track_callback=lambda: None, replay_gain=RG_NO_REPLAYGAIN): """audio_output is an AudioOutput object to play audio to next_track_callback is an optional function which is called with no arguments when the current track is finished""" self.__state__ = PLAYER_STOPPED self.__audio_output__ = audio_output self.__next_track_callback__ = next_track_callback self.__audiofile__ = None self.__pcmreader__ = None self.__buffer_size__ = 1 self.__replay_gain__ = replay_gain self.__current_frames__ = 0 self.__total_frames__ = 1 def set_audiofile(self, audiofile): """sets audiofile to play""" self.__audiofile__ = audiofile def state(self): """returns current state of player which is one of: PLAYER_STOPPED, PLAYER_PAUSED, PLAYER_PLAYING""" return self.__state__ def progress(self): """returns current progress as a (current frames, total frames) tuple""" return (self.__current_frames__, self.__total_frames__) def stop(self): """changes current state of player to PLAYER_STOPPED""" if self.__state__ == PLAYER_STOPPED: # already stopped, so nothing to do return else: if self.__state__ == PLAYER_PAUSED: self.__audio_output__.resume() self.__state__ = PLAYER_STOPPED self.__pcmreader__ = None self.__current_frames__ = 0 self.__total_frames__ = 1 def pause(self): """if playing, changes current state of player to PLAYER_PAUSED""" # do nothing if player is stopped or already paused if self.__state__ == PLAYER_PLAYING: self.__audio_output__.pause() self.__state__ = PLAYER_PAUSED def play(self): """if audiofile has been opened, changes current state of player to PLAYER_PLAYING""" from audiotools import BufferedPCMReader if self.__state__ == PLAYER_PLAYING: # already playing, so nothing to do return elif self.__state__ == PLAYER_PAUSED: # go from unpaused to playing self.__audio_output__.resume() self.__state__ = PLAYER_PLAYING elif (self.__state__ == PLAYER_STOPPED) and (self.__audiofile__ is not None): # go from stopped to playing # if an audiofile has been opened # get PCMReader from selected audiofile pcmreader = self.__audiofile__.to_pcm() # apply ReplayGain if requested if self.__replay_gain__ in (RG_TRACK_GAIN, RG_ALBUM_GAIN): gain = self.__audiofile__.get_replay_gain() if gain is not None: from audiotools.replaygain import ReplayGainReader if self.__replay_gain__ == RG_TRACK_GAIN: pcmreader = ReplayGainReader(pcmreader, gain.track_gain, gain.track_peak) else: pcmreader = ReplayGainReader(pcmreader, gain.album_gain, gain.album_peak) # buffer PCMReader so that one can process small chunks of data self.__pcmreader__ = BufferedPCMReader(pcmreader) # calculate quarter second buffer size # (or at least 256 samples) self.__buffer_size__ = max(int(round(0.25 * pcmreader.sample_rate)), 256) # set output to be compatible with PCMReader self.__audio_output__.set_format( sample_rate=self.__pcmreader__.sample_rate, channels=self.__pcmreader__.channels, channel_mask=self.__pcmreader__.channel_mask, bits_per_sample=self.__pcmreader__.bits_per_sample, ) # reset progress self.__current_frames__ = 0 self.__total_frames__ = self.__audiofile__.total_frames() # update state so audio begins playing self.__state__ = PLAYER_PLAYING def output_audio(self): """if player is playing, output the next chunk of audio if possible if audio is exhausted, stop playing and call the next_track callback""" if self.__state__ == PLAYER_PLAYING: try: frame = self.__pcmreader__.read(self.__buffer_size__) except (IOError, ValueError) as err: # some sort of read error occurred # so cease playing file and move on to next self.stop() if callable(self.__next_track_callback__): self.__next_track_callback__() return if len(frame) > 0: self.__current_frames__ += frame.frames self.__audio_output__.play(frame) else: # audio has been exhausted self.stop() if callable(self.__next_track_callback__): self.__next_track_callback__() def run(self, commands, responses): """runs the audio playing thread while accepting commands from the given Queue""" try: from queue import Empty except ImportError: from Queue import Empty while True: try: (command, args) = commands.get(self.__state__ != PLAYER_PLAYING) # got a command to process if command == "open": # stop whatever's playing and prepare new track for playing self.stop() self.set_audiofile(args[0]) elif command == "play": self.play() elif command == "set_replay_gain": self.__replay_gain__ = args[0] elif command == "set_output": # resume (if necessary) and close existing output if self.__state__ == PLAYER_PAUSED: self.__audio_output__.resume() self.__audio_output__.close() # set new output and set format (if necessary) self.__audio_output__ = args[0] if self.__pcmreader__ is not None: self.__audio_output__.set_format( sample_rate=self.__pcmreader__.sample_rate, channels=self.__pcmreader__.channels, channel_mask=self.__pcmreader__.channel_mask, bits_per_sample=self.__pcmreader__.bits_per_sample, ) # if paused, reset audio output to paused if self.__state__ == PLAYER_PAUSED: self.__audio_output__.pause() elif command == "pause": self.pause() elif command == "toggle_play_pause": # switch from paused to playing or playing to paused if self.__state__ == PLAYER_PAUSED: self.play() elif self.__state__ == PLAYER_PLAYING: self.pause() elif command == "stop": self.stop() self.__audio_output__.close() elif command == "close": self.stop() self.__audio_output__.close() return except Empty: # no commands to process # so output audio if playing self.output_audio()
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_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
class AudioPlayer(object): def __init__(self, audio_output, next_track_callback=lambda: None, replay_gain=RG_NO_REPLAYGAIN): """audio_output is an AudioOutput object to play audio to next_track_callback is an optional function which is called with no arguments when the current track is finished""" self.__state__ = PLAYER_STOPPED self.__audio_output__ = audio_output self.__next_track_callback__ = next_track_callback self.__audiofile__ = None self.__pcmreader__ = None self.__buffer_size__ = 1 self.__replay_gain__ = replay_gain self.__current_frames__ = 0 self.__total_frames__ = 1 def set_audiofile(self, audiofile): """sets audiofile to play""" self.__audiofile__ = audiofile def state(self): """returns current state of player which is one of: PLAYER_STOPPED, PLAYER_PAUSED, PLAYER_PLAYING""" return self.__state__ def progress(self): """returns current progress as a (current frames, total frames) tuple""" return (self.__current_frames__, self.__total_frames__) def stop(self): """changes current state of player to PLAYER_STOPPED""" if (self.__state__ == PLAYER_STOPPED): # already stopped, so nothing to do return else: if (self.__state__ == PLAYER_PAUSED): self.__audio_output__.resume() self.__state__ = PLAYER_STOPPED self.__pcmreader__ = None self.__current_frames__ = 0 self.__total_frames__ = 1 def pause(self): """if playing, changes current state of player to PLAYER_PAUSED""" # do nothing if player is stopped or already paused if (self.__state__ == PLAYER_PLAYING): self.__audio_output__.pause() self.__state__ = PLAYER_PAUSED def play(self): """if audiofile has been opened, changes current state of player to PLAYER_PLAYING""" from audiotools import BufferedPCMReader if (self.__state__ == PLAYER_PLAYING): # already playing, so nothing to do return elif (self.__state__ == PLAYER_PAUSED): # go from unpaused to playing self.__audio_output__.resume() self.__state__ = PLAYER_PLAYING elif ((self.__state__ == PLAYER_STOPPED) and (self.__audiofile__ is not None)): # go from stopped to playing # if an audiofile has been opened # get PCMReader from selected audiofile pcmreader = self.__audiofile__.to_pcm() # apply ReplayGain if requested if (self.__replay_gain__ in (RG_TRACK_GAIN, RG_ALBUM_GAIN)): gain = self.__audiofile__.get_replay_gain() if (gain is not None): from audiotools.replaygain import ReplayGainReader if (self.__replay_gain__ == RG_TRACK_GAIN): pcmreader = ReplayGainReader(pcmreader, gain.track_gain, gain.track_peak) else: pcmreader = ReplayGainReader(pcmreader, gain.album_gain, gain.album_peak) # buffer PCMReader so that one can process small chunks of data self.__pcmreader__ = BufferedPCMReader(pcmreader) # calculate quarter second buffer size # (or at least 256 samples) self.__buffer_size__ = max( int(round(0.25 * pcmreader.sample_rate)), 256) # set output to be compatible with PCMReader self.__audio_output__.set_format( sample_rate=self.__pcmreader__.sample_rate, channels=self.__pcmreader__.channels, channel_mask=self.__pcmreader__.channel_mask, bits_per_sample=self.__pcmreader__.bits_per_sample) # reset progress self.__current_frames__ = 0 self.__total_frames__ = self.__audiofile__.total_frames() # update state so audio begins playing self.__state__ = PLAYER_PLAYING def output_audio(self): """if player is playing, output the next chunk of audio if possible if audio is exhausted, stop playing and call the next_track callback""" if (self.__state__ == PLAYER_PLAYING): try: frame = self.__pcmreader__.read(self.__buffer_size__) except (IOError, ValueError) as err: # some sort of read error occurred # so cease playing file and move on to next self.stop() if (callable(self.__next_track_callback__)): self.__next_track_callback__() return if (len(frame) > 0): self.__current_frames__ += frame.frames self.__audio_output__.play(frame) else: # audio has been exhausted self.stop() if (callable(self.__next_track_callback__)): self.__next_track_callback__() def run(self, commands, responses): """runs the audio playing thread while accepting commands from the given Queue""" try: from queue import Empty except ImportError: from Queue import Empty while (True): try: (command, args) = commands.get(self.__state__ != PLAYER_PLAYING) # got a command to process if (command == "open"): # stop whatever's playing and prepare new track for playing self.stop() self.set_audiofile(args[0]) elif (command == "play"): self.play() elif (command == "set_replay_gain"): self.__replay_gain__ = args[0] elif (command == "set_output"): # resume (if necessary) and close existing output if (self.__state__ == PLAYER_PAUSED): self.__audio_output__.resume() self.__audio_output__.close() # set new output and set format (if necessary) self.__audio_output__ = args[0] if (self.__pcmreader__ is not None): self.__audio_output__.set_format( sample_rate=self.__pcmreader__.sample_rate, channels=self.__pcmreader__.channels, channel_mask=self.__pcmreader__.channel_mask, bits_per_sample=self.__pcmreader__.bits_per_sample) # if paused, reset audio output to paused if (self.__state__ == PLAYER_PAUSED): self.__audio_output__.pause() elif (command == "pause"): self.pause() elif (command == "toggle_play_pause"): # switch from paused to playing or playing to paused if (self.__state__ == PLAYER_PAUSED): self.play() elif (self.__state__ == PLAYER_PLAYING): self.pause() elif (command == "stop"): self.stop() self.__audio_output__.close() elif (command == "close"): self.stop() self.__audio_output__.close() return except Empty: # no commands to process # so output audio if playing self.output_audio()
class AudioPlayer: def __init__(self, audio_output, next_track_callback=lambda: None): """audio_output is an AudioOutput object to play audio to next_track_callback is an optional function which is called with no arguments when the current track is finished""" self.__state__ = PLAYER_STOPPED self.__audio_output__ = audio_output self.__next_track_callback__ = next_track_callback self.__audiofile__ = None self.__pcmreader__ = None self.__buffer_size__ = 1 self.__replay_gain__ = RG_NO_REPLAYGAIN self.__current_frames__ = 0 self.__total_frames__ = 1 def set_audiofile(self, audiofile): """sets audiofile to play""" self.__audiofile__ = audiofile def state(self): """returns current state of player which is one of: PLAYER_STOPPED, PLAYER_PAUSED, PLAYER_PLAYING""" return self.__state__ def progress(self): """returns current progress as a (current frames, total frames) tuple""" return (self.__current_frames__, self.__total_frames__) def stop(self): """changes current state of player to PLAYER_STOPPED""" if (self.__state__ == PLAYER_STOPPED): #already stopped, so nothing to do return else: if (self.__state__ == PLAYER_PAUSED): self.__audio_output__.resume() self.__state__ = PLAYER_STOPPED self.__pcmreader__.close() self.__pcmreader__ = None self.__current_frames__ = 0 self.__total_frames__ = 1 def pause(self): """if playing, changes current state of player to PLAYER_PAUSED""" #do nothing if player is stopped or already paused if (self.__state__ == PLAYER_PLAYING): self.__audio_output__.pause() self.__state__ = PLAYER_PAUSED def play(self): """if audiofile has been opened, changes current state of player to PLAYER_PLAYING""" from audiotools import BufferedPCMReader if (self.__state__ == PLAYER_PLAYING): #already playing, so nothing to do return elif (self.__state__ == PLAYER_PAUSED): #go from unpaused to playing self.__audio_output__.resume() self.__state__ = PLAYER_PLAYING elif ((self.__state__ == PLAYER_STOPPED) and (self.__audiofile__ is not None)): #go from stopped to playing #if an audiofile has been opened #get PCMReader from selected audiofile pcmreader = self.__audiofile__.to_pcm() #apply ReplayGain if requested if (self.__replay_gain__ in (RG_TRACK_GAIN, RG_ALBUM_GAIN)): gain = self.__audiofile__.replay_gain() if (gain is not None): from audiotools.replaygain import ReplayGainReader if (replay_gain == RG_TRACK_GAIN): pcmreader = ReplayGainReader(pcmreader, gain.track_gain, gain.track_peak) else: pcmreader = ReplayGainReader(pcmreader, gain.album_gain, gain.album_peak) #buffer PCMReader so that one can process small chunks of data self.__pcmreader__ = BufferedPCMReader(pcmreader) #calculate quarter second buffer size #(or at least 256 samples) self.__buffer_size__ = max(int(round(0.25 * pcmreader.sample_rate)), 256) #set output to be compatible with PCMReader self.__audio_output__.set_format( sample_rate=self.__pcmreader__.sample_rate, channels=self.__pcmreader__.channels, channel_mask=self.__pcmreader__.channel_mask, bits_per_sample=self.__pcmreader__.bits_per_sample) #reset progress self.__current_frames__ = 0 self.__total_frames__ = self.__audiofile__.total_frames() #update state so audio begins playing self.__state__ = PLAYER_PLAYING def output_audio(self): """if player is playing, output the next chunk of audio if possible if audio is exhausted, stop playing and call the next_track callback""" if (self.__state__ == PLAYER_PLAYING): try: frame = self.__pcmreader__.read(self.__buffer_size__) except (IOError, ValueError), err: #some sort of read error occurred #so cease playing file and move on to next self.stop() if (callable(self.__next_track_callback__)): self.__next_track_callback__() return if (len(frame) > 0): self.__current_frames__ += frame.frames self.__audio_output__.play(frame) else: #audio has been exhausted self.stop() if (callable(self.__next_track_callback__)): self.__next_track_callback__()