AliJabbarpour commited on
Commit
9fcb995
·
verified ·
1 Parent(s): 0295fc8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +56 -19
app.py CHANGED
@@ -42,16 +42,60 @@ diarize_pipeline = DiarizationPipeline.from_pretrained(
42
  )
43
  print("Speaker-diarization model ready!")
44
 
 
45
 
46
- def realign_word_timestamps(waveform_16k, words, whisper_start, whisper_end):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  """
48
  برای یه جمله، به‌جای زمان‌بندی حدسی خودِ Whisper، با یه مدل تخصصی جدا
49
  (wav2vec2 از طریق torchaudio) دقیقاً چک می‌کنه هر کلمه از کجای صدا گفته شده.
 
 
50
  اگه به هر دلیلی هم‌ترازی جواب نداد، همون زمان‌بندی قبلی Whisper دست‌نخورده می‌مونه.
51
  """
52
  try:
53
- margin = 1
54
- start_sample = max(0, int((whisper_start - margin) * 16000))
 
 
 
55
  end_sample = int((whisper_end + margin) * 16000)
56
  clip = waveform_16k[:, start_sample:end_sample]
57
 
@@ -88,8 +132,8 @@ def realign_word_timestamps(waveform_16k, words, whisper_start, whisper_end):
88
  if len(matched) < n_chars:
89
  new_words.append(w)
90
  continue
91
- w_start = matched[0].start * ratio + (whisper_start - margin)
92
- w_end = matched[-1].end * ratio + (whisper_start - margin)
93
  new_words.append({"word": w["word"], "start": round(max(0, w_start), 3), "end": round(max(0, w_end), 3)})
94
  span_idx += n_chars
95
 
@@ -99,17 +143,6 @@ def realign_word_timestamps(waveform_16k, words, whisper_start, whisper_end):
99
  return words
100
 
101
 
102
- def get_speaker_at(diarization, time_sec):
103
- """
104
- برای یه لحظه‌ی مشخص از صدا، می‌گه اون لحظه مال کدوم گوینده‌ست (SPEAKER_00, SPEAKER_01, ...).
105
- اگه هیچ گوینده‌ای پیدا نشد، None برمی‌گردونه.
106
- """
107
- for turn, _, speaker in diarization.itertracks(yield_label=True):
108
- if turn.start <= time_sec <= turn.end:
109
- return speaker
110
- return None
111
-
112
-
113
  @app.get("/health")
114
  async def health():
115
  return {"status": "ok", "message": "SubFlow backend is running"}
@@ -165,7 +198,7 @@ async def transcribe(websocket: WebSocket):
165
  print("Step 5: transcription done")
166
 
167
  await websocket.send_json({"status": "aligning"})
168
- print("Step 5.5: loading audio for word-alignment")
169
 
170
  align_waveform, sr = torchaudio.load(audio_to_transcribe)
171
  if sr != 16000:
@@ -173,8 +206,8 @@ async def transcribe(websocket: WebSocket):
173
  if align_waveform.shape[0] > 1:
174
  align_waveform = align_waveform.mean(dim=0, keepdim=True)
175
 
176
- print("Step 5.6: running speaker diarization")
177
  diarization = diarize_pipeline(audio_to_transcribe)
 
178
 
179
  segments = []
180
  for seg in segments_raw:
@@ -183,8 +216,12 @@ async def transcribe(websocket: WebSocket):
183
  continue
184
  word_dicts = [{"word": w.word, "start": w.start, "end": w.end} for w in words]
185
 
 
 
 
 
186
  realigned = realign_word_timestamps(
187
- align_waveform, word_dicts, word_dicts[0]["start"], word_dicts[-1]["end"]
188
  )
189
 
190
  mid_time = (realigned[0]["start"] + realigned[-1]["end"]) / 2
 
42
  )
43
  print("Speaker-diarization model ready!")
44
 
45
+ MIN_NOISE_BLOCK_SEC = 1.0 # حداقل طول یه بلوک بی‌گوینده تا «نویز» حساب بشه
46
 
