code: def add_word_timestamps(
      *,
      segments: List[dict],
      model: "Whisper",
      tokenizer: Tokenizer,
      mel: torch.Tensor,
      num_frames: int,
      prepend_punctuations: str = "\"'“¿([{-",
      append_punctuations: str = "\"'.。,，!！?？:：”)]}、",
      last_speech_timestamp: float,
      **kwargs,
  ):
      if len(segments) == 0:
          return
  
      text_tokens_per_segment = [
          [
              token
              for token in segment["tokens"]
              if token < tokenizer.eot
          ]
          for segment in segments
      ]
  
      text_tokens = list(
          itertools.chain.from_iterable(text_tokens_per_segment)
      )
      alignment = find_alignment(
          model, tokenizer, text_tokens, mel, num_frames, **kwargs
      )
      word_durations = np.array([t.end - t.start for t in alignment])
      word_durations = word_durations[word_durations.nonzero()]
      median_duration = (
          np.median(word_durations) if len(word_durations) > 0 else 0.0
      )
      median_duration = min(0.7, float(median_duration))
      max_duration = median_duration * 2
  
      # hack: truncate long words at sentence boundaries.
      # a better segmentation algorithm based on VAD should be able to replace this.
      if len(word_durations) > 0:
          sentence_end_marks = ".。!！?？"
          # ensure words at sentence boundaries are not longer than twice the median word duration.
          for i in range(1, len(alignment)):
              if alignment[i].end - alignment[i].start > max_duration:
                  if alignment[i].word in sentence_end_marks:
                      alignment[i].end = (
                          alignment[i].start + max_duration
                      )
                  elif alignment[i - 1].word in sentence_end_marks:
                      alignment[i].start = (
                          alignment[i].end - max_duration
                      )
  
      merge_punctuations(
          alignment, prepend_punctuations, append_punctuations
      )
  
      time_offset = segments[0]["seek"] * HOP_LENGTH / SAMPLE_RATE
      word_index = 0
  
      for segment, text_tokens in zip(
          segments, text_tokens_per_segment
      ):
          saved_tokens = 0
          words = []
  
          while word_index < len(alignment) and saved_tokens < len(
              text_tokens
          ):
              timing = alignment[word_index]
  
              if timing.word:
                  words.append(
                      dict(
                          word=timing.word,
                          start=round(time_offset + timing.start, 2),
                          end=round(time_offset + timing.end, 2),
                          probability=timing.probability,
                      )
                  )
  
              saved_tokens += len(timing.tokens)
              word_index += 1
  
          # hack: truncate long words at segment boundaries.
          # a better segmentation algorithm based on VAD should be able to replace this.
          if len(words) > 0:
              # ensure the first and second word after a pause is not longer than
              # twice the median word duration.
              if words[0][
                  "end"
              ] - last_speech_timestamp > median_duration * 4 and (
                  words[0]["end"] - words[0]["start"] > max_duration
                  or (
                      len(words) > 1
                      and words[1]["end"] - words[0]["start"]
                      > max_duration * 2
                  )
              ):
                  if (
                      len(words) > 1
                      and words[1]["end"] - words[1]["start"]
                      > max_duration
                  ):
                      boundary = max(
                          words[1]["end"] / 2,
                          words[1]["end"] - max_duration,
                      )
                      words[0]["end"] = words[1]["start"] = boundary
                  words[0]["start"] = max(
                      0, words[0]["end"] - max_duration
                  )
  
              # prefer the segment-level start timestamp if the first word is too long.
              if (
                  segment["start"] < words[0]["end"]
                  and segment["start"] - 0.5 > words[0]["start"]
              ):
                  words[0]["start"] = max(
                      0,
                      min(
                          words[0]["end"] - median_duration,
                          segment["start"],
                      ),
                  )
              else:
                  segment["start"] = words[0]["start"]
  
              # prefer the segment-level end timestamp if the last word is too long.
              if (
                  segment["end"] > words[-1]["start"]
                  and segment["end"] + 0.5 < words[-1]["end"]
              ):
                  words[-1]["end"] = max(
                      words[-1]["start"] + median_duration,
                      segment["end"],
                  )
              else:
                  segment["end"] = words[-1]["end"]
  
              last_speech_timestamp = segment["end"]
  
          segment["words"] = words
  
file_path: whisper/whisper/timing.py
imports: import itertools
  import subprocess
  import warnings
  from dataclasses import dataclass
  from typing import TYPE_CHECKING, List
  import numba
  import numpy
  import torch
  import torch.nn.functional
  from .audio import HOP_LENGTH, SAMPLE_RATE, TOKENS_PER_SECOND
  from .tokenizer import Tokenizer
label_desc: Use image node_id 8 for calling a global function with example usage: add_word_timestamps() without return types