continue fixing

This commit is contained in:
2026-08-18 21:57:25 +08:00
parent aa8baab7c0
commit 3f86e8f44e
6 changed files with 202 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
from dataclasses import dataclass
from pathlib import Path
from typing import Tuple
from typing import Optional, Tuple
import numpy as np
@@ -11,6 +11,8 @@ FRAME_SAMPLES = 480
# speech_frame_mask bridges gaps of up to 5 frames, so use 6 frames (~180 ms).
MIN_PAUSE_FRAMES = 6
ONSET_MARGIN_MS = 50
START_LOOKBACK_MS = 300
START_LOOKAHEAD_MS = 400
class AudioAnalysisError(RuntimeError):
@@ -150,6 +152,57 @@ def refine_sentence_end_ms(
return padded_end_ms
def refine_sentence_start_ms(
samples: np.ndarray,
sample_rate: int = SAMPLE_RATE,
*,
raw_start_ms: int,
previous_end_ms: int,
) -> int:
"""Pull a sentence start forward to the actual speech onset.
Whisper's segment start can be earlier than the real speech onset (the tail
of the previous sentence is still audible at the beginning of the next
sentence). When the raw start lands inside continuous speech, this looks for
the last pause of at least MIN_PAUSE_FRAMES before the next speech run and
moves the start to that onset, so adjacent sentences share no audio.
"""
if raw_start_ms <= previous_end_ms:
return max(previous_end_ms, raw_start_ms)
frame_ms = 1000 * max(1, int(round(sample_rate * 0.03))) / sample_rate
total_ms = samples.size / sample_rate * 1000
window_start_ms = max(0, raw_start_ms - START_LOOKBACK_MS)
window_end_ms = min(int(total_ms), raw_start_ms + START_LOOKAHEAD_MS)
start_sample = int(window_start_ms / 1000 * sample_rate)
end_sample = min(samples.size, int(window_end_ms / 1000 * sample_rate))
if end_sample - start_sample < max(1, int(round(sample_rate * 0.03))):
return raw_start_ms
try:
speech = speech_frame_mask(samples[start_sample:end_sample], sample_rate)
except AudioAnalysisError:
return raw_start_ms
onset_frame = int(round((raw_start_ms - window_start_ms) / frame_ms))
if (
0 <= onset_frame < len(speech)
and speech[onset_frame]
and (onset_frame == 0 or not speech[onset_frame - 1])
):
return raw_start_ms # already at a speech onset
boundary_ms: Optional[float] = None
silence_frames = 0
for index, is_speech in enumerate(speech):
if not is_speech:
silence_frames += 1
continue
if silence_frames >= MIN_PAUSE_FRAMES:
boundary_ms = window_start_ms + index * frame_ms
silence_frames = 0
if boundary_ms is None:
return raw_start_ms
boundary_ms = min(boundary_ms, raw_start_ms + START_LOOKAHEAD_MS)
return max(previous_end_ms, int(boundary_ms))
def _bridge_false_runs(values: np.ndarray, max_frames: int) -> None:
start = None
for index, value in enumerate(values):