Mercurial > hg > auditok
changeset 202:c92ed44b7737
Add tqdm progress bar to PyAudioPlayer
author | Amine Sehili <amine.sehili@gmail.com> |
---|---|
date | Sun, 19 May 2019 15:30:46 +0100 |
parents | 5b4b9d58e897 |
children | f482cc79c10e |
files | auditok/io.py |
diffstat | 1 files changed, 112 insertions(+), 31 deletions(-) [+] |
line wrap: on
line diff
--- a/auditok/io.py Tue May 14 21:11:50 2019 +0100 +++ b/auditok/io.py Sun May 19 15:30:46 2019 +0100 @@ -43,6 +43,17 @@ except ImportError: _WITH_PYDUB = False +try: + from tqdm import tqdm as _tqdm + + DEFAULT_BAR_FORMAT_TQDM = "|" + "{bar}" + "|" + "[{elapsed}/{duration}]" + DEFAULT_NCOLS_TQDM = 30 + DEFAULT_MIN_INTERVAL_TQDM = 0.05 + _WITH_TQDM = True +except ImportError: + _WITH_TQDM = False + + __all__ = [ "AudioIOError", "AudioParameterError", @@ -140,7 +151,9 @@ channels, use_channel) """ - err_message = "'{ln}' (or '{sn}') must be a positive integer, found: '{val}'" + err_message = ( + "'{ln}' (or '{sn}') must be a positive integer, found: '{val}'" + ) parameters = [] for (long_name, short_name) in ( ("sampling_rate", "sr"), @@ -176,8 +189,12 @@ return audioop.tomono(data, sample_width, 0.5, 0.5) fmt = DATA_FORMAT[sample_width] buffer = array(fmt, data) - mono_channels = [array(fmt, buffer[ch::channels]) for ch in range(channels)] - avg_arr = array(fmt, (sum(samples) // channels for samples in zip(*mono_channels))) + mono_channels = [ + array(fmt, buffer[ch::channels]) for ch in range(channels) + ] + avg_arr = array( + fmt, (sum(samples) // channels for samples in zip(*mono_channels)) + ) return _array_to_bytes(avg_arr) @@ -227,7 +244,9 @@ ): if not sample_width in (1, 2, 4): - raise AudioParameterError("Sample width must be one of: 1, 2 or 4 (bytes)") + raise AudioParameterError( + "Sample width must be one of: 1, 2 or 4 (bytes)" + ) self._sampling_rate = sampling_rate self._sample_width = sample_width @@ -433,7 +452,8 @@ raise AudioIOError("Stream is not open") bytes_to_read = self._sample_size_all_channels * size data = self._buffer[ - self._current_position_bytes : self._current_position_bytes + bytes_to_read + self._current_position_bytes : self._current_position_bytes + + bytes_to_read ] if data: self._current_position_bytes += len(data) @@ -546,7 +566,9 @@ class RawAudioSource(_FileAudioSource, Rewindable): - def __init__(self, file, sampling_rate, sample_width, channels, use_channel=0): + def __init__( + self, file, sampling_rate, sample_width, channels, use_channel=0 + ): _FileAudioSource.__init__( self, sampling_rate, sample_width, channels, use_channel ) @@ -696,6 +718,20 @@ return None +def make_tqdm_progress_bar(iterable, total, duration, **tqdm_kwargs): + + tqdm_kwargs = tqdm_kwargs.copy() + fmt = tqdm_kwargs.get("bar_format", DEFAULT_BAR_FORMAT_TQDM) + fmt = fmt.replace("{duration}", "{:.3f}".format(duration)) + tqdm_kwargs["bar_format"] = fmt + + tqdm_kwargs["ncols"] = tqdm_kwargs.get("ncols", DEFAULT_NCOLS_TQDM) + tqdm_kwargs["mininterval"] = tqdm_kwargs.get( + "mininterval", DEFAULT_MIN_INTERVAL_TQDM + ) + return _tqdm(iterable, total=total, **tqdm_kwargs) + + class PyAudioPlayer: """ A class for audio playback using Pyaudio @@ -725,13 +761,25 @@ output=True, ) - def play(self, data): + def play(self, data, progress_bar=False, **progress_bar_kwargs): + chunk_gen, nb_chunks = self._chunk_data(data) + if progress_bar and _WITH_TQDM: + duration = len(data) / ( + self.sampling_rate * self.sample_width * self.channels + ) + chunk_gen = make_tqdm_progress_bar( + chunk_gen, + total=nb_chunks, + duration=duration, + **progress_bar_kwargs + ) if self.stream.is_stopped(): self.stream.start_stream() - - for chunk in self._chunk_data(data): - self.stream.write(chunk) - + try: + for chunk in chunk_gen: + self.stream.write(chunk) + except KeyboardInterrupt: + pass self.stream.stop_stream() def stop(self): @@ -742,11 +790,17 @@ def _chunk_data(self, data): # make audio chunks of 100 ms to allow interruption (like ctrl+c) - chunk_size = int((self.sampling_rate * self.sample_width * self.channels) / 10) - start = 0 - while start < len(data): - yield data[start : start + chunk_size] - start += chunk_size + bytes_1_sec = self.sampling_rate * self.sample_width * self.channels + chunk_size = bytes_1_sec // 10 + # make sure chunk_size is a multiple of sample_width * channels + chunk_size -= chunk_size % (self.sample_width * self.channels) + nb_chunks, rest = divmod(len(data), chunk_size) + if rest > 0: + nb_chunks += 1 + chunk_gen = ( + data[i : i + chunk_size] for i in range(0, len(data), chunk_size) + ) + return chunk_gen, nb_chunks def player_for(source): @@ -765,9 +819,7 @@ and number of channels as `source`. """ return PyAudioPlayer( - source.sampling_rate, - source.sample_width, - source.channels, + source.sampling_rate, source.sample_width, source.channels ) @@ -784,12 +836,20 @@ microphone using PyAudio. """ - sampling_rate = kwargs.get("sampling_rate", kwargs.get("sr", DEFAULT_SAMPLING_RATE)) - sample_width = kwargs.get("sample_rate", kwargs.get("sw", DEFAULT_SAMPLE_WIDTH)) + sampling_rate = kwargs.get( + "sampling_rate", kwargs.get("sr", DEFAULT_SAMPLING_RATE) + ) + sample_width = kwargs.get( + "sample_rate", kwargs.get("sw", DEFAULT_SAMPLE_WIDTH) + ) channels = kwargs.get("channels", kwargs.get("ch", DEFAULT_NB_CHANNELS)) - use_channel = kwargs.get("use_channel", kwargs.get("uc", DEFAULT_USE_CHANNEL)) + use_channel = kwargs.get( + "use_channel", kwargs.get("uc", DEFAULT_USE_CHANNEL) + ) if input == "-": - return StdinAudioSource(sampling_rate, sample_width, channels, use_channel) + return StdinAudioSource( + sampling_rate, sample_width, channels, use_channel + ) if isinstance(input, bytes): return BufferAudioSource(input, sampling_rate, sample_width, channels) @@ -812,7 +872,12 @@ def _load_raw( - file, sampling_rate, sample_width, channels, use_channel=0, large_file=False + file, + sampling_rate, + sample_width, + channels, + use_channel=0, + large_file=False, ): """ Load a raw audio file with standard Python. @@ -858,9 +923,14 @@ data = fp.read() if channels != 1: # TODO check if striding with mmap doesn't load all data to memory - data = _extract_selected_channel(data, channels, sample_width, use_channel) + data = _extract_selected_channel( + data, channels, sample_width, use_channel + ) return BufferAudioSource( - data, sampling_rate=sampling_rate, sample_width=sample_width, channels=1 + data, + sampling_rate=sampling_rate, + sample_width=sample_width, + channels=1, ) @@ -881,7 +951,9 @@ data = fp.readframes(-1) if channels > 1: data = _extract_selected_channel(data, channels, swidth, use_channel) - return BufferAudioSource(data, sampling_rate=srate, sample_width=swidth, channels=1) + return BufferAudioSource( + data, sampling_rate=srate, sample_width=swidth, channels=1 + ) def _load_with_pydub(filename, audio_format, use_channel=0): @@ -975,7 +1047,9 @@ if audio_format == "raw": srate, swidth, channels, use_channel = _get_audio_parameters(kwargs) - return _load_raw(filename, srate, swidth, channels, use_channel, large_file) + return _load_raw( + filename, srate, swidth, channels, use_channel, large_file + ) use_channel = _normalize_use_channel(kwargs.get("use_channel")) if audio_format in ["wav", "wave"]: @@ -987,7 +1061,9 @@ filename, audio_format=audio_format, use_channel=use_channel ) else: - raise AudioIOError("pydub is required for audio formats other than raw or wav") + raise AudioIOError( + "pydub is required for audio formats other than raw or wav" + ) def _save_raw(data, file): @@ -1015,13 +1091,18 @@ fp.writeframes(data) -def _save_with_pydub(data, file, audio_format, sampling_rate, sample_width, channels): +def _save_with_pydub( + data, file, audio_format, sampling_rate, sample_width, channels +): """ Saves audio data with pydub (https://github.com/jiaaro/pydub). See also :func:`to_file`. """ segment = AudioSegment( - data, frame_rate=sampling_rate, sample_width=sample_width, channels=channels + data, + frame_rate=sampling_rate, + sample_width=sample_width, + channels=channels, ) with open(file, "wb") as fp: segment.export(fp, format=audio_format)