Skip to content

Commit 0a9cc00

Browse files
authored
fix(auto-model): preserve VAD segments on punctuation mismatch (#3434)
Fall back to VAD-aligned string sentence records when a sized punctuation array cannot align with long-audio ASR timestamps. Verified against both reporter attachments: 103.mp3 recovers 199 sentence records and 2980.mp3 remains byte-for-byte unchanged. Signed-off-by: LauraGPT <lauragpt@users.noreply.github.com>
1 parent cbed336 commit 0a9cc00

2 files changed

Lines changed: 98 additions & 14 deletions

File tree

funasr/auto/auto_model.py

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,42 @@ def _join_vad_texts(texts):
6868
return joined
6969

7070

71+
def _vad_segment_sentences(restored_data, vadsegments):
72+
"""Build readable sentence records directly from VAD-aligned ASR chunks."""
73+
sentences = []
74+
for result, vadsegment in zip(restored_data, vadsegments):
75+
text = re.sub(r"<\|[^|]*\|>", "", str(result.get("text", ""))).strip()
76+
if not text:
77+
continue
78+
79+
timestamps = []
80+
raw_timestamps = result.get("timestamp")
81+
if raw_timestamps is None:
82+
raw_timestamps = result.get("timestamps", [])
83+
for item in raw_timestamps or []:
84+
if isinstance(item, dict):
85+
start = item.get("start_time")
86+
end = item.get("end_time")
87+
if start is None or end is None:
88+
continue
89+
timestamps.append([int(float(start) * 1000), int(float(end) * 1000)])
90+
elif isinstance(item, (list, tuple)) and len(item) >= 2:
91+
timestamps.append([int(item[0]), int(item[1])])
92+
93+
start = timestamps[0][0] if timestamps else vadsegment[0]
94+
end = timestamps[-1][1] if timestamps else vadsegment[1]
95+
sentences.append(
96+
{
97+
"start": start,
98+
"end": end,
99+
"text": text,
100+
"sentence": text,
101+
"timestamp": timestamps,
102+
}
103+
)
104+
return sentences
105+
106+
71107
def _get_punc_tokens(text, punc_array, punc_model):
72108
"""Return the surface tokens represented by a CT-Transformer punctuation array."""
73109
try:
@@ -1018,6 +1054,7 @@ def inference_with_vad(self, input, input_len=None, **cfg):
10181054

10191055
timestamp_text = punc_input_text
10201056
sentence_timestamps = result.get("timestamp", [])
1057+
punc_alignment_failed = False
10211058
if punc_res is not None:
10221059
try:
10231060
punc_length = len(punc_array)
@@ -1038,6 +1075,7 @@ def inference_with_vad(self, input, input_len=None, **cfg):
10381075
timestamp_text, sentence_timestamps = merged_units
10391076
if punc_array is not None and punc_length != len(sentence_timestamps):
10401077
punc_array = None
1078+
punc_alignment_failed = True
10411079
surface_sentence_list = None
10421080
if aligned_word_text is not None and punc_array is not None:
10431081
surface_sentence_list = _timestamp_sentences_from_surface(
@@ -1131,25 +1169,17 @@ def inference_with_vad(self, input, input_len=None, **cfg):
11311169
if not len(result["text"].strip()):
11321170
sentence_list = []
11331171
elif self.punc_model is None and punc_res is None and not sentence_timestamps:
1134-
sentence_list = []
1135-
for rest, vadsegment in zip(restored_data, vadsegments):
1136-
text = str(rest.get("text", "")).strip()
1137-
if not text:
1138-
continue
1139-
sentence_list.append(
1140-
{
1141-
"start": vadsegment[0],
1142-
"end": vadsegment[1],
1143-
"text": text,
1144-
"sentence": text,
1145-
"timestamp": [],
1146-
}
1147-
)
1172+
sentence_list = _vad_segment_sentences(restored_data, vadsegments)
11481173
elif punc_res is None:
11491174
logging.warning(
11501175
"punc_model is required for sentence_timestamp, skipping sentence segmentation."
11511176
)
11521177
sentence_list = []
1178+
elif punc_alignment_failed:
1179+
logging.warning(
1180+
"punctuation timestamps could not be aligned, falling back to VAD segments."
1181+
)
1182+
sentence_list = _vad_segment_sentences(restored_data, vadsegments)
11531183
elif surface_sentence_list is not None:
11541184
sentence_list = surface_sentence_list
11551185
else:

tests/test_punc_model_none.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,60 @@ def mock_inference(data, *args, **kwargs):
274274
],
275275
)
276276

277+
@patch("funasr.auto.auto_model.slice_padding_audio_samples")
278+
@patch("funasr.auto.auto_model.load_audio_text_image_video")
279+
@patch("funasr.auto.auto_model.prepare_data_iterator")
280+
def test_sentence_timestamp_falls_back_to_vad_when_punctuation_alignment_fails(
281+
self, mock_prep, mock_load, mock_slice
282+
):
283+
"""A malformed long-audio punctuation array must not collapse all VAD segments."""
284+
am = self._make_auto_model(punc_model=MagicMock())
285+
tag = "<|zh|><|NEUTRAL|><|Speech|><|woitn|>"
286+
results_seq = [
287+
[{"key": "test_utt", "value": [[100, 1100], [1300, 2300]]}],
288+
[
289+
{
290+
"text": f"{tag}第一句",
291+
"timestamp": [[0, 300], [300, 600], [600, 900]],
292+
"words": ["第", "一", "句"],
293+
}
294+
],
295+
[
296+
{
297+
"text": f"{tag}第二句",
298+
"timestamp": [[0, 300], [300, 600], [600, 900]],
299+
"words": ["第", "二", "句"],
300+
}
301+
],
302+
[{"text": "第一句。第二句。", "punc_array": [3]}],
303+
]
304+
am.inference = MagicMock(side_effect=lambda *args, **kwargs: results_seq.pop(0))
305+
mock_prep.return_value = (["test_utt"], [np.zeros(40000, dtype=np.float32)])
306+
mock_load.return_value = np.zeros(40000, dtype=np.float32)
307+
mock_slice.return_value = ([np.zeros(16000, dtype=np.float32)], [16000])
308+
309+
results = am.inference_with_vad("dummy_input", sentence_timestamp=True)
310+
311+
self.assertEqual(
312+
results[0]["sentence_info"],
313+
[
314+
{
315+
"start": 100,
316+
"end": 1000,
317+
"text": "第一句",
318+
"sentence": "第一句",
319+
"timestamp": [[100, 400], [400, 700], [700, 1000]],
320+
},
321+
{
322+
"start": 1300,
323+
"end": 2200,
324+
"text": "第二句",
325+
"sentence": "第二句",
326+
"timestamp": [[1300, 1600], [1600, 1900], [1900, 2200]],
327+
},
328+
],
329+
)
330+
277331
@patch("funasr.auto.auto_model.distribute_spk")
278332
@patch("funasr.auto.auto_model.postprocess")
279333
@patch("funasr.auto.auto_model.sv_chunk")

0 commit comments

Comments
 (0)