text: import itertools
  
  from .utils import db_to_float
  
  
  def detect_silence(audio_segment, min_silence_len=1000, silence_thresh=-16, seek_step=1):
      seg_len = len(audio_segment)
  
      # you can't have a silent portion of a sound that is longer than the sound
      if seg_len < min_silence_len:
          return []
  
      # convert silence threshold to a float value (so we can compare it to rms)
      silence_thresh = db_to_float(silence_thresh) * audio_segment.max_possible_amplitude
  
      # find silence and add start and end indicies to the to_cut list
      silence_starts = []
  
      # check successive (1 sec by default) chunk of sound for silence
      # try a chunk at every "seek step" (or every chunk for a seek step == 1)
      last_slice_start = seg_len - min_silence_len
      slice_starts = range(0, last_slice_start + 1, seek_step)
  
      # guarantee last_slice_start is included in the range
      # to make sure the last portion of the audio is seached
      if last_slice_start % seek_step:
          slice_starts = itertools.chain(slice_starts, [last_slice_start])
  
      for i in slice_starts:
          audio_slice = audio_segment[i:i + min_silence_len]
          if audio_slice.rms <= silence_thresh:
              silence_starts.append(i)
  
      # short circuit when there is no silence
      if not silence_starts:
          return []
  
      # combine the silence we detected into ranges (start ms - end ms)
      silent_ranges = []
  
      prev_i = silence_starts.pop(0)
      current_range_start = prev_i
  
      for silence_start_i in silence_starts:
          continuous = (silence_start_i == prev_i + seek_step)
  
          # sometimes two small blips are enough for one particular slice to be
          # non-silent, despite the silence all running together. Just combine
          # the two overlapping silent ranges.
          silence_has_gap = silence_start_i > (prev_i + min_silence_len)
  
          if not continuous and silence_has_gap:
              silent_ranges.append([current_range_start,
                                    prev_i + min_silence_len])
              current_range_start = silence_start_i
          prev_i = silence_start_i
  
      silent_ranges.append([current_range_start,
                            prev_i + min_silence_len])
  
      return silent_ranges
  
  
  def detect_nonsilent(audio_segment, min_silence_len=1000, silence_thresh=-16, seek_step=1):
      silent_ranges = detect_silence(audio_segment, min_silence_len, silence_thresh, seek_step)
      len_seg = len(audio_segment)
  
      # if there is no silence, the whole thing is nonsilent
      if not silent_ranges:
          return [[0, len_seg]]
  
      # short circuit when the whole audio segment is silent
      if silent_ranges[0][0] == 0 and silent_ranges[0][1] == len_seg:
          return []
  
      prev_end_i = 0
      nonsilent_ranges = []
      for start_i, end_i in silent_ranges:
          nonsilent_ranges.append([prev_end_i, start_i])
          prev_end_i = end_i
  
      if end_i != len_seg:
          nonsilent_ranges.append([prev_end_i, len_seg])
  
      if nonsilent_ranges[0] == [0, 0]:
          nonsilent_ranges.pop(0)
  
      return nonsilent_ranges
  
  
  def split_on_silence(audio_segment, min_silence_len=1000, silence_thresh=-16, keep_silence=100,
                       seek_step=1):
      """
      audio_segment - original pydub.AudioSegment() object
  
      min_silence_len - (in ms) minimum length of a silence to be used for
          a split. default: 1000ms
  
      silence_thresh - (in dBFS) anything quieter than this will be
          considered silence. default: -16dBFS
  
      keep_silence - (in ms or True/False) leave some silence at the beginning
          and end of the chunks. Keeps the sound from sounding like it
          is abruptly cut off.
          When the length of the silence is less than the keep_silence duration
          it is split evenly between the preceding and following non-silent
          segments.
          If True is specified, all the silence is kept, if False none is kept.
          default: 100ms
      """
  
      if isinstance(keep_silence, bool):
          keep_silence = len(audio_segment) if keep_silence else 0
  
      not_silence_ranges = detect_nonsilent(audio_segment, min_silence_len, silence_thresh, seek_step)
  
      # from the itertools documentation
      def pairwise(iterable):
          "s -> (s0,s1), (s1,s2), (s2, s3), ..."
          a, b = itertools.tee(iterable)
          next(b, None)
          return zip(a, b)
  
      start_min = 0
      chunks = []
      for (start_i, end_i), (start_ii, end_ii) in pairwise(not_silence_ranges):
          end_max = end_i + (start_ii - end_i + 1)//2  # +1 for rounding with integer division
          start_i = max(start_min, start_i - keep_silence)
          end_i = min(end_max, end_i + keep_silence)
  
          chunks.append(audio_segment[start_i:end_i])
          start_min = end_max
  
      chunks.append(audio_segment[max(start_min, start_ii - keep_silence):
                                  min(len(audio_segment), end_ii + keep_silence)])
  
  
      return chunks
  
  
  def detect_leading_silence(sound, silence_threshold=-50.0, chunk_size=10):
      '''
      sound is a pydub.AudioSegment
      silence_threshold in dB
      chunk_size in ms
      iterate over chunks until you find the first one with sound
      '''
      trim_ms = 0 # ms
      assert chunk_size > 0 # to avoid infinite loop
      while sound[trim_ms:trim_ms+chunk_size].dBFS < silence_threshold and trim_ms < len(sound):
          trim_ms += chunk_size
  
      return trim_ms
  
  
  