- Зачем: - транскрипция может тихо терять содержание: блоки галлюцинированных повторов и обрыв распознавания до конца файла выглядят как законченный транскрипт (класс ошибок из ADR-006), и читатель об этом не узнаёт. - Что: - новый модуль quality.py: детектор разрыва в хвосте (строго >120 с) и детектор блоков повторяющихся сегментов (серия >=4 для длинного текста, >=10 для короткого вроде «ага»). - предупреждения жёлтым в консоли (single и batch) и строками «Внимание» в шапке markdown-транскрипта; _format_duration переименована в публичную format_duration. - 27 новых тестов: границы порогов, нормализация текста, шапка formatter, точные строки CLI-сообщений с обрезкой «(+ ещё N)». - Проверка: - uv run pytest — 218 passed, 1 skipped. - ручной прогон записи 23:01 (gigaam-v3/onnx) — транскрипт полный, предупреждений нет. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
126 lines
3.6 KiB
Python
126 lines
3.6 KiB
Python
import pytest
|
|
|
|
from local_transcriber.quality import (
|
|
REPETITION_MIN_LEN,
|
|
TAIL_GAP_WARN_S,
|
|
_normalize,
|
|
find_repetition_blocks,
|
|
tail_gap,
|
|
)
|
|
from local_transcriber.types import Segment, TranscribeResult
|
|
|
|
|
|
def _result(segments, duration):
|
|
return TranscribeResult(
|
|
segments=segments,
|
|
language="ru",
|
|
language_probability=0.95,
|
|
duration=duration,
|
|
device_used="cpu",
|
|
)
|
|
|
|
|
|
def _segments(texts, start=0.0):
|
|
return [
|
|
Segment(start=start + index, end=start + index + 1.0, text=text)
|
|
for index, text in enumerate(texts)
|
|
]
|
|
|
|
|
|
def test_tail_gap_returns_positive_gap():
|
|
result = _result([Segment(0.0, 10.0, "Текст")], duration=42.0)
|
|
|
|
assert tail_gap(result) == 32.0
|
|
|
|
|
|
def test_tail_gap_empty_segments_returns_zero():
|
|
assert tail_gap(_result([], duration=42.0)) == 0.0
|
|
|
|
|
|
def test_tail_gap_negative_gap_returns_zero():
|
|
result = _result([Segment(0.0, 43.0, "Текст")], duration=42.0)
|
|
|
|
assert tail_gap(result) == 0.0
|
|
|
|
|
|
@pytest.mark.parametrize("gap", [119.99, 120.0])
|
|
def test_tail_gap_boundary_does_not_warn(gap):
|
|
result = _result([Segment(0.0, 10.0, "Текст")], duration=10.0 + gap)
|
|
|
|
assert tail_gap(result) <= TAIL_GAP_WARN_S
|
|
|
|
|
|
def test_tail_gap_boundary_warns_above_threshold():
|
|
result = _result([Segment(0.0, 10.0, "Текст")], duration=130.01)
|
|
|
|
assert tail_gap(result) > TAIL_GAP_WARN_S
|
|
|
|
|
|
def test_normalize_removes_case_punctuation_and_collapses_spaces():
|
|
assert _normalize(' «ПРИВЕТ…» — (мир) [тест] <да> ') == "привет мир тест да"
|
|
assert _normalize("раз–два-три: да; нет!") == "раздватри да нет"
|
|
|
|
|
|
def test_normalize_punctuation_only_returns_empty():
|
|
assert _normalize('.,!?…:;—–-"\'«»()[]<> ') == ""
|
|
|
|
|
|
def test_find_repetition_blocks_four_long_segments_with_normalization():
|
|
segments = _segments(["Повтор!", "повтор", "«ПОВТОР»", "повтор…"])
|
|
|
|
blocks = find_repetition_blocks(segments)
|
|
|
|
assert len(blocks) == 1
|
|
assert blocks[0].start == 0.0
|
|
assert blocks[0].end == 4.0
|
|
assert blocks[0].count == 4
|
|
assert blocks[0].text == "Повтор!"
|
|
|
|
|
|
def test_find_repetition_blocks_three_long_segments_is_empty():
|
|
assert find_repetition_blocks(_segments(["Повтор", "повтор", "повтор"])) == []
|
|
|
|
|
|
def test_find_repetition_blocks_length_boundary():
|
|
assert find_repetition_blocks(_segments(["пять5"] * 4)) == []
|
|
|
|
blocks = find_repetition_blocks(_segments(["шесть6"] * 4))
|
|
|
|
assert len("шесть6") == REPETITION_MIN_LEN
|
|
assert len(blocks) == 1
|
|
|
|
|
|
def test_find_repetition_blocks_short_text_boundary():
|
|
assert find_repetition_blocks(_segments(["Ага."] * 9)) == []
|
|
|
|
blocks = find_repetition_blocks(_segments(["Ага."] * 10))
|
|
|
|
assert len(blocks) == 1
|
|
assert blocks[0].count == 10
|
|
|
|
|
|
def test_find_repetition_blocks_empty_normalized_text_is_ignored():
|
|
assert find_repetition_blocks(_segments([":", "", " "] * 10)) == []
|
|
|
|
|
|
def test_find_repetition_blocks_two_separate_runs():
|
|
segments = [
|
|
*_segments(["Первый повтор"] * 4),
|
|
Segment(10.0, 11.0, "Разрыв"),
|
|
*_segments(["Второй повтор"] * 4, start=20.0),
|
|
]
|
|
|
|
blocks = find_repetition_blocks(segments)
|
|
|
|
assert len(blocks) == 2
|
|
assert blocks[0].text == "Первый повтор"
|
|
assert blocks[1].text == "Второй повтор"
|
|
|
|
|
|
def test_find_repetition_blocks_empty_list_is_empty():
|
|
assert find_repetition_blocks([]) == []
|
|
|
|
|
|
def test_find_repetition_blocks_single_segment_is_empty():
|
|
assert find_repetition_blocks([Segment(0.0, 1.0, "Повтор")]) == []
|