fixed a gain
This commit is contained in:
@@ -159,7 +159,8 @@ segment 会自动合并成同一句。若转写服务支持词级时间戳
|
|||||||
标点所在单词的时间戳;不支持时自动按文字长度在 segment 内估算,请求失败会回退为
|
标点所在单词的时间戳;不支持时自动按文字长度在 segment 内估算,请求失败会回退为
|
||||||
不带词级时间戳的普通请求。MOSS 不支持该参数时同样自动回退,不影响转写。
|
不带词级时间戳的普通请求。MOSS 不支持该参数时同样自动回退,不影响转写。
|
||||||
每句结束时间默认再向后顺延 300ms(`MOSS_END_PADDING_MS`,可在 200–500 之间调整),
|
每句结束时间默认再向后顺延 300ms(`MOSS_END_PADDING_MS`,可在 200–500 之间调整),
|
||||||
避免句子末尾发音被切掉;顺延不会越过下一句的开头。
|
避免句子末尾发音被切掉;顺延不会越过下一句的开头。顺延后还会用音频检测句子间的
|
||||||
|
停顿,把结束时间拉回到下一句语音真正开始之前,避免偶尔切到下一句的开头。
|
||||||
|
|
||||||
## 3. 部署 API
|
## 3. 部署 API
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import numpy as np
|
|||||||
|
|
||||||
SAMPLE_RATE = 16_000
|
SAMPLE_RATE = 16_000
|
||||||
FRAME_SAMPLES = 480
|
FRAME_SAMPLES = 480
|
||||||
|
# A silence run at least this long (in 30 ms frames) separates two sentences.
|
||||||
|
# speech_frame_mask bridges gaps of up to 5 frames, so use 6 frames (~180 ms).
|
||||||
|
MIN_PAUSE_FRAMES = 6
|
||||||
|
ONSET_MARGIN_MS = 50
|
||||||
|
|
||||||
|
|
||||||
class AudioAnalysisError(RuntimeError):
|
class AudioAnalysisError(RuntimeError):
|
||||||
@@ -108,6 +112,44 @@ def speech_frame_mask(samples: np.ndarray, sample_rate: int = SAMPLE_RATE) -> np
|
|||||||
return speech
|
return speech
|
||||||
|
|
||||||
|
|
||||||
|
def refine_sentence_end_ms(
|
||||||
|
samples: np.ndarray,
|
||||||
|
sample_rate: int = SAMPLE_RATE,
|
||||||
|
*,
|
||||||
|
raw_end_ms: int,
|
||||||
|
padded_end_ms: int,
|
||||||
|
) -> int:
|
||||||
|
"""Pull a padded sentence end back to just before the next sentence's speech.
|
||||||
|
|
||||||
|
Whisper's timestamps are not always aligned with the real audio: the next
|
||||||
|
segment's start can be later than the actual speech onset, so a fixed
|
||||||
|
end-padding may occasionally run into the next sentence's beginning. This
|
||||||
|
finds the first silence run of at least MIN_PAUSE_FRAMES inside the padded
|
||||||
|
region and stops the sentence just before the speech that follows it.
|
||||||
|
"""
|
||||||
|
if padded_end_ms <= raw_end_ms:
|
||||||
|
return padded_end_ms
|
||||||
|
frame_ms = 1000 * max(1, int(round(sample_rate * 0.03))) / sample_rate
|
||||||
|
start_sample = max(0, int(raw_end_ms / 1000 * sample_rate))
|
||||||
|
end_sample = min(samples.size, int(padded_end_ms / 1000 * sample_rate))
|
||||||
|
if end_sample <= start_sample:
|
||||||
|
return padded_end_ms
|
||||||
|
try:
|
||||||
|
speech = speech_frame_mask(samples[start_sample:end_sample], sample_rate)
|
||||||
|
except AudioAnalysisError:
|
||||||
|
return padded_end_ms
|
||||||
|
silence_frames = 0
|
||||||
|
for index, is_speech in enumerate(speech):
|
||||||
|
if not is_speech:
|
||||||
|
silence_frames += 1
|
||||||
|
continue
|
||||||
|
if silence_frames >= MIN_PAUSE_FRAMES:
|
||||||
|
onset_ms = int(raw_end_ms + index * frame_ms)
|
||||||
|
return max(raw_end_ms, min(padded_end_ms, onset_ms - ONSET_MARGIN_MS))
|
||||||
|
silence_frames = 0
|
||||||
|
return padded_end_ms
|
||||||
|
|
||||||
|
|
||||||
def _bridge_false_runs(values: np.ndarray, max_frames: int) -> None:
|
def _bridge_false_runs(values: np.ndarray, max_frames: int) -> None:
|
||||||
start = None
|
start = None
|
||||||
for index, value in enumerate(values):
|
for index, value in enumerate(values):
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import uuid
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from .audio_metrics import AudioAnalysisError, analyze_samples, decode_audio_mono
|
from .audio_metrics import (
|
||||||
|
AudioAnalysisError,
|
||||||
|
analyze_samples,
|
||||||
|
decode_audio_mono,
|
||||||
|
refine_sentence_end_ms,
|
||||||
|
)
|
||||||
from .config import Settings
|
from .config import Settings
|
||||||
from .generate_boundaries import ALGORITHM_VERSION, make_entry
|
from .generate_boundaries import ALGORITHM_VERSION, make_entry
|
||||||
from .models import SentenceBoundary, SentenceBoundaryDocument
|
from .models import SentenceBoundary, SentenceBoundaryDocument
|
||||||
@@ -112,15 +117,19 @@ def document_from_transcript(
|
|||||||
sentence_segments = split_sentences_at_punctuation(transcript.segments)
|
sentence_segments = split_sentences_at_punctuation(transcript.segments)
|
||||||
for index, sentence_segment in enumerate(sentence_segments):
|
for index, sentence_segment in enumerate(sentence_segments):
|
||||||
start_ms = max(previous_end, int(round(sentence_segment.start_seconds * 1000)))
|
start_ms = max(previous_end, int(round(sentence_segment.start_seconds * 1000)))
|
||||||
|
raw_end_ms = int(round(sentence_segment.end_seconds * 1000))
|
||||||
end_ms = min(
|
end_ms = min(
|
||||||
duration_ms,
|
duration_ms,
|
||||||
int(round(sentence_segment.end_seconds * 1000)) + end_padding_ms,
|
raw_end_ms + end_padding_ms,
|
||||||
)
|
)
|
||||||
if index + 1 < len(sentence_segments):
|
if index + 1 < len(sentence_segments):
|
||||||
next_start_ms = int(
|
next_start_ms = int(
|
||||||
round(sentence_segments[index + 1].start_seconds * 1000)
|
round(sentence_segments[index + 1].start_seconds * 1000)
|
||||||
)
|
)
|
||||||
end_ms = min(end_ms, next_start_ms)
|
end_ms = min(end_ms, next_start_ms)
|
||||||
|
end_ms = refine_sentence_end_ms(
|
||||||
|
samples, sample_rate, raw_end_ms=raw_end_ms, padded_end_ms=end_ms
|
||||||
|
)
|
||||||
if not sentence_segment.text.strip() or end_ms <= start_ms:
|
if not sentence_segment.text.strip() or end_ms <= start_ms:
|
||||||
continue
|
continue
|
||||||
start_sample = max(0, int(start_ms / 1000 * sample_rate))
|
start_sample = max(0, int(start_ms / 1000 * sample_rate))
|
||||||
|
|||||||
Reference in New Issue
Block a user