47
+
48
+ def get_speaker_at(diarization, time_sec):
49
+ for turn, _, speaker in diarization.itertracks(yield_label=True):
50
+ if turn.start <= time_sec <= turn.end:
51
+ return speaker
52
+ return None
53
+
54
+
55
+ def find_leading_noise_cutoff(diarization, whisper_start, search_back_sec=4.0):
56
+ """
57
+ برمی‌گرده به عقب از شروعِ حدسیِ Whisper، دنبال یه بلوک نویز/بی‌گوینده‌ی
58
+ طولانی (حداقل MIN_NOISE_BLOCK_SEC) که دقیقاً قبل از این جمله باشه.
59
+ اگه پیدا کرد، زمان پایانِ اون بلوک نویز رو برمی‌گردونه (یعنی «از اینجا به بعد
60
+ اعتماد کن»)؛ اگه پیدا نکرد، None برمی‌گردونه (یعنی چیزی رو نبُر).
61
+ """
62
+ window_start = max(0, whisper_start - search_back_sec)
63
+ step = 0.05
64
+ t = whisper_start
65
+ noise_end = None
66
+ noise_start = None
67
+
68
+ while t > window_start:
69
+ speaker = get_speaker_at(diarization, t)
70
+ if speaker is None:
71
+ if noise_end is None:
72
+ noise_end = t
73
+ noise_start = t
74
+ else:
75
+ break
76
+ t -= step
77
+
78
+ if noise_start is not None and noise_end is not None:
79
+ block_len = noise_end - noise_start
80
+ if block_len >= MIN_NOISE_BLOCK_SEC:
81
+ return noise_end
82
+ return None
83
+
84
+
85
+ def realign_word_timestamps(waveform_16k, words, whisper_start, whisper_end, cutoff_start=None):
86
  """
87
  برای یه جمله، به‌جای زمان‌بندی حدسی خودِ Whisper، با یه مدل تخصصی جدا
88
  (wav2vec2 از طریق torchaudio) دقیقاً چک می‌کنه هر کلمه از کجای صدا گفته شده.
89
+ اگه cutoff_start داده بشه (یعنی یه بلوک نویز قبل از جمله شناسایی شده)،
90
+ جستجو از همون نقطه شروع میشه، نه از قبل‌ترش.
91
  اگه به هر دلیلی هم‌ترازی جواب نداد، همون زمان‌بندی قبلی Whisper دست‌نخورده می‌مونه.
92
  """
93
  try:
94
+ margin = 0.5
95
+ raw_start = whisper_start - margin
96
+ effective_start = max(raw_start, cutoff_start) if cutoff_start is not None else raw_start
97
+
98
+ start_sample = max(0, int(effective_start * 16000))
99
  end_sample = int((whisper_end + margin) * 16000)
100
  clip = waveform_16k[:, start_sample:end_sample]
101
 
 
132
  if len(matched) < n_chars:
133
  new_words.append(w)
134
  continue
135
+ w_start = matched[0].start * ratio + effective_start
136
+ w_end = matched[-1].end * ratio + effective_start
137
  new_words.append({"word": w["word"], "start": round(max(0, w_start), 3), "end": round(max(0, w_end), 3)})
138
  span_idx += n_chars
139
 
 
143
  return words
144
 
145
 
 
 
 
 
 
 
 
 
 
 
 
146
  @app.get("/health")
147
  async def health():
148
  return {"status": "ok", "message": "SubFlow backend is running"}
 
198
  print("Step 5: transcription done")
199
 
200
  await websocket.send_json({"status": "aligning"})
201
+ print("Step 5.5: loading audio + running speaker diarization")
202
 
203
  align_waveform, sr = torchaudio.load(audio_to_transcribe)
204
  if sr != 16000:
 
206
  if align_waveform.shape[0] > 1:
207
  align_waveform = align_waveform.mean(dim=0, keepdim=True)
208
 
 
209
  diarization = diarize_pipeline(audio_to_transcribe)
210
+ print("Step 5.6: diarization done, now aligning words per segment")
211
 
212
  segments = []
213
  for seg in segments_raw:
 
216
  continue
217
  word_dicts = [{"word": w.word, "start": w.start, "end": w.end} for w in words]
218
 
219
+ cutoff = find_leading_noise_cutoff(diarization, word_dicts[0]["start"])
220
+ if cutoff is not None:
221
+ print(f" [noise-trim] '{seg.text.strip()[:40]}...' cutoff moved from {word_dicts[0]['start']:.2f}s to {cutoff:.2f}s")
222
+
223
  realigned = realign_word_timestamps(
224
+ align_waveform, word_dicts, word_dicts[0]["start"], word_dicts[-1]["end"], cutoff_start=cutoff
225
  )
226
 
227
  mid_time = (realigned[0]["start"] + realigned[-1]["end"]) / 